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:
Tim Hinrichs 2013-08-26 14:16:12 -07:00
parent 863fdc4d79
commit 3259680709
3 changed files with 61 additions and 1 deletions

View File

@ -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.

View File

@ -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],

View File

@ -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. """