From d5e14c3ebc74f2249e15112eb9be00777e0d2127 Mon Sep 17 00:00:00 2001 From: Tim Vieira Date: Mon, 24 Jun 2013 21:53:03 -0400 Subject: [PATCH] New output format - print nullary terms together at the top. - don't print empty charts - heading is "Solution" instead of "Charts" REPL cmd 'chart' renamed to 'sol' Added lots of help documentation for REPL --- examples/expected/dijkstra.py.out | 24 +- examples/expected/equalities.py.out | 42 +-- examples/expected/fib-limit.py.out | 12 +- examples/expected/matrixops.py.out | 27 +- examples/expected/papa2.py.out | 45 +-- examples/expected/simple.py.out | 18 +- src/Dyna/Backend/Python/Backend.hs | 8 +- src/Dyna/Backend/Python/chart.py | 10 +- src/Dyna/Backend/Python/defn.py | 18 +- src/Dyna/Backend/Python/interpreter.py | 34 ++- src/Dyna/Backend/Python/load/__init__.py | 4 +- src/Dyna/Backend/Python/post/__init__.py | 6 +- src/Dyna/Backend/Python/post/draw_circuit.py | 9 +- .../post/{dump_chart.py => dump_solution.py} | 13 +- src/Dyna/Backend/Python/post/graph.py | 49 ++-- src/Dyna/Backend/Python/post/save.py | 7 +- src/Dyna/Backend/Python/repl.py | 274 ++++++++++++++++-- test/repl/aggregator-conflict.expect | 1 - test/repl/late-aggregator-assignment.expect | 2 - test/repl/retract-rule | 4 +- test/repl/retract-rule.expect | 28 +- 21 files changed, 390 insertions(+), 245 deletions(-) rename src/Dyna/Backend/Python/post/{dump_chart.py => dump_solution.py} (75%) diff --git a/examples/expected/dijkstra.py.out b/examples/expected/dijkstra.py.out index 4fcbb54..0331585 100644 --- a/examples/expected/dijkstra.py.out +++ b/examples/expected/dijkstra.py.out @@ -1,8 +1,12 @@ -Charts -============ +Solution +======== +end := "MyHouse" +goal := 2410 +start := "FriendHouse" + edge/2 -================= +====== edge("BOS","JFK") := 187 edge("BOS","MIA") := 1258 edge("DFW","LAX") := 1235 @@ -17,16 +21,8 @@ edge("MIA","LAX") := 2342 edge("ORD","DFW") := 802 edge("ORD","MyHouse") := 20 -end/0 -================= -end := "MyHouse" - -goal/0 -================= -goal := 2410 - path/1 -================= +====== path("BOS") := 10 path("DFW") := 1588 path("FriendHouse") := 0 @@ -37,7 +33,3 @@ path("MyHouse") := 2410 path("ORD") := 2390 path("SFO") := 2779 -start/0 -================= -start := "FriendHouse" - diff --git a/examples/expected/equalities.py.out b/examples/expected/equalities.py.out index 637d9c7..f4e53c2 100644 --- a/examples/expected/equalities.py.out +++ b/examples/expected/equalities.py.out @@ -1,35 +1,11 @@ -Charts -============ -a/0 -================= -a := 0 - -b/0 -================= -b := 0 - -by_evl_cross/0 -================= -by_evl_cross := true - -by_evl_refl/0 -================= -by_evl_refl := true - -by_is_0a/0 -================= -by_is_0a := true - -by_is_ab/0 -================= - - -by_syn_cross/0 -================= -by_syn_cross := false - -by_syn_refl/0 -================= -by_syn_refl := true +Solution +======== +a := 0 +b := 0 +by_evl_cross := true +by_evl_refl := true +by_is_0a := true +by_syn_cross := false +by_syn_refl := true diff --git a/examples/expected/fib-limit.py.out b/examples/expected/fib-limit.py.out index 2d35826..db580ec 100644 --- a/examples/expected/fib-limit.py.out +++ b/examples/expected/fib-limit.py.out @@ -1,8 +1,10 @@ -Charts -============ +Solution +======== +lim := 10 + f/1 -================= +=== f(1) := 1 f(2) := 1 f(3) := 2 @@ -13,7 +15,3 @@ f(7) := 13 f(8) := 21 f(9) := 34 -lim/0 -================= -lim := 10 - diff --git a/examples/expected/matrixops.py.out b/examples/expected/matrixops.py.out index 76ff743..cc48229 100644 --- a/examples/expected/matrixops.py.out +++ b/examples/expected/matrixops.py.out @@ -1,26 +1,15 @@ -Charts -============ -a/0 -================= - - -b/0 -================= - - -c/0 -================= - +Solution +======== cols/1 -================= +====== cols(a) := 2 cols(b) := 3 cols(c) := 3 m/3 -================= +=== m(a,1,1) := 1 m(a,1,2) := 0 m(a,2,1) := 0 @@ -39,23 +28,23 @@ m(c,2,2) := 2 m(c,2,3) := 0 product/2 -================= +========= product(a,b) := c rows/1 -================= +====== rows(a) := 2 rows(b) := 2 rows(c) := 2 shape/3 -================= +======= shape(a,2,2) := true shape(b,2,3) := true shape(c,2,3) := true times/4 -================= +======= times(a,b,1,1) := 3 times(a,b,1,2) := 0 times(a,b,1,3) := 1 diff --git a/examples/expected/papa2.py.out b/examples/expected/papa2.py.out index 3ec66f3..aac98f0 100644 --- a/examples/expected/papa2.py.out +++ b/examples/expected/papa2.py.out @@ -1,33 +1,18 @@ -Charts -============ -best/0 -================= -best := pair(1,t("S",t("S",t("NP","Papa"),t("VP",t("VP",t("V","ate"),t("NP",t("Det","the"),t("N","caviar"))),t("PP",t("P","with"),t("NP",t("Det","a"),t("N","spoon"))))),".")) - -bestParse/0 -================= -bestParse := t("S",t("S",t("NP","Papa"),t("VP",t("VP",t("V","ate"),t("NP",t("Det","the"),t("N","caviar"))),t("PP",t("P","with"),t("NP",t("Det","a"),t("N","spoon"))))),".") - -bestScore/0 -================= -bestScore := 1 +Solution +======== +best := pair(1,t("S",t("S",t("NP","Papa"),t("VP",t("VP",t("V","ate"),t("NP",t("Det","the"),t("N","caviar"))),t("PP",t("P","with"),t("NP",t("Det","a"),t("N","spoon"))))),".")) +bestParse := t("S",t("S",t("NP","Papa"),t("VP",t("VP",t("V","ate"),t("NP",t("Det","the"),t("N","caviar"))),t("PP",t("P","with"),t("NP",t("Det","a"),t("N","spoon"))))),".") +bestScore := 1 +length := 8 goal/1 -================= +====== goal(t("S",t("S",t("NP","Papa"),t("VP",t("V","ate"),t("NP",t("NP",t("Det","the"),t("N","caviar")),t("PP",t("P","with"),t("NP",t("Det","a"),t("N","spoon")))))),".")) := 1 goal(t("S",t("S",t("NP","Papa"),t("VP",t("VP",t("V","ate"),t("NP",t("Det","the"),t("N","caviar"))),t("PP",t("P","with"),t("NP",t("Det","a"),t("N","spoon"))))),".")) := 1 -length/0 -================= -length := 8 - -pair/2 -================= - - phrase/4 -================= +======== phrase(".",7,8,".") := 1 phrase("Det",2,3,t("Det","the")) := 1 phrase("Det",5,6,t("Det","a")) := 1 @@ -57,7 +42,7 @@ phrase("the",2,3,"the") := 1 phrase("with",4,5,"with") := 1 rewrite/2 -================= +========= rewrite("Det","a") := 1 rewrite("Det","the") := 1 rewrite("N","caviar") := 1 @@ -67,7 +52,7 @@ rewrite("P","with") := 1 rewrite("V","ate") := 1 rewrite/3 -================= +========= rewrite("NP","Det","N") := 1 rewrite("NP","NP","PP") := 1 rewrite("PP","P","NP") := 1 @@ -76,16 +61,8 @@ rewrite("S","S",".") := 1 rewrite("VP","V","NP") := 1 rewrite("VP","VP","PP") := 1 -t/2 -================= - - -t/3 -================= - - word/2 -================= +====== word(".",7) := true word("Papa",0) := true word("a",5) := true diff --git a/examples/expected/simple.py.out b/examples/expected/simple.py.out index bd2f72c..e43e651 100644 --- a/examples/expected/simple.py.out +++ b/examples/expected/simple.py.out @@ -1,15 +1,7 @@ -Charts -============ -a/0 -================= -a := true - -b/0 -================= -b := true - -c/0 -================= -c := true +Solution +======== +a := true +b := true +c := true diff --git a/src/Dyna/Backend/Python/Backend.hs b/src/Dyna/Backend/Python/Backend.hs index 033315b..d6ea0f2 100644 --- a/src/Dyna/Backend/Python/Backend.hs +++ b/src/Dyna/Backend/Python/Backend.hs @@ -279,14 +279,14 @@ pdope_ _ (OPEmit h r i vs) = do ds <- get -- A python map of variable name to value - let varmap = braces $ align $ fillPunct (comma <> space) $ - ("'nodes'" <> colon <> (encloseSep lbracket rbracket comma $ map (("d"<>).pretty) [0..ds-1])) - : (map (\v -> let v' = pretty v in dquotes v' <> colon <+> v') vs) + let varmap = brackets $ align $ fillPunct (comma <> space) $ + parens ("'nodes'" <> comma <> (encloseSep lbracket rbracket comma $ map (("d"<>).pretty) [0..ds-1])) + : (map (\v -> let v' = pretty v in parens (dquotes v' <> comma <+> v')) vs) return $ "emit" <> tupled [ pretty h , pretty r , pretty i - , varmap + , "tuple" <> (parens $ varmap) ] -- | Render a dopamine sequence's checks and loops above a (indended) core. diff --git a/src/Dyna/Backend/Python/chart.py b/src/Dyna/Backend/Python/chart.py index a6209d7..7bd2f07 100644 --- a/src/Dyna/Backend/Python/chart.py +++ b/src/Dyna/Backend/Python/chart.py @@ -16,9 +16,17 @@ class Chart(object): return aggregator(self.agg_name) def __repr__(self): + rows = [term for term in self.intern.values() if term.value is not None] + + if not rows: + return '' + + if self.arity == 0: + return '%s := %s' % (term, _repr(term.value)) + x = '\n'.join('%-30s := %s' % (term, _repr(term.value)) for term in sorted(rows)) - return '%s\n=================\n%s' % (self.name, x) + return '%s\n%s\n%s\n' % (self.name, '='*len(self.name), x) def __getitem__(self, s): assert len(s) == self.arity + 1, \ diff --git a/src/Dyna/Backend/Python/defn.py b/src/Dyna/Backend/Python/defn.py index e48d28f..cbcd7c5 100644 --- a/src/Dyna/Backend/Python/defn.py +++ b/src/Dyna/Backend/Python/defn.py @@ -17,7 +17,7 @@ class Aggregator(object): class BAggregator(Counter, Aggregator): -# def __init__(self): +# def __init__(self): # super(BAggregator, self).__init__() def inc(self, val, ruleix, variables): self[val] += 1 @@ -25,7 +25,7 @@ class BAggregator(Counter, Aggregator): self[val] -= 1 def fromkeys(self, *_): assert False, "This method should never be called." - + class PlusEquals(object): __slots__ = 'pos', 'neg' @@ -55,10 +55,20 @@ def user_vars(variables): "Post process the variables past to emit (which passes them to aggregator)." # remove the 'u' prefix on user variables 'uX' # Note: We also ignore user variables with an underscore prefix - return tuple((name[1:], val) for name, val in variables.items() + return tuple((name[1:], val) for name, val in variables if name.startswith('u') and not name.startswith('u_')) +from term import _repr +def drepr(vs): + return '{%s}' % ', '.join('%s=%s' % (k, _repr(v)) for k,v in vs.iteritems()) + +from collections import namedtuple +class Result(namedtuple('Result', 'value variables')): + def __repr__(self): + return 'Result(value=%s, variables=%s)' % (_repr(self.value), drepr(dict(self.variables))) + + class DictEquals(BAggregator): def inc(self, val, ruleix, variables): @@ -72,7 +82,7 @@ class DictEquals(BAggregator): self[val, vs] -= 1 def fold(self): - return list((v, dict(b)) for (v, b), cnt in self.iteritems()) + return tuple(Result(v, b) for (v, b), cnt in self.iteritems() if cnt > 0) class majority_equals(BAggregator): diff --git a/src/Dyna/Backend/Python/interpreter.py b/src/Dyna/Backend/Python/interpreter.py index 6d5d665..2d7d5a3 100644 --- a/src/Dyna/Backend/Python/interpreter.py +++ b/src/Dyna/Backend/Python/interpreter.py @@ -43,6 +43,8 @@ TODO Maybe subscription to diff is a different beast, only available as a procedural world. + - TODO: True and 1 are equivalent. This sometimes leads to strange behavior. + - New syntax for doing repl stuff (@nwf): load, subscribe, post-process - sheebang @@ -55,7 +57,7 @@ TODO - crash handler - - where to errors go? + - where do errors go? - @nwf suggests temporary measure for LSA students: time-stamped file sitting in the users home directory, @@ -95,9 +97,6 @@ FASTER - Collect all query modes used by the planner. Consider indexing value column if plans need it. - - dynac should provide routines for building terms. We can hack something - together with anf output, but this will be prety kludgy and inefficient. - - better default prioritization (currently FIFO) - BAggregators aren't very efficient. @@ -190,6 +189,7 @@ import os, sys, imp, argparse from collections import defaultdict from hashlib import sha1 from time import time +from path import path import load, post @@ -314,13 +314,23 @@ class Interpreter(object): def dump_charts(self, out=sys.stdout): print >> out - print >> out, 'Charts' - print >> out, '============' + print >> out, 'Solution' + print >> out, '========' fns = self.chart.keys() fns.sort() - for x in fns: - print >> out, self.chart[x] + nullary = [x for x in fns if x.endswith('/0')] + others = [x for x in fns if not x.endswith('/0')] + # show nullary charts first + for x in nullary: + y = str(self.chart[x]) # skip empty chart + if y: + print >> out, y + if nullary: print >> out + for x in others: + y = str(self.chart[x]) # skip empty chart + if y: + print >> out, y self.dump_errors(out) def dump_errors(self, out=sys.stdout): @@ -329,7 +339,7 @@ class Interpreter(object): return print >> out print >> out, 'Errors' - print >> out, '============' + print >> out, '======' for item, (val, es) in self.error.items(): print >> out, 'because %r is %s:' % (item, _repr(val)) for e, h in es: @@ -491,7 +501,6 @@ class Interpreter(object): rule.updaters.append(handler) handler.rule = rule - def gbc(self, fn, *args): # TODO: need to distinguish `unknown` from `null` @@ -533,12 +542,11 @@ class Interpreter(object): item.aggregator.dec(val, ruleix, variables) else: item.aggregator.inc(val, ruleix, variables) -# self.agenda[item] = 0 # everything is high priority self.agenda[item] = time() # FIFO def repl(self): import repl - repl.REPL(self, dotdynadir / 'dyna.hist').cmdloop() + repl.REPL(self).cmdloop() def do(self, filename, initialize=True): """ @@ -646,7 +654,7 @@ def peel(fn, item): assert item.fn == fn return item.args -from path import path + def main(): parser = argparse.ArgumentParser(description="The dyna interpreter!") parser.add_argument('source', nargs='?', type=path, diff --git a/src/Dyna/Backend/Python/load/__init__.py b/src/Dyna/Backend/Python/load/__init__.py index eca4929..dea5dff 100644 --- a/src/Dyna/Backend/Python/load/__init__.py +++ b/src/Dyna/Backend/Python/load/__init__.py @@ -3,10 +3,10 @@ from tsv import tsv from matrix import matrix from pickled import pickled -import re +import re as _re def run(interp, line): - [(name, module, args)] = re.findall('^([a-z][a-zA-Z_0-9]*) = ([a-z][a-zA-Z_0-9]*)\((.*)\)', line) + [(name, module, args)] = _re.findall('^([a-z][a-zA-Z_0-9]*) = ([a-z][a-zA-Z_0-9]*)\((.*)\)', line) m = getattr(__import__('load'), module)(interp, name) exec 'm.main(%s)' % args interp.go() diff --git a/src/Dyna/Backend/Python/post/__init__.py b/src/Dyna/Backend/Python/post/__init__.py index 30aa68e..3168473 100644 --- a/src/Dyna/Backend/Python/post/__init__.py +++ b/src/Dyna/Backend/Python/post/__init__.py @@ -1,11 +1,11 @@ -import re +import re as _re from save import save from graph import graph from draw_circuit import draw_circuit -from dump_chart import dump_chart +from dump_solution import dump_solution def run(interp, line): - [(name, args)] = re.findall('([a-z][a-zA-Z_0-9]*)\((.*)\)$', line) + [(name, args)] = _re.findall('([a-z][a-zA-Z_0-9]*)\((.*)\)$', line) m = globals()[name](interp) eval('m.main(%s)' % args) diff --git a/src/Dyna/Backend/Python/post/draw_circuit.py b/src/Dyna/Backend/Python/post/draw_circuit.py index 3d4ed5d..a6884c4 100644 --- a/src/Dyna/Backend/Python/post/draw_circuit.py +++ b/src/Dyna/Backend/Python/post/draw_circuit.py @@ -1,7 +1,3 @@ -""" -Crude visualization of circuit pertaining to state of the interpreter. -""" - import webbrowser from debug import Hypergraph from cStringIO import StringIO @@ -21,7 +17,7 @@ def infer_edges(interp): # Use rule initializers to find all active hyperedges in the current Chart. def _emit(item, _, ruleix, variables): - b = variables['nodes'] + b = dict(variables)['nodes'] b.sort() b = tuple(b) edges.add((item, ruleix, b)) @@ -33,6 +29,9 @@ def infer_edges(interp): class draw_circuit(object): + """ + Crude visualization of circuit pertaining to state of the interpreter. + """ def __init__(self, interp): self.interp = interp diff --git a/src/Dyna/Backend/Python/post/dump_chart.py b/src/Dyna/Backend/Python/post/dump_solution.py similarity index 75% rename from src/Dyna/Backend/Python/post/dump_chart.py rename to src/Dyna/Backend/Python/post/dump_solution.py index 438d3d9..9d911c8 100644 --- a/src/Dyna/Backend/Python/post/dump_chart.py +++ b/src/Dyna/Backend/Python/post/dump_solution.py @@ -1,14 +1,9 @@ - - -""" -Save interpreter state using python's pickle protocol. -""" - import sys - -class dump_chart(object): - +class dump_solution(object): + """ + Print solution + """ def __init__(self, interp): self.interp = interp diff --git a/src/Dyna/Backend/Python/post/graph.py b/src/Dyna/Backend/Python/post/graph.py index f06b460..79b59ad 100644 --- a/src/Dyna/Backend/Python/post/graph.py +++ b/src/Dyna/Backend/Python/post/graph.py @@ -1,38 +1,37 @@ -""" -Postprocessor for animated visualization of basic elements such as lines and -text. - -We look for the following patterns in the dynabase - - visual element - v - frame(T, &text(String, tuple(X, Y))). - ^ - time index - -Frames should have value true. The example above places a text element reading -`String` at position `(X,Y)` in a frame at time `T`. This element can be -specified by dyna rule. -""" - import pylab as pl from matplotlib.animation import FuncAnimation from collections import defaultdict class graph(object): + """ + Postprocessor for animated visualization of basic elements such as lines and + text. + + We look for the following patterns in the dynabase + + visual element + v + frame(T, &text(String, tuple(X, Y))). + ^ + time index + + Frames should have value true. The example above places a text element reading + `String` at position `(X,Y)` in a frame at time `T`. This element can be + specified by dyna rule. + """ def __init__(self, interp): self.interp = interp - def main(self, outfile): - + def main(self, outfile, fps=30): + frame = defaultdict(list) for _, [t, item], val in self.interp.chart['frame/2'][:,:,:]: if val: frame[t].append(item) - + nframes = max(frame) - + def draw_frame(t): ax.cla() ax.set_title(t) @@ -49,12 +48,8 @@ class graph(object): ax.text(x,y,s) else: print 'dont know how to render', item - + fig = pl.figure() ax = pl.axes() - - print 'creating animation..' anim = FuncAnimation(fig, draw_frame, frames=nframes) - print 'saving...' - anim.save(outfile, fps=30, extra_args=['-vcodec', 'libx264']) - print 'wrote examples/force.dyna.mp4' + anim.save(outfile, fps=fps, extra_args=['-vcodec', 'libx264']) diff --git a/src/Dyna/Backend/Python/post/save.py b/src/Dyna/Backend/Python/post/save.py index 9e3ba8c..78e5701 100644 --- a/src/Dyna/Backend/Python/post/save.py +++ b/src/Dyna/Backend/Python/post/save.py @@ -1,11 +1,10 @@ -""" -Save interpreter state using python's pickle protocol. -""" import cPickle - class save(object): + """ + Save interpreter state using python's pickle protocol. + """ def __init__(self, interp): self.interp = interp diff --git a/src/Dyna/Backend/Python/repl.py b/src/Dyna/Backend/Python/repl.py index 72fb9c8..d222eed 100644 --- a/src/Dyna/Backend/Python/repl.py +++ b/src/Dyna/Backend/Python/repl.py @@ -1,4 +1,19 @@ -import os, cmd, readline +""" +TODO: unsubscribe + +TODO: should probably remove the new rule after we get the results. + +TODO: probably should show "changed" + +TODO: queries are all maintained... should probably toss out the query rule. If +users want queries to be kept up-to-date user should subscribe instead. + +TODO: help should print call signature of loads and post-processors in addition +to help. + +""" + +import re, os, cmd, readline import debug, interpreter from utils import ip @@ -11,10 +26,37 @@ import load, post from interpreter import Interpreter, foo, none +from term import _repr +from defn import drepr + + +def lexer(term): + return re.findall('"[^"]*"' # string + '|[a-z][a-zA-Z_0-9]*' # functor + '|[A-Z][a-zA-Z0-9_]*' # variable + '|[(), ]' # parens and comma + '|[^(), ]+', term) # everything else + + +def subst(term, v): + """ + >>> subst('f("asdf",*g(1,X, Y), X+1)', {'X': 1234}) + 'f("asdf",*g(1,1234, Y), 1234+1)' + + >>> subst('f("asdf",*g(1,X, Y), XX+1)', {'X': 1234}) + 'f("asdf",*g(1,1234, Y), XX+1)' + + >>> subst('f("asdf",*g(1,uX, Y), X_+1)', {'X': 1234}) + 'f("asdf",*g(1,uX, Y), X_+1)' + + """ + assert isinstance(v, dict) + return ''.join((_repr(v[x]) if x in v else x) for x in lexer(term)) + class REPL(cmd.Cmd, object): - def __init__(self, interp, hist): + def __init__(self, interp, hist=dotdynadir / 'dyna.hist'): self.interp = interp cmd.Cmd.__init__(self) self.hist = hist @@ -25,17 +67,54 @@ class REPL(cmd.Cmd, object): readline.read_history_file(hist) self.lineno = 0 + # create help routines based on doc string. + for x, v in REPL.__dict__.iteritems(): + if x.startswith('do_') and hasattr(v, '__doc__'): + def show_doc(d=v.__doc__): + print d + setattr(self, 'help_' + x[3:], show_doc) + @property def prompt(self): - return ':- ' #% self.lineno + return ':- ' def do_rules(self, _): + """ + List rules in the program. + """ self.interp.dump_rules() def do_retract_rule(self, idx): + """ + Retract rule from program by rule index. + + :- a += 1. + :- b += 1. + :- c += a*b. + + :- rules + 0: a += 1. + 1: b += 1. + 2: c += a * b. + + :- retract_rule 0 + + This removes rule 0 from the program. Now, let's inspect the changes to + the solution. + + :- sol + + Solution + ======== + b := 1. + + """ self.interp.retract_rule(int(idx)) def do_exit(self, _): + """ + Exit REPL by typing exit or control-d. See also EOF. + """ readline.write_history_file(self.hist) return -1 @@ -56,7 +135,10 @@ class REPL(cmd.Cmd, object): self.lineno += 1 return stop - def do_chart(self, _): + def do_sol(self, _): + """ + Show solution. + """ self.interp.dump_charts() def emptyline(self): @@ -64,34 +146,80 @@ class REPL(cmd.Cmd, object): pass def do_ip(self, _): + """ + Development tool. Jump into an interactive python shell. + """ ip() def do_debug(self, line): + """ + Development tool. Used for view Dyna's intermediate representations. + """ with file(dotdynadir / 'repl-debug-line.dyna', 'wb') as f: f.write(line) debug.main(f.name) - def do_query(self, line): - - if line.endswith('.'): + def _query(self, q): + if q.endswith('.'): print "Queries don't end with a dot." return - - query = 'out(%s) dict= %s.' % (self.lineno, line) - - self.default(query) - + query = '$out(%s) dict= %s.' % (self.lineno, q) + self.default(query, show_changed=False) try: - [(_, _, results)] = self.interp.chart['out/1'][self.lineno,:] + [(_, _, results)] = self.interp.chart['$out/1'][self.lineno,:] except ValueError: + return [] + return results + + def do_vquery(self, q): + """ + See query. + """ + results = self._query(q) + if results is None: + return + if len(results) == 0: print 'No results.' return - for val, bindings in results: - print ' ', val, 'when', bindings + print ' ', _repr(val), 'when', drepr(dict(bindings)) + print + + def do_query(self, q): + """ + Query solution. + + Consider the following example; + + :- f(1) := 1. + :- f(2) := 4. + + There a few versions of query: + + - `vquery` shows variable bindings + + :- vquery f(X) + 1 when {X=1} + 4 when {X=1} + + - `query` shows variable bindings applied to query + + :- query f(X) + 1 is f(1) + 4 is f(2) + + """ + results = self._query(q) + if results is None: + return + if len(results) == 0: + print 'No results.' + return + for term, result in sorted((subst(q, dict(result.variables)), result) for result in results): + print ' ', _repr(result.value), 'is', term print - def default(self, line): + def default(self, line, show_changed=True): """ Called on an input line when the command prefix is not recognized. In that case we execute the line as Python code. @@ -110,7 +238,8 @@ class REPL(cmd.Cmd, object): print '> new rule(s) were not added to program.' print else: - self._changed(changed) + if show_changed: + self._changed(changed) def _changed(self, changed): if not changed: @@ -118,6 +247,20 @@ class REPL(cmd.Cmd, object): print '=============' for x, v in sorted(changed.items()): print '%s := %s' % (x, _repr(v)) + + def _changed_subscriptions(self, changed): + + # TODO: this doesn't show changes - it redumps everything. + + if not changed: + return + for x, _ in sorted(changed.items()): + if x.fn == '$subscribed/2': + [i, q] = x.args + if x.value: + print '%s: %s' % (i, q) + for result in x.value: + print ' ', _repr(result.value), 'when', drepr(dict(result.variables)) print self.interp.dump_errors() @@ -133,33 +276,114 @@ class REPL(cmd.Cmd, object): readline.write_history_file(self.hist) def do_subscribe(self, line): + """ + Establish a subscription to the results of a query. + + For example, + + :- subscribe f(X,X) + :- f(1,1) := 1. f(1,2) := 2. f(2,2) := 3. + Changes + ======= + f(X,X): + 1 when {X=1} + + To view all subscriptions: + + :- subscriptions + f(X): + 1 when {X=1} + 2 when {X=2} + + """ if line.endswith('.'): print "Queries don't end with a dot." return # subscriptions are maintained via forward chaining. - query = 'subscribed(%s, %s) dict= %s.' % (self.lineno, _repr(line), line) + query = '$subscribed(%s, %s) dict= %s.' % (self.lineno, _repr(line), line) self.default(query) def do_subscriptions(self, _): - for (_, [_, q], answers) in self.interp.chart['subscribed/2'][:,:,:]: - print - print q - for [value, vs] in answers: - print ' %s when {%s}' \ - % (value, ', '.join('%s=%s' % (k, _repr(v)) for k,v in vs.items())) + "List subscriptions. See subscribe." + for (_, [_, q], results) in self.interp.chart['$subscribed/2'][:,:,:]: + if results: + print q + for result in results: + print ' ', _repr(result.value), 'when', drepr(dict(result.variables)) print + def do_help(self, line): + mod = line.split() + if len(mod) <= 1: + return super(REPL, self).do_help(line) + else: + if len(mod) == 2: + [cmd, sub] = mod + if cmd in ('load', 'post'): + try: + print getattr(globals()[cmd], sub).__doc__ + except (KeyError, AttributeError): + print 'No help available for "%s %s"' % (cmd, sub) + return + else: + return + print 'Error: Did not understand help command.' + def do_load(self, line): + """ + Execute load command. + + Available loaders: + + {loaders} + + For more information about a particular loader type the following (in + this case we get help for the `tsv` loader): + + :- help load tsv + + Examples: + + :- load data = tsv("examples/data/data.csv", delim=',') + :- sol + Solution + ======== + data/3 + ====== + data(2,"cow","boy") := true + + data/4 + ====== + data(0,"a","b","3.0") := true + data(1,"c","d","4.0") := true + + """ try: load.run(self.interp, line) -# self.interp.dump_charts() except: show_traceback() readline.write_history_file(self.hist) + do_load.__doc__ = do_load.__doc__.format(loaders=', '.join(x for x in dir(load) if not x.startswith('_'))) + def do_post(self, line): + """ + Execute post-processor. + + Available post-processors: + + {post} + + For more information about a particular post processor (in this case + `save`) + + :- help post save + + """ try: post.run(self.interp, line) except: show_traceback() readline.write_history_file(self.hist) + + do_post.__doc__ = do_post.__doc__.format(post=', '.join(x for x in dir(post) if not x.startswith('_'))) diff --git a/test/repl/aggregator-conflict.expect b/test/repl/aggregator-conflict.expect index 143f9ff..a571301 100644 --- a/test/repl/aggregator-conflict.expect +++ b/test/repl/aggregator-conflict.expect @@ -1,6 +1,5 @@ :- :- ============= a := 1 - :- DynaCompilerError: FATAL: Encountered error in input program: Conflicting aggregators; rule /home/timv/.dyna/tmp/966093dc38b755a6f17b02774b5c656931163a3a.dyna:5:1-/home/timv/.dyna/tmp/966093dc38b755a6f17b02774b5c656931163a3a.dyna:5:3 diff --git a/test/repl/late-aggregator-assignment.expect b/test/repl/late-aggregator-assignment.expect index ae5380e..0599ae3 100644 --- a/test/repl/late-aggregator-assignment.expect +++ b/test/repl/late-aggregator-assignment.expect @@ -1,13 +1,11 @@ :- :- :- 0: a += b * c. :- ============= b := 2 - :- 0: a += b * c. 1: b := 2. :- ============= a := 6 c := 3 - :- 0: a += b * c. 1: b := 2. 2: c := 3. diff --git a/test/repl/retract-rule b/test/repl/retract-rule index cacff9a..25e039a 100755 --- a/test/repl/retract-rule +++ b/test/repl/retract-rule @@ -5,10 +5,10 @@ a += 1. b += 1. a += 1. rules -chart +sol retract_rule 0 retract_rule 1 -chart" |./dyna > $0.out +sol" |./dyna > $0.out diff $0.expect $0.out && echo pass diff --git a/test/repl/retract-rule.expect b/test/repl/retract-rule.expect index c4c5012..bfc109b 100644 --- a/test/repl/retract-rule.expect +++ b/test/repl/retract-rule.expect @@ -1,35 +1,21 @@ :- :- ============= a := 1 - :- ============= b := 1 - :- ============= a := 2 - :- 0: a += 1. 1: b += 1. 2: a += 1. :- -Charts -============ -a/0 -================= -a := 2 - -b/0 -================= -b := 1 +Solution +======== +a := 2 +b := 1 :- :- :- -Charts -============ -a/0 -================= -a := 1 - -b/0 -================= - +Solution +======== +a := 1 :- exit -- 2.50.1