Commits

Pierre Carbonnelle committed 9894c72

Initial support for nested list.

Comments (0)

Files changed (4)

pyDatalog/examples/test.py

     assert(eq(X, 3)) == [] # because X==Z is undefined
     assert(eq(2, 3)) == []
     assert(eq(3, 3)) == [()]
+
+    # list unification
+    pyDatalog.create_atoms('X,Y, X1, X2, p, a, eq')
+    assert ( X==(1,2) ) == [((1,2),)]
+    assert ( X==(1,(2,)) ) == [((1,(2,)),)] # nested
+    assert ( X==(1,) + (2,) ) == [((1,2),)] # expression
     
+    assert ( (X==(1,2)) & (X==(1, X2)) & (Y==X2)) == [((1, 2), 2, 2)] # TODO drop last literal
+    assert ( (X==(1,(2,))) & (X==(1, (X2,))) & (Y==X2)) == [((1, (2,)), 2, 2)]
+    assert ( (X==(1,2)) & (X==Y)) == [((1, 2), (1, 2))]
+    assert ( (X==(1,2)) & (Y==(1,2)) & (X==Y)) == [((1, 2), (1, 2))]
+    assert ( (X==(1,2)) & (Y==(1,3)) & (X==Y)) == []
+    
+    eq(X,Y) <= (X==Y)
+    assert ( eq(X,(1,2))) == [((1,2),)]
+    assert ( eq(X,(1,(2,))) ) == [((1,(2,)),)] # nested
+    assert ( eq(X,(1,) + (2,)) ) == [((1,2),)] # expression
+    
+    """ TODO
+    assert ( eq(X,(1,2)) & (eq(X,(1, X2))) & (Y==X2)) == [((1, 2), 2, 2)] # TODO drop last literal
+    assert ( eq(X,(1,(2,))) & (X==(1, (X2,))) & (Y==X2)) == [((1, (2,)), 2, 2)]
+    assert ( eq(X,(1,2)) & eq(X,Y)) == [((1, 2), (1, 2))]
+    assert ( eq(X,(1,2)) & (Y==(1,2)) & (X==Y)) == [((1, 2), (1, 2))]
+    assert ( eq(X,(1,2)) & (Y==(1,3)) & (X==Y)) == []
+    """
+    
+    + (p[a] == (1,2))
+    assert ( p[X]==(1,2) ) == [('a',)]
+    assert ( (p[X]==(1,2)) & (p[X]==(1, X2)) & (Y==X2)) == [('a', 2, 2)] # TODO drop last literal
+    assert ( (p[X]==(1,2)) & (p[X]==Y)) == [('a', (1, 2))]
+    assert ( (p[X]==(1,2)) & (Y==(1,2)) & (p[X]==Y)) == [('a', (1, 2))]
+    assert ( (p[X]==(1,2)) & (Y==(1,3)) & (p[X]==Y)) == []
+    + (p[a] == (1,(2,)))
+    assert ( (p[X]==(1,(2,))) & (p[X]==(1, (X2,))) & (Y==X2)) == [('a', 2, 2)]
+
     print("Test completed successfully.")
 
     

pyDatalog/pyDatalog.py

 
 def assert_fact(predicate_name, *args):
     """ assert predicate_name(args) """
-    literal = Literal.make(predicate_name, args)
+    literal = Literal.make(predicate_name, [pyParser.Expression._for(arg) for arg in args])
     _assert_fact(literal)
 
 def retract_fact(predicate_name, *args):
     """ retracts predicate_name(args) """
-    literal = Literal.make(predicate_name, args)
+    literal = Literal.make(predicate_name, [pyParser.Expression._for(arg) for arg in args])
     _retract_fact(literal)
 
 def program():

pyDatalog/pyEngine.py

 from itertools import groupby
 import re
 import six
+from six.moves import xrange
 import weakref
 
 pyDatalog = None #circ: later set by pyDatalog to avoid circular import
     # registry = weakref.WeakValueDictionary()
     @classmethod
     def of(cls, atom):
-        if isinstance(atom, Interned):
-            return self
-        #elif isinstance(atom, (list, tuple, xrange)):
-        #    return VarTuple(tuple(atom)) # TODO Enterned all elements of _id ?
+        if isinstance(atom, (Interned, Fresh_var)):
+            return atom
+        elif isinstance(atom, (list, tuple, xrange)):
+            return VarTuple(atom)
         else:
             return Const(atom)
     notFound = object()
     def is_const(self):
         return False
     def get_tag(self, i, env):
-        env.setdefault(self, 'v%i' % i)
+        env.setdefault(self, 'v%i' % i) # TODO return the result of this statement directly ?
         return env[self] 
 
     def subst(self, env):
         return env.get(self, self)
     def shuffle(self, env):
         env.setdefault(self, Fresh_var())
-        return env[self]
+        return env[self] #TODO no need to return a value
     def chase(self, env):
         return env[self].chase(env) if self in env else self
     
     def unify_var(self, var, env):
         env[var] = self
         return env
+    def unify_tuple(self, tuple, env):
+        env[self] = tuple
+        return env
     
     def is_safe(self, clause):
         return any([is_in(self, bodi) for bodi in clause.body])
     def __str__(self): 
-        return "variable_%s" % self.id 
+        return "variable_%s" % self.id
+    def equals_primitive(self, term, subgoal):
+        return None
 
 class Var(Fresh_var, Interned):
     registry = weakref.WeakValueDictionary()
     def subst(self, env):
         return self
     def shuffle(self, env):
-        return None
+        return None  #TODO no need to return a value
     def chase(self, env):
         return self
     
         return None
     def unify_var(self, var, env):
         return var.unify_const(self, env)
+    def unify_tuple(self, tuple, env):
+        return None
     
     def is_safe(self, clause): 
         return True
     
     def __str__(self): 
-        return "'%s'" % self.id 
+        return "'%s'" % self.id
+    def equals_primitive(self, term, subgoal):
+        if self == term:          # Both terms are constant and equal.
+            literal = Literal(binary_equals_pred, (self, self))
+            return fact(subgoal, literal)
+
+class VarTuple(Interned):
+    registry = weakref.WeakValueDictionary()
+    def __new__(cls,  _id):
+        _id = tuple([Interned.of(element) for element in _id])
+        o = cls.registry.get(_id, Interned.notFound)
+        if o is Interned.notFound: 
+            o = object.__new__(cls) # o is the ref that keeps it alive
+            o._id = _id
+            o.key = add_size('o' + str(id(o)) )
+            cls.registry[_id] = o
+        return o
+    
+    @property # TODO use lazy property
+    def id(self):
+        return tuple([element.id for element in self._id])
+    def __len__(self):
+        return len(self._id)        
+    
+    def is_const(self):
+        return all(element.is_const() for element in self._id)
+    
+    def get_tag(self, i, env):
+        return self.key
+    def subst(self, env):
+        return VarTuple([element.subst(env) for element in self._id])
+    def shuffle(self, env):
+        for element in self._id:
+            element.shuffle(env)
+    def chase(self, env):
+        return VarTuple([element.chase(env) for element in self._id])
+    
+    def unify(self, term, env):
+        return term.unify_tuple(self, env)
+    def unify_const(self, const, env):
+        return None
+    def unify_var(self, var, env):
+        return var.unify_tuple(self, env)
+    def unify_tuple(self, tuple, env):
+        if len(self) != len(tuple):
+            return None
+        for e1, e2 in zip(self._id, tuple._id):
+            if e1 != e2:
+                env = e1.unify(e2, env)
+                if env == None: return env
+        return env
+
+    def is_safe(self, clause): 
+        return all(element.is_safe(clause) for element in self._id)
+
+    def __str__(self): 
+        return "'%s'" % str(str(e) for e in self.id)
+    def equals_primitive(self, term, subgoal):
+        if self == term:          # Both terms are constant and equal.
+            literal = Literal(binary_equals_pred, (self, self))
+            return fact(subgoal, literal)
 
 # Predicate symbols
 
     return x.equals_primitive(y, subgoal)
 binary_equals_pred.prim = equals_primitive
 
-Var.equals_primitive = lambda self, term, subgoal: None
-Fresh_var.equals_primitive = lambda self, term, subgoal: None
-
-def _(self, term, subgoal):
-    if self == term:          # Both terms are constant and equal.
-        literal = Literal(binary_equals_pred, (self, self))
-        return fact(subgoal, literal)
-Const.equals_primitive = _
-
 # Does a literal unify with an fact known to contain only constant
 # terms?
 
 
 class Operand(object):
     def __init__(self, type, value):
-        self.value = value if type == 'constant' else None
-        self.index = value if type != 'constant' else None
+        self.value = value if type != 'variable' else None
+        self.index = value if type == 'variable' else None
     def eval(self, environment):
-        return environment[self.index-1] if self.index != None else self.value
+        if self.index != None:
+            return environment[self.index-1]
+        elif isinstance(self.value, (list, tuple)):
+            return tuple(element.eval(environment) for element in self.value)
+        else:
+            return self.value
         
 class Expression(object):
     def __init__(self, operator, operand1, operand2):
         if literal.pred.operator == "==" and (not x.is_const() or x.id == Y):
             args.insert(0,Y)
             yield args
-        elif x.is_const() and compare(x.id, literal.pred.operator, Y) :
+        elif x.is_const() and compare(x.id, literal.pred.operator, Y):
             args.insert(0,x.id)
             yield args
         elif (literal.pred.operator == "in" and not x.is_const()):

pyDatalog/pyParser.py

     
     def __eq__(self, other):
         assert isinstance(self, (VarSymbol, Function)), "Left-hand side of equality must be a symbol or function, not an expression."
-        if self._pyD_type == 'variable' and not isinstance(other, VarSymbol):
+        other = Expression._for(other)
+        if self._pyD_type == 'variable' and (not isinstance(other, VarSymbol) or other._pyD_type=='constant'):
             return Literal.make_for_comparison(self, '==', other)
         else:
             return Literal.make("=", (self, other))
     """ represents the symbol of a variable, both inline and in pyDatalog program 
     """
     def __init__ (self, name, forced_type=None):
-        self._pyD_name = name
         self._pyD_negated = False # for aggregate with sort in descending order
-        name = tuple(name) if isinstance(name, list) else name
-        if forced_type=="constant" or isinstance(name, (int, list, tuple, xrange)) or not name or name[0] not in string.ascii_uppercase + '_':
+        if isinstance(name, (list, tuple, xrange)):
+            self._pyD_name = [Expression._for(element) for element in name]
+            self._pyD_type = 'tuple'
+            self._pyD_lua = pyEngine.VarTuple([e._pyD_lua for e in self._pyD_name])
+        elif forced_type=="constant" or isinstance(name, (int, list, tuple, xrange)) or not name or name[0] not in string.ascii_uppercase + '_':
+            self._pyD_name = name
             self._pyD_type = 'constant'
             self._pyD_lua = pyEngine.Const(name)
         else:
+            self._pyD_name = name
             self._pyD_type = 'variable'
             self._pyD_lua = pyEngine.Var(name)
 
     def lua_expr(self, variables):
         if self._pyD_type == 'variable':
             return pyEngine.Operand('variable', variables.index(self._pyD_name))
+        elif self._pyD_type == 'tuple':
+            return pyEngine.Operand('tuple', [element.lua_expr(variables) for element in self._pyD_name])
         else:
             return pyEngine.Operand('constant', self._pyD_name)
     
     def _variables(self):
         if self._pyD_type == 'variable':
             return OrderedDict({self._pyD_name : self})
+        elif self._pyD_type == 'tuple':
+            variables = OrderedDict()
+            for element in self._pyD_name:
+                variables.update(element._variables())
+            return variables
         else:
             return OrderedDict()