Added recursion to the language.
To support recursion, we only needed to avoid processing events that were noops, which we needed to do anyway. Issue: # Change-Id: I77c60d78f4f73eed33bbfe30d4b94361bbd3a73f
This commit is contained in:
parent
863fdc4d79
commit
3259680709
|
@ -115,6 +115,12 @@ class Atom (object):
|
|||
self.arguments = arguments
|
||||
self.location = location
|
||||
|
||||
@classmethod
|
||||
def create_from_table_tuple(cls, table, tuple):
|
||||
""" LIST is a python list representing an atom, e.g.
|
||||
['p', 17, "string", 3.14]. Returns the corresponding Atom. """
|
||||
return cls(table, [Term.create_from_python(x) for x in tuple])
|
||||
|
||||
@classmethod
|
||||
def create_from_list(cls, list):
|
||||
""" LIST is a python list representing an atom, e.g.
|
||||
|
|
|
@ -128,6 +128,9 @@ class Event(object):
|
|||
sign = '-'
|
||||
return "{}{}({})".format(self.table, sign, str(self.tuple))
|
||||
|
||||
def atom(self):
|
||||
return compile.Atom.create_from_table_tuple(self.table, self.tuple.tuple)
|
||||
|
||||
##############################################################################
|
||||
## Database
|
||||
##############################################################################
|
||||
|
@ -186,6 +189,18 @@ class Database(object):
|
|||
def __len__(self):
|
||||
return len(self.contents)
|
||||
|
||||
def __ge__(self, other):
|
||||
return other <= self
|
||||
|
||||
def __le__(self, other):
|
||||
for proof in self.contents:
|
||||
if proof not in other.contents:
|
||||
return False
|
||||
return True
|
||||
|
||||
def __eq__(self, other):
|
||||
return self <= other and other <= self
|
||||
|
||||
class DBTuple(object):
|
||||
def __init__(self, iterable, proofs=None):
|
||||
self.tuple = tuple(iterable)
|
||||
|
@ -282,6 +297,23 @@ class Database(object):
|
|||
def log(self, table, msg, depth=0):
|
||||
self.tracer.log(table, "DB: " + msg, depth)
|
||||
|
||||
def is_noop(self, event):
|
||||
""" Returns T if EVENT is a noop on the database. """
|
||||
# insert/delete same code but with flipped return values
|
||||
# Code below is written as insert, except noop initialization.
|
||||
if event.is_insert():
|
||||
noop = True
|
||||
else:
|
||||
noop = False
|
||||
if event.table not in self.data:
|
||||
return not noop
|
||||
event_data = self.data[event.table]
|
||||
for dbtuple in event_data:
|
||||
# event.tuple is a dbtuple (and == only checks their .tuple fields)
|
||||
if dbtuple == event.tuple and event.tuple.proofs <= dbtuple.proofs:
|
||||
return noop
|
||||
return not noop
|
||||
|
||||
def select(self, query):
|
||||
#logging.debug("DB: select({})".format(str(query)))
|
||||
if isinstance(query, compile.Atom):
|
||||
|
@ -346,7 +378,7 @@ class Database(object):
|
|||
either [] meaning the lookup failed or [{}] meaning the lookup
|
||||
succeeded; otherwise, returns one binding list for each tuple in
|
||||
the database matching LITERAL under BINDING. """
|
||||
# slow--should stop at first match, not find all of them
|
||||
# slow for negation--should stop at first match, not find all of them
|
||||
matches = self.matches_atom(literal, binding)
|
||||
if literal.is_negated():
|
||||
if len(matches) > 0:
|
||||
|
@ -622,6 +654,10 @@ class Runtime (object):
|
|||
""" Toplevel data evaluation routine. """
|
||||
while len(self.queue) > 0:
|
||||
event = self.queue.dequeue()
|
||||
if self.database.is_noop(event):
|
||||
self.log(event.table, "is noop")
|
||||
continue
|
||||
self.log(event.table, "is not noop")
|
||||
if event.is_insert():
|
||||
self.propagate(event)
|
||||
self.database.insert(event.table, event.tuple)
|
||||
|
@ -683,6 +719,8 @@ class Runtime (object):
|
|||
for new_tuple in new_tuples:
|
||||
# self.log(event.table,
|
||||
# "new_tuple {}: {}".format(str(new_tuple), str(new_tuples[new_tuple])))
|
||||
# Only enqueue if new data.
|
||||
# Putting the check here is necessary to support recursion.
|
||||
self.queue.enqueue(Event(table=atom.table,
|
||||
tuple=new_tuple,
|
||||
proofs=new_tuples[new_tuple],
|
||||
|
|
|
@ -440,6 +440,22 @@ class TestRuntime(unittest.TestCase):
|
|||
run.delete("p(x) :- q(x), r(x)")
|
||||
self.check(run, 'q(1) r(1) q(2) r(2)', "Delete rule")
|
||||
|
||||
def test_recursion(self):
|
||||
run = self.prep_runtime('q(x,y) :- p(x,y)'
|
||||
'q(x,y) :- p(x,z), q(z,y)')
|
||||
run.insert('p(1,2)')
|
||||
run.insert('p(2,3)')
|
||||
run.insert('p(3,4)')
|
||||
run.insert('p(4,5)')
|
||||
self.check(run, 'p(1,2) p(2,3) p(3,4) p(4,5)'
|
||||
'q(1,2) q(2,3) q(1,3) q(3,4) q(2,4) q(1,4) q(4,5) q(3,5) '
|
||||
'q(1,5) q(2,5)',
|
||||
'Insert into recursive rules')
|
||||
run.delete('p(1,2)')
|
||||
self.check(run, 'p(2,3) p(3,4) p(4,5)'
|
||||
'q(2,3) q(3,4) q(2,4) q(4,5) q(3,5) q(2,5)',
|
||||
'Delete from recursive rules')
|
||||
|
||||
# TODO(tim): add tests for explanations
|
||||
def test_explanations(self):
|
||||
""" Test the explanation event handler. """
|
||||
|
|
Loading…
Reference in New Issue