Pierre Carbonnelle avatar Pierre Carbonnelle committed 265c908

create_atoms can now give Datalog capabilities to modules, classes and functions, for use in logic expressions

Comments (0)

Files changed (4)

pyDatalog/examples/test.py

     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 unsafe3(1) == []
     assert unsafe3(2) == [()]
     assert unsafe3(X) == []
+
+    """ built-ins and import                    """
     
-    already = object()
+    # len
+    
+    pyDatalog.create_atoms('len')
+    assert (len('ok')) == 2
+    assert ((X==(1,2)) & (Y==len(X)) >= Y)==2
+    
+    # str.lower
+    
+    pyDatalog.create_atoms('str.lower')
+    assert (str.lower('OK')) == 'ok'
+    assert ((X=='OK') & (Y==str.lower(X)) >= Y) == 'ok'
+    
+    # str
+    
+    pyDatalog.create_atoms('str')
+    assert (str.lower('OK')) == 'ok'
+    assert ((X=='OK') & (Y==str.lower(X)) >= Y) == 'ok'
+    assert ((X==('1','2')) & (Y==str.join('.', X)) >= Y) == '1.2'
+    assert ((X=='ok') & (Y==str.format('Hello {0}', X)) >= Y) == 'Hello ok'
+    #not supported : print((X==('1','2')) & (Y=='.'.join(X)))
+    
+    # time
+    
+    import time
+    pyDatalog.create_atoms('time')
+    assert 0 < (time.time())
+    
+    # math
+    
+    import math
+    pyDatalog.create_atoms('math')
+    assert (math.cos(0)) == 1
+    assert ((X==0) & (Y==math.cos(X)) >= Y) == 1
+    
+    from math import cos
+    pyDatalog.create_atoms('cos')
+    assert (cos(0)) == 1
+    assert ((X==0) & (Y==cos(X)) >= Y) == 1
+    
+    # string
+    
+    import string
+    pyDatalog.create_atoms('string')
+    assert (string.digits) == '0123456789'
+
     _error = False
     try:
-        pyDatalog.create_atoms('already')
+        pyDatalog.create_atoms('xoiker.test')
     except Exception as e:
-        if e.value != "Name conflict.  Can't redefine already as atom":
+        if e.value != "Unknown variable : xoiker":
             print(e.value) 
         _error = True
     assert _error
+    
     print("Test completed successfully.")
 
     

pyDatalog/pyDatalog.py

     pyParser.clear()
     Logic()
 
+def _pyD_decorator(arg, copy=False):
+    # makes a copy of arg if copy=True, or if we can't modify arg
+    if hasattr(arg, '_pyD_atomized'): 
+        return arg
+    result = arg
+    if hasattr(arg, '__dict__') and (inspect.ismodule(arg) or inspect.isclass(arg) ):
+        new, ok = Dummy(), not copy
+        #TODO new class should inherit like arg does --> extended dictionary
+        for a in arg.__dict__:
+            new_f = _pyD_decorator(getattr(arg, a))
+            new.__dict__[a] = new_f #TODO can't use setattr because of Dummy ?
+            try:
+                setattr(arg, a, new_f) # fails for arg=str, for example
+            except:
+                ok = False
+        result = arg if ok else new
+    elif hasattr(arg, '__call__'): # it's a function
+        
+        def result(*arguments):
+            # if any argument is an Expression, return an Operation
+            # else immediately evaluate the function
+            # TODO support keyword arguments
+            # TODO keep default values
+            for a in arguments: 
+                if isinstance(a, pyParser.Expression):
+                    return pyParser.Operation(arg, '(', arguments) 
+            return arg(*arguments)
+        
+        try: # copy __doc__
+            result.__dict__.update(arg.__dict__)
+        except:
+            pass
+    try:
+        setattr(result, '_pyD_atomized', True)
+    except:
+        pass
+    return result
+
+ATOMS = ['_sum','sum_','_min','min_','_max','max_', '_len','len_','concat','concat_','rank','rank_',
+         'running_sum','running_sum_','range_','tuple_', 'format_']
+
 def create_atoms(*args):
-    """ create atoms for in-line queries """
+    """ create atoms for in-line clauses and queries """
     stack = inspect.stack()
     try:
         locals_ = stack[1][0].f_locals
-        args = [arg.split(',') for arg in args]
-        args = [arg.strip() for argl in args for arg in argl]
-        for arg in set(args + ['_sum','sum_','_min','min_','_max','max_',
-        '_len','len_','concat','concat_','rank','rank_','running_sum','running_sum_',
-        'range_','tuple_', 'format_']):
-            if arg in locals_: 
-                if not isinstance(locals_[arg], (pyParser.Symbol, pyParser.Variable)):
-                    raise util.DatalogError("Name conflict.  Can't redefine %s as atom" % arg, None, None)
+        args = [arg.strip() for arglist in args for arg in 
+                (arglist.split(',') if isinstance(arglist, util.string_types) else [arglist])]
+        for arg in set(args + ATOMS):
+            assert isinstance(arg, util.string_types)
+            words = arg.split('.')
+            if 2<len(words): #TODO deal with more
+                    raise util.DatalogError("Too many '.' in atom %s" % arg, None, None)
+            if words[0] in __builtins__:
+                root = __builtins__[words[0]]
+                # can't set attributes --> decorate the whole builtin !
+                locals_[words[0]] = _pyD_decorator(root, copy=True) # don't change builtins !
+            elif words[0] in locals_:
+                root = locals_[words[0]]
+                if len(words)==2: # e.g. str.split
+                    atom = getattr(root, words[1])
+                    setattr(root, words[1], _pyD_decorator(atom))
+                else: # e.g. math
+                    locals_[arg] = _pyD_decorator(root)
             else:
-                if arg[0] not in string.ascii_uppercase:
+                if len(words)==2: # e.g. kkecxivarenx.len
+                    raise util.DatalogError("Unknown variable : %s" % words[0], None, None)
+                if arg[0] not in string.ascii_uppercase: # e.g. queen
                     locals_[arg] = pyParser.Symbol(arg)
-                else:
-                    locals_[arg] = pyParser.Variable(arg)    
+                else: # e.g. X
+                    locals_[arg] = pyParser.Variable(arg)
     finally:
         del stack
 
 def variables(n):
-    """ create variables for in-line queries """
+    """ create variables for in-line clauses and queries """
     return [pyParser.Variable() for i in range(n)]
 
 Variable = pyParser.Variable

pyDatalog/pyEngine.py

                     v = getattr(v, attribute)
                 return Interned.of(v)
             elif self.operator == '(':
-                return Interned.of(lhs.id.__call__(*rhs._id))
+                return Interned.of(lhs.id.__call__(*(rhs.id)))
             assert False # dead code
         return Operation(lhs, self.operator, rhs)
             
     
     @classmethod
     def is_known(cls, pred_id):
+        #TODO look at _pyD_ methods in classes, to detect more
         prefix = pred_id.split('.')[0] # we assumes it has a '.'
         if prefix in Class_dict:
             for cls in Class_dict[prefix].__mro__:

pyDatalog/pyParser.py

     def __init__ (self, name, forced_type=None):
         self._pyD_negated = False # for aggregate with sort in descending order
         self._pyD_precalculations = Body() # no precalculations
+        self._pyD_atomized = True
         if isinstance(name, (list, tuple, util.xrange)):
             self._pyD_value = list(map(Expression._pyD_for, name))
             self._pyD_name = str([element._pyD_name for element in self._pyD_value])
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.