M prolog/builtin/modules.py +1 -1
@@ 37,7 37,7 @@ def handle_use_module_with_library(engin
             else:
                 assert isinstance(fd, int)
                 os.close(fd) # cleanup
-                newpath = Atom(temppath)
+                newpath = Atom.fromname(temppath)
                 break
         if not newpath:
             error.throw_existence_error("source_sink", arg)

          
M prolog/builtin/streams.py +5 -5
@@ 63,11 63,11 @@ def impl_open_options(engine, heap, srcp
 
         try:
             alias = opts["alias"]
-            prolog_stream.alias = alias
+            prolog_stream.setalias(alias)
         except KeyError:
-            alias = "$stream_%d" % prolog_stream.fd()
+            alias = prolog_stream.alias
         engine.streamwrapper.aliases[alias] = prolog_stream
-        stream.unify(term.Callable.build(alias), heap)
+        stream.unify(prolog_stream.alias_atom, heap)
 
 @expose_builtin("open", unwrap_spec=["atom", "atom", "obj"])
 def impl_open(engine, heap, srcpath, mode, stream):

          
@@ 216,13 216,13 @@ def impl_put_byte_1(engine, heap, obj):
 def impl_current_input(engine, heap, obj):
     if not isinstance(obj, term.Var) and not isinstance(obj, term.Atom):
         error.throw_domain_error("stream", obj)
-    obj.unify(term.Atom(engine.streamwrapper.current_instream.alias), heap)
+    obj.unify(engine.streamwrapper.current_instream.alias_atom, heap)
 
 @expose_builtin("current_output", unwrap_spec=["obj"])
 def impl_current_output(engine, heap, obj):
     if not isinstance(obj, term.Var) and not isinstance(obj, term.Atom):
         error.throw_domain_error("stream", obj)
-    obj.unify(term.Atom(engine.streamwrapper.current_outstream.alias), heap)
+    obj.unify(engine.streamwrapper.current_outstream.alias_atom, heap)
 
 @expose_builtin("set_input", unwrap_spec=["instream"])
 def impl_set_input(engine, heap, stream):

          
M prolog/interpreter/helper.py +2 -2
@@ 104,7 104,7 @@ def unwrap_instream(engine, obj):
         else:
             if not isinstance(stream, PrologInputStream):
                 error.throw_permission_error("input", "stream",
-                        term.Atom(stream.alias))
+                        stream.alias_atom)
             assert isinstance(stream, PrologInputStream)
             return stream
     error.throw_domain_error("stream", obj)

          
@@ 120,7 120,7 @@ def unwrap_outstream(engine, obj):
         else:
             if not isinstance(stream, PrologOutputStream):
                 error.throw_permission_error("output", "stream",
-                        term.Atom(stream.alias))
+                        stream.alias_atom)
             assert isinstance(stream, PrologOutputStream)
             return stream
     error.throw_domain_error("stream", obj)

          
M prolog/interpreter/module.py +1 -1
@@ 61,7 61,7 @@ class Module(object):
     _immutable_fields_ = ["name", "nameatom", "_toplevel_rule"]
     def __init__(self, name):
         self.name = name
-        self.nameatom = Atom(name)
+        self.nameatom = Atom.fromname(name)
         self.functions = {}
         self.exports = []
         self._toplevel_rule = _make_toplevel_rule(self)

          
M prolog/interpreter/signature.py +11 -1
@@ 62,7 62,7 @@ class Signature(object):
 
     _cache = SignatureFactory()
 
-    _immutable_fields_ = ["name", "numargs", "atom_signature", "factory"]
+    _immutable_fields_ = ["name", "numargs", "atom_signature", "factory", "default_location"]
 
     def __init__(self, name, numargs, cached=False, factory=None):
         assert name is not None

          
@@ 79,6 79,7 @@ class Signature(object):
         else:
             atom_signature = self
         self.atom_signature = atom_signature
+        self.default_location = Location(self, "default")
         factory.init_extra_attrs(self)
 
     def eq(self, other):

          
@@ 144,3 145,12 @@ class Signature(object):
     @staticmethod
     def register_extr_attr(name, engine=False, default=None):
         Signature._cache.register_extr_attr(name, engine, default)
+
+
+class Location(object):
+    _immutable_fields_ = ['signature']
+
+    def __init__(self, signature, repr=None):
+        self.signature = signature
+        self.repr = repr
+

          
M prolog/interpreter/stream.py +6 -2
@@ 1,5 1,5 @@ 
 from rpython.rlib.streamio import fdopen_as_stream
-from prolog.interpreter.term import NonVar
+from prolog.interpreter.term import NonVar, Atom
 from prolog.interpreter.error import UnificationFailed
 
 class StreamWrapper(object):

          
@@ 16,7 16,11 @@ class StreamWrapper(object):
 class PrologStream(object):
     def __init__(self, stream):
         self.stream = stream
-        self.alias = "$stream_%d" % self.fd()
+        self.setalias("$stream_%d" % self.fd())
+
+    def setalias(self, alias):
+        self.alias = alias
+        self.alias_atom = Atom.fromname(alias)
 
     def fd(self):   
         return self.stream.try_to_find_file_descriptor()

          
M prolog/interpreter/term.py +82 -48
@@ 385,6 385,7 @@ class NonVar(PrologObject):
         return self
 
 class Callable(NonVar):
+    """ Abstract base class for compound terms and atoms. """
     __slots__ = ()
 
     def __init__(self):

          
@@ 396,6 397,9 @@ class Callable(NonVar):
     def signature(self):
         raise NotImplementedError("abstract base")
     
+    def location(self):
+        raise NotImplementedError("abstract base")
+    
     def get_prolog_signature(self):
         return Callable.build("/", [Callable.build(self.name()),
                                     Number(self.argument_count())])

          
@@ 494,10 498,29 @@ class Callable(NonVar):
         return func(engine, self)
     
     @staticmethod
-    @jit.unroll_safe
     def build(term_name, args=None, signature=None, heap=None, cache=True):
         if args is None:
             args = []
+        if signature is None:
+            if cache:
+                signature = Signature.getsignature(term_name, len(args))
+            else:
+                signature = Signature(term_name, len(args))
+        else:
+            assert signature.numargs == len(args)
+        assert isinstance(signature, Signature)
+        return Callable.build_sig(args, signature, heap=heap, cache=cache)
+
+    @staticmethod
+    @jit.unroll_safe
+    def build_sig(args, signature, heap=None, cache=True):
+        location = signature.default_location
+        return Callable.build_location(args, location, heap, cache)
+
+    @staticmethod
+    @jit.unroll_safe
+    def build_location(args, location, heap=None, cache=True):
+        signature = location.signature
         if heap is not None:
             # perform variable shunting:
             # remove variables that are not needed because they are bound

          
@@ 509,30 532,24 @@ class Callable(NonVar):
                     args[i] = arg.getbinding()
         if len(args) == 0:
             if cache:
-                return Atom.newatom(term_name, signature)
-            return Atom(term_name, signature)
+                return Atom.newatom(signature)
+            return Atom(signature)
         else:
-            if signature is None:
-                if cache:
-                    signature = Signature.getsignature(term_name, len(args))
-                else:
-                    signature = Signature(term_name, len(args))
-            else:
-                assert signature.numargs == len(args)
-            assert isinstance(signature, Signature)
 
-            cls = Callable._find_specialized_class(term_name, len(args))
+            cls = Callable._find_specialized_class(signature)
             if cls is not None:
-                return cls(term_name, args, signature)
-            cls = Callable._find_specialized_class('Term', len(args))
-            if cls is not None:
-                return cls(term_name, args, signature)
-            return Term(term_name, args, signature)
+                return cls(args, location)
+            return Term(args, location)
 
     @staticmethod
     @jit.elidable
-    def _find_specialized_class(term_name, numargs):
-        return specialized_term_classes.get((term_name, numargs), None)
+    def _find_specialized_class(signature):
+        term_name = signature.name
+        numargs = signature.numargs
+        res = specialized_term_classes.get((term_name, numargs), None)
+        if res is not None:
+            return res
+        return specialized_term_classes.get(('Term', numargs), None)
 
     def __repr__(self):
         return "%s(%s, %r)" % (self.__class__.__name__, self.name(),

          
@@ 555,15 572,19 @@ class Callable(NonVar):
 
 class Atom(Callable):
     TYPE_STANDARD_ORDER = 2
-    __slots__ = ('_name', '_signature')
+    __slots__ = ('_signature', )
     cache = {}
     _immutable_fields_ = ["_signature"]
     
-    def __init__(self, name, signature=None):
-        if signature is None:
-            signature = Signature(name, 0)
+    def __init__(self, signature):
+        assert signature is not None
+        assert isinstance(signature, Signature)
         Callable.__init__(self)
         self._signature = signature
+
+    @staticmethod
+    def fromname(name):
+        return Atom(Signature(name, 0))
     
     def __str__(self):
         return self.name()

          
@@ 573,13 594,11 @@ class Atom(Callable):
     
     @staticmethod
     @jit.elidable
-    def newatom(name, signature=None):
-        if signature is None:
-            signature = Signature.getsignature(name, 0)
+    def newatom(signature):
         result = Atom.cache.get(signature, None)
         if result is not None:
             return result
-        Atom.cache[signature] = result = Atom(name, signature)
+        Atom.cache[signature] = result = Atom(signature)
         return result
     
     def eval_arithmetic(self, engine):

          
@@ 605,6 624,9 @@ class Atom(Callable):
     def signature(self):
         return self._signature
 
+    def location(self):
+        return self._signature.default_location
+    
     def nonvar_unify_and_standardize_apart(self, other, heap, env):
         if not isinstance(other, Atom):
             raise UnificationFailed

          
@@ 748,13 770,12 @@ def _term_unify_and_standardize_apart(ob
 
 class Term(Callable):
     TYPE_STANDARD_ORDER = 3
-    _immutable_fields_ = ["_args[*]", "_name", "_signature"]
-    __slots__ = ('_name', '_signature', '_args')
+    _immutable_fields_ = ["_args[*]", "_name", "_location"]
+    __slots__ = ('_name', '_location', '_args')
     
-    def __init__(self, term_name, args, signature):
-        assert signature.name == term_name
+    def __init__(self, args, location):
         self._args = make_sure_not_resized(args)
-        self._signature = signature
+        self._location = location
         Callable.__init__(self)
     
     def __repr__(self):

          
@@ 773,7 794,11 @@ class Term(Callable):
         return len(self._args)
     
     def signature(self):
-        return self._signature
+        return self._location.signature
+
+    def location(self):
+        return self._location
+
 
 @specialize.argtype(0)
 def rcmp(a, b): # RPython does not support cmp...

          
@@ 809,9 834,9 @@ def generate_class(cname, fname, n_args)
         else:
             TYPE_STANDARD_ORDER = Term.TYPE_STANDARD_ORDER
 
-        def __init__(self, term_name, args, signature):
+        def __init__(self, args, location):
+            assert location.signature is signature
             parent._init_values(self, args)
-            assert self.name() == term_name
             assert args is None or len(args) == n_args
 
         def name(self):

          
@@ 820,9 845,12 @@ def generate_class(cname, fname, n_args)
         def signature(self):
             return signature
 
-        def _make_new(self, name, signature):
+        def location(self):
+            return signature.default_location
+    
+        def _make_new(self, location):
             cls = specific_class
-            return cls(name, None, signature)
+            return cls(None, location)
 
     specific_class.__name__ = cname
     return specific_class

          
@@ 834,7 862,7 @@ def generate_abstract_class(n_args):
 
         _immutable_fields_ = ["val_%d" % x for x in arg_iter]
 
-        def __init__(self, term_name, args, signature):
+        def __init__(self, args, location):
             raise NotImplementedError
 
         def _init_values(self, args):

          
@@ 843,7 871,7 @@ def generate_abstract_class(n_args):
             for x in arg_iter:
                 setattr(self, 'val_%d' % x, args[x])
 
-        def _make_new(self, name, signature):
+        def _make_new(self, location):
             raise NotImplementedError("abstract base class")
 
         def arguments(self):

          
@@ 890,7 918,7 @@ def generate_abstract_class(n_args):
                 raise UnificationFailed
 
         def copy_standardize_apart(self, heap, env):
-            result = self._make_new(self.name(), self.signature())
+            result = self._make_new(self.location())
             reuse = True
             i = 0
             for i in arg_iter:

          
@@ 926,7 954,7 @@ def generate_abstract_class(n_args):
 
         @specialize.arg(1)
         def _copy_term(self, copy_individual, heap, *extraargs):
-            result = self._make_new(self.name(), self.signature())
+            result = self._make_new(self.location())
             newinstance = False
             i = 0
             for i in arg_iter:

          
@@ 949,21 977,24 @@ def generate_generic_class(n_args):
     assert parent is not None
 
     class generic_callable(parent):
-        _immutable_fields_ = ["_signature"]
+        _immutable_fields_ = ["_location"]
         TYPE_STANDARD_ORDER = Term.TYPE_STANDARD_ORDER
 
-        def __init__(self, term_name, args, signature):
+        def __init__(self, args, location):
             parent._init_values(self, args)
-            self._signature = signature
+            self._location = location
             assert args is None or len(args) == n_args
-            assert self.name() == term_name
 
-        def _make_new(self, name, signature):
+        def _make_new(self, location):
             cls = generic_callable
-            return cls(name, None, signature)
+            return cls(None, location)
 
         def signature(self):
-            return self._signature
+            return self._location.signature
+
+        def location(self):
+            return self._location
+
     generic_callable.__name__ = 'Generic'+str(n_args)
     return generic_callable
 

          
@@ 971,14 1002,17 @@ def generate_generic_class(n_args):
 specialized_term_classes = {}
 callables = {}
 
+# generate base classes
 for numargs in range(1, OPTIMIZED_TERM_SIZE_MAX):
     callables['Abstract', numargs] = generate_abstract_class(numargs)
 
+# generate classes that store the signature on the class
 classes = [('Cons', '.', 2), ('Or', ';', 2), ('And', ',', 2)]
 for cname, fname, numargs in classes:
     specialized_term_classes[fname, numargs] = generate_class(
                                                         cname, fname, numargs)
 
+# generate classes that store the signature on the instance
 for numargs in range(1, 10):
     assert ('Term', numargs) not in specialized_term_classes
     specialized_term_classes['Term', numargs] = generate_generic_class(numargs)

          
M prolog/interpreter/test/test_heap.py +1 -1
@@ 267,7 267,7 @@ def test_simple_hooks():
     v1.unify(a1, hp)
     v2.unify(a2, hp)
     t1 = Callable.build("f", [v1, v2])
-    t2 = Callable.build("f", [Atom("a"), Atom("b")])
+    t2 = Callable.build("f", [Atom.fromname("a"), Atom.fromname("b")])
     t1.unify(t2, hp)
     assert hp.hook.attvar == a2
     assert hp.hook.next.attvar == a1

          
M prolog/interpreter/test/test_signature.py +4 -1
@@ 62,8 62,11 @@ def test_extra_attr_engine():
 
 def test_atom_signature():
     factory = SignatureFactory()
-    factory.register_extr_attr("foo", engine=True)
     sig1 = factory.getsignature("a", 0)
     assert sig1.atom_signature is sig1
     sig2 = factory.getsignature("a", 5)
     assert sig2.atom_signature is sig1
+
+def test_default_location():
+    sig1 = Signature("a", 0)
+    assert sig1.default_location.signature is sig1

          
M prolog/interpreter/test/test_specialized_terms.py +1 -1
@@ 15,7 15,7 @@ def test_callable_build_for_term1():
     assert t1.argument_count() == 1
 
 def test_callable_build_for_term1_from_factory():
-    t2 = Callable.build('foo', [Atom('bar')])
+    t2 = Callable.build('foo', [Atom.fromname('bar')])
     assert not isinstance(t2, Term)
     assert isinstance(t2, Callable)
     assert t2.name() == 'foo'

          
M prolog/interpreter/test/test_unification.py +17 -0
@@ 50,6 50,23 @@ def test_term():
     assert X.dereference(heap).name()== "HALLO"
     assert Y.dereference(heap).name()== "hallo"
 
+def test_term_stores_location():
+    X = BindingVar()
+    t1 = Callable.build("f", [Callable.build("hallo"), X])
+    assert t1._location.signature.name == "f"
+
+def test_term_copy_preserves_location():
+    from prolog.interpreter.signature import Signature, Location
+    from prolog.interpreter.memo import CopyMemo
+    sig1 = Signature("f", 1)
+    loc1 = Location(sig1, "foo")
+    v1 = BindingVar()
+    v1.binding = Number(10)
+    t1 = Callable.build_location([v1], loc1)
+    t2 = t1.copy(None, CopyMemo())
+    assert t1 is not t2
+    assert t2.location() is loc1
+
 def test_enumerate_vars():
     from prolog.interpreter.memo import EnumerationMemo
     X = BindingVar()