From 53b901cef81471032428bc08748b7a479956662f Mon Sep 17 00:00:00 2001 From: Tim Vieira Date: Fri, 28 Jun 2013 01:31:32 -0400 Subject: [PATCH] `draw_circuit` and `trace` work with BC computation BUGFIX: looks like my previous bugs fix broke alignment with python and dopamine code. I've fixed the Python code. Dopamine coming soon. REPL `query` no longer kept up-to-date with FC because we throw away the rule once we're done with it. added REPL `run` command to execute dyna code from a different file (NOTE: there is no namespacing so bad things can happen.) BUGFIX: Fixed cyclic `Term` comparsion. BUGFIX: Fixed dictionary changed size on iteration exception. Thanks to Jason for reporting. --- src/Dyna/Backend/Python/Backend.hs | 28 +++++---- src/Dyna/Backend/Python/chart.py | 4 +- src/Dyna/Backend/Python/debug.py | 38 +++--------- src/Dyna/Backend/Python/interpreter.py | 61 +++++--------------- src/Dyna/Backend/Python/post/draw_circuit.py | 21 ++++--- src/Dyna/Backend/Python/post/trace.py | 2 +- src/Dyna/Backend/Python/repl.py | 42 +++++++++++--- src/Dyna/Backend/Python/term.py | 16 +++-- src/Dyna/Backend/Python/utils.py | 16 ++++- 9 files changed, 117 insertions(+), 111 deletions(-) diff --git a/src/Dyna/Backend/Python/Backend.hs b/src/Dyna/Backend/Python/Backend.hs index 337f743..0b297b5 100644 --- a/src/Dyna/Backend/Python/Backend.hs +++ b/src/Dyna/Backend/Python/Backend.hs @@ -253,17 +253,25 @@ pdope_ _ (OPIter v vs f d (Just (PDBS c))) = dynacPanic $ <+> parens (pretty $ c v vs) -- XXX This works only for the special case at hand (thus the asserts) -pdope_ bc (OPIter o m f DetSemi Nothing) | (f,length m) `S.member` bc = +pdope_ bc (OPIter o m f DetSemi Nothing) | (f,length m) `S.member` bc = do + dookie <- incState return $ - assert (iIsFree $ nExpose $ o^.mv_mi) $ - assert (all (not . iIsFree . nExpose . _mv_mi) m) $ - vcat - [ pretty (o^.mv_var) - <+> equals - <+> "gbc" - <> tupled (pfas f m : map (pretty . _mv_var) m) - , "if" <+> pretty (o^.mv_var) <+> "is not None" <> colon - ] + assert (iIsFree $ nExpose $ o^.mv_mi) $ + assert (all (not . iIsFree . nExpose . _mv_mi) m) $ + vcat + [ pretty (o^.mv_var) + <+> equals + <+> "gbc" + <> tupled (pfas f m : map (pretty . _mv_var) m) + +--- needs an opbuild + , ("d" <> pretty dookie) + <+> equals + <+> ("build" <> tupled (pfas f m : map (pretty . _mv_var) m)) + + , "if" <+> pretty (o^.mv_var) <+> "is not None" <> colon + + ] pdope_ bc (OPIter o m f _ Nothing) = assert (not $ (f,length m) `S.member` bc) $ do diff --git a/src/Dyna/Backend/Python/chart.py b/src/Dyna/Backend/Python/chart.py index 7bd2f07..7b90ad6 100644 --- a/src/Dyna/Backend/Python/chart.py +++ b/src/Dyna/Backend/Python/chart.py @@ -41,10 +41,10 @@ class Chart(object): if len(b) == 0: # all arguments are free. - candidates = self.intern.itervalues() + candidates = self.intern.values() elif len(b) == 1: - candidates = iter(b[0]) + candidates = list(b[0]) else: b.sort(key=len) # start with smaller ones diff --git a/src/Dyna/Backend/Python/debug.py b/src/Dyna/Backend/Python/debug.py index 9823e42..e42d132 100644 --- a/src/Dyna/Backend/Python/debug.py +++ b/src/Dyna/Backend/Python/debug.py @@ -349,51 +349,31 @@ Update %s """ % (bline, kv, block) + # ------------- + # Dopamine code with file(d + '/dopini') as f: code = f.read() print >> html, '

Initialization plans

' for (f,bline,bcol,eline,ecol,kv,block) in \ - re.findall(';; (.*?):(\d+):(\d+)-.*?:(\d+):(\d+) (.*)\n((?: [^\n]*\n)*)' - , code) : + re.findall(';; (.*?):(\d+):(\d+)-.*?:(\d+):(\d+) (.*)\n((?: [^\n]*\n)*)', code): + print >> html, """
Initializer:\n%s
""" % (bline, block) - print >> html, """\ -
-
-Initializer:
-%s
-
-
-""" % (bline, block) + # ---------------- + # Python code with file(d + '/plan') as f: code = f.read() - # print >> html, code - print >> html, '

Update code

' - for block in re.split('\n\s*\n', code): - - x = re.findall('Span:\s*(.*?):(\d+):(\d+)-.*?:(\d+):(\d+)\n', - block) - + x = re.findall('RuleIx: (\d+)\n', block) if not x: continue - - [(f, bline, bcol, eline, ecol)] = x - code = block + [ruleix] = x lexer = get_lexer_by_name("python", stripall=True) formatter = HtmlFormatter(linenos=False) - pretty_code = highlight(code, lexer, formatter) - - print >> html, """\ -
-
-%s
-
-
-""" % (bline, pretty_code) + print >> html, """
%s
""" % (ruleix, highlight(block, lexer, formatter)) print >> html, '' print >> html, '' diff --git a/src/Dyna/Backend/Python/interpreter.py b/src/Dyna/Backend/Python/interpreter.py index f3998b5..2bf13b9 100644 --- a/src/Dyna/Backend/Python/interpreter.py +++ b/src/Dyna/Backend/Python/interpreter.py @@ -39,7 +39,6 @@ TODO - functor - ignore variable - - output formats: vquery and query - show diffs Maybe subscription to diff is a different beast, only available as a @@ -200,7 +199,7 @@ import load, post from chart import Chart, Term, _repr from defn import aggregator from utils import ip, red, green, blue, magenta, yellow, parse_attrs, \ - ddict, dynac, read_anf + ddict, dynac, read_anf, strip_comments from prioritydict import prioritydict from config import dotdynadir @@ -244,7 +243,7 @@ class Rule(object): return parse_attrs(self.init or self.query)['Span'] @property def src(self): - return parse_attrs(self.init or self.query)['rule'] + return strip_comments(parse_attrs(self.init or self.query)['rule']) def __repr__(self): return 'Rule(%s, %r)' % (self.idx, self.src) @@ -281,9 +280,6 @@ class Interpreter(object): self.rules = ddict(Rule) self.error = {} - # not essential, available in parser_state - self.backchained = set() - def __getstate__(self): return ((self.chart, self.agenda, @@ -327,7 +323,7 @@ class Interpreter(object): print >> out, '========' fns = self.chart.keys() fns.sort() - fns = [x for x in fns if x not in self.backchained] # don't show backchained items + fns = [x for x in fns if x not in self._gbc] # don't show backchained items 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 @@ -364,26 +360,6 @@ class Interpreter(object): for i in sorted(self.rules): print '%3s: %s' % (i, self.rules[i].src) -# def query(self, q): -# if q.endswith('.'): -# print "Queries don't end with a dot." -# return -# -# query = 'out("%s") dict= %s.' % (q, q) -# -# src = self.dynac_code(query) # might raise DynaCompilerError -# self.do(src) -# -# try: -# [(_, _, results)] = self.chart['out/1'][q,:] -# except ValueError: -# print 'No results.' -# return -# -# for val, bindings in results: -# print ' ', val, 'when', bindings -# print - def build(self, fn, *args): # TODO: codegen should handle true/0 is True and false/0 is False if fn == "true/0": @@ -401,24 +377,10 @@ class Interpreter(object): # def retract_item(self, item): # """ -# For the moment we only correctly retract leaves. -# -# If you retract a non-leaf item, you run the risk of it being -# rederived. In the case of cyclic programs the derivation might be the -# same or different. +# For the moment we only correctly retract leaves. If you retract a +# non-leaf item, you run the risk of it being rederived. In the case of +# cyclic programs the derivation might be the same or different. # """ -# # and now, for something truely horrendous -- look up an item by it's -# # string value! This could fail because of whitespace or trivial -# # formatting differences. -# items = {} -# for c in self.chart.values(): -# for i in c.intern.values(): -# items[str(i)] = i -# try: -# item = items[item] -# except KeyError: -# print 'item not found. This could be because of a trivial formatting differences...' -# return # self.emit(item, item.value, None, sys.maxint, delete=True) # return self.go() @@ -571,7 +533,6 @@ class Interpreter(object): A rule is bad if the compiler rejects it or it's initializer fails. """ assert os.path.exists(filename) -# assert os.path.exists(filename + '.anf') env = imp.load_source('dynamically_loaded_module', filename) @@ -591,6 +552,13 @@ class Interpreter(object): for k, v in env.agg_decl.items(): self.new_fn(k, v) + new_rules = set() + for _, r, _ in env.queries: + new_rules.add(r) + for r, _ in env.initializers: + new_rules.add(r) + self.new_rules = new_rules + for fn, r, h in env.queries: self.new_query(fn, r, h) @@ -619,8 +587,6 @@ class Interpreter(object): # accept the new parser state self.parser_state = env.parser_state - self.backchained = {f + '/' + a for f, a in re.findall(":-backchain '([^']+)'/(\d+).", env.parser_state)} - # process emits for e in emits: self.emit(*e, delete=False) @@ -709,7 +675,6 @@ def main(): if args.plan: plan = args.source else: -# plan = dotdynadir / 'tmp' / args.source.read_hexhash('sha1') + '.plan.py' plan = args.source + '.plan.py' dynac(args.source, plan) diff --git a/src/Dyna/Backend/Python/post/draw_circuit.py b/src/Dyna/Backend/Python/post/draw_circuit.py index e697a3e..b01a5e7 100644 --- a/src/Dyna/Backend/Python/post/draw_circuit.py +++ b/src/Dyna/Backend/Python/post/draw_circuit.py @@ -1,7 +1,4 @@ # -*- coding: utf-8 -*- -""" -TODO: nwf remove comments from rule source -""" import webbrowser from debug import Hypergraph @@ -29,7 +26,17 @@ def infer_edges(interp): edges.add((item, ruleix, tuple(b), variables)) for r in interp.rules.values(): - r.init(emit=_emit) + if r.init is not None: + r.init(emit=_emit) + else: + assert r.query is not None + + # todo: this might pick nodes that aren't used + for fn, hs in interp._gbc.items(): + for x in interp.chart[fn].intern.values(): + if x.value is not None: + for h in hs: + h(*x.args, emit=_emit) return edges @@ -42,8 +49,7 @@ class draw_circuit(object): def __init__(self, interp): self.interp = interp - def main(self, outfile): - global interp + def main(self, outfile, open=True): interp = self.interp es = infer_edges(interp) @@ -80,4 +86,5 @@ class draw_circuit(object): print >> f, '' - webbrowser.open(f.name) + if open: + webbrowser.open(f.name) diff --git a/src/Dyna/Backend/Python/post/trace.py b/src/Dyna/Backend/Python/post/trace.py index ba54ca2..c28b8f2 100644 --- a/src/Dyna/Backend/Python/post/trace.py +++ b/src/Dyna/Backend/Python/post/trace.py @@ -64,7 +64,7 @@ def dig(head, visited, groups, interp): block = branch([dig(x, visited, groups, interp) for x in body]) if block: - contribs.append(crux.format() + [''] + block) + contribs.append(crux.format() + ['|'] + block) else: contribs.append(crux.format()) diff --git a/src/Dyna/Backend/Python/repl.py b/src/Dyna/Backend/Python/repl.py index 3af4120..aaa1ccc 100644 --- a/src/Dyna/Backend/Python/repl.py +++ b/src/Dyna/Backend/Python/repl.py @@ -1,7 +1,7 @@ """ TODO: unsubscribe -TODO: should probably remove the new rule after we get the results. +TODO: query should probably remove the new rule after we get the results. TODO: subscriptions probably should only show "changes" @@ -17,7 +17,7 @@ TODO: $include load rules from a file. import re, os, cmd, readline import debug, interpreter -from utils import ip, lexer, subst +from utils import dynac, ip, lexer, subst from errors import DynaCompilerError, DynaInitializerException from chart import _repr from config import dotdynadir @@ -136,17 +136,43 @@ class REPL(cmd.Cmd, object): f.write(line) debug.main(f.name) + def do_run(self, filename): + """ + Load dyna rules from `filename`. + + :- run examples/papa.dyna + + """ + try: + changed = self.interp.do(dynac(filename)) + except DynaCompilerError as e: + print e + else: + self._changed(changed) + def _query(self, q): if q.endswith('.'): print "Queries don't end with a dot." return - query = '$out(%s) dict= %s.' % (self.lineno, q) - self.default(query, show_changed=False) + + self.interp.new_rules = set() + try: - [(_, _, results)] = self.interp.chart['$out/1'][self.lineno,:] - except ValueError: - return [] - return results + query = "$out(%s) dict= %s." % (self.lineno, q) + self.default(query, show_changed=False) + try: + [(_, _, results)] = self.interp.chart['$out/1'][self.lineno,:] + return results + except ValueError: + return [] + finally: + # cleanup: + # retract newly added rules. + for r in self.interp.new_rules: + self.interp.retract_rule(r) + # drop $out chart + del self.interp.chart['$out/1'] + def do_vquery(self, q): """ diff --git a/src/Dyna/Backend/Python/term.py b/src/Dyna/Backend/Python/term.py index f03d600..79b96b2 100644 --- a/src/Dyna/Backend/Python/term.py +++ b/src/Dyna/Backend/Python/term.py @@ -12,17 +12,24 @@ class Term(object): self.value = None self.aggregator = None + def __eq__(self, other): + if other is None: + return False + if not isinstance(other, Term): + return False + return self.fn == other.fn and self.args == other.args + def __cmp__(self, other): +# if self is other: +# return 0 if other is None: return 1 if not isinstance(other, Term): return 1 +# if self == other: +# return 0 return cmp((self.fn, self.args), (other.fn, other.args)) - # default hash and eq suffice because we intern - #def __hash__(self): - #def __eq__(self): - def __repr__(self): "Pretty print a term. Will retrieve the complete (ground) term." fn = '/'.join(self.fn.split('/')[:-1]) # drop arity from name. @@ -226,4 +233,3 @@ def symbol(name): # X.value = Y # print [X,Y,Z] # assert X.value == Y.value == Z.value == 3 - diff --git a/src/Dyna/Backend/Python/utils.py b/src/Dyna/Backend/Python/utils.py index 7e6d0cd..92162e4 100644 --- a/src/Dyna/Backend/Python/utils.py +++ b/src/Dyna/Backend/Python/utils.py @@ -29,13 +29,25 @@ black, red, green, yellow, blue, magenta, cyan, white = \ map('\033[3%sm%%s\033[0m'.__mod__, range(8)) -def dynac(f, out): +_comments = re.compile('%.*$', re.MULTILINE) +def strip_comments(src): + return _comments.sub('', src).strip() + + +def dynac(f, out=None): """ Run compiler on file, ``f``, write results to ``out``. Raises ``DynaCompilerError`` on failure. """ from errors import DynaCompilerError + f = path(f) + if not f.exists(): + raise DynaCompilerError("File '%s' does not exist." % f) + + if out is None: + out = dotdynadir / 'tmp' / f.read_hexhash('sha1') + '.plan.py' + p = Popen(['%s/dist/build/dyna/dyna' % dynahome, '--dump-anf=' + out + '.anf', # timv: don't like this filename... '-B', 'python', '-o', out, f], stdout=PIPE, stderr=PIPE) @@ -44,6 +56,8 @@ def dynac(f, out): assert not stdout.strip(), [stdout, stderr] raise DynaCompilerError(stderr) + return out + def lexer(term): return re.findall('"[^"]*"' # string -- 2.50.1