]> hydra-www.ietfng.org Git - dyna2/commitdiff
Many changes
authorTim Vieira <tim.f.vieira@gmail.com>
Sun, 30 Jun 2013 02:49:42 +0000 (22:49 -0400)
committerTim Vieira <tim.f.vieira@gmail.com>
Sun, 30 Jun 2013 02:49:42 +0000 (22:49 -0400)
* Support for `=` aggregator. #23

* BUGFIX: when --plan is specified (sorry :-X)

* tweak to error handling. when an item's aggregator fails we set it's value to
  $error instead of it's last "ok" value.

  the repl also show this item as "changed"

* tweaks to doctest runnner

* attempt at contains/2 for list membership -- will eventually change to infix
  operator `in`

* cons and nil can't be assigned values.

src/Dyna/Backend/Python/Backend.hs
src/Dyna/Backend/Python/chart.py
src/Dyna/Backend/Python/defn.py
src/Dyna/Backend/Python/dyna-doctest.py
src/Dyna/Backend/Python/errors.py
src/Dyna/Backend/Python/interpreter.py
src/Dyna/Backend/Python/repl.py
src/Dyna/Backend/Python/term.py
test/repl/aggregator-conflict
test/repl/late-aggregator-assignment.expect
test/repl/retract-rule.expect

index 74d4faceb055ef5d0a71c6b4d8c45fa0d183e931..8d697d9b754a42bcf856d2b31e140f7d8358193c 100644 (file)
@@ -53,6 +53,7 @@ aggrs = S.fromList
   , "+=" , "*="
   , "and=" , "or=" , "&=" , "|="
   , ":-"
+  , "="
   , "majority=" , "set=" , "bag="
   , ":="
   , "dict="
@@ -152,7 +153,6 @@ constants = go
   go ("split", _)   = Just $ PDBS $ call "split" []
   go ("float", _)   = Just $ PDBS $ call "float" []
   go ("int", _)     = Just $ PDBS $ call "int" []
-  go ("getattr", _) = Just $ PDBS $ call "getattr" []
   go ("pycall", _)  = Just $ PDBS $ call "pycall" []
 
   go ("<=",2)    = Just $ PDBS $ infixOp "<="
index 0b14aeb17d17062476c958d3b93bbfa75c7ed9e4..7d3a9a89254ab911c415919307811d345ab1b0e9 100644 (file)
@@ -17,17 +17,32 @@ 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:
+            [term] = rows
             return '%s := %s' % (term, _repr(term.value))
-
-        x = '\n'.join('%-30s := %s' % (term, _repr(term.value)) for term in sorted(rows))
-        return '%s\n%s\n%s\n' % (self.name, '='*len(self.name), x)
+        p = [(_repr(term), _repr(term.value)) for term in sorted(rows)]
+
+        lines = [self.name, '='*len(self.name)]  # heading
+
+        for term, value in p:
+            lines.append('%-30s := %s' % (term, value))
+
+#        terms, values = zip(*p)
+#        widths = map(len, terms)
+#        fmt = '%%-%ds => %%s.' % max(widths)
+#        if max(widths) > 50:
+#            for term, value in zip(terms, values):
+#                lines.append(term)
+#                lines.append('   => %s.' % value)
+#        else:
+#            for term, value in zip(terms, values):
+#                lines.append(fmt % (term, value))
+
+        lines.append('')
+        return '\n'.join(lines)
 
     def __getitem__(self, s):
         assert len(s) == self.arity + 1, \
index a45b35e5fb08b43e731dd994746e7b8a28b5228b..a67c4d2c638967906280ee116755ab4c1efbbc34 100644 (file)
@@ -3,10 +3,14 @@ from __future__ import division
 # TODO: codegen should produce specialized Term with inc/dec methods baked
 # in. This seems nicer than having a separate aggregator object.
 
+# TODO: aggregators might want a reference to the item they are associated with.
+
 import operator
 from collections import Counter
 from utils import drepr, _repr
+from errors import AggregatorError
 
+"""
 class Aggregator(object):
     def fold(self):
         raise NotImplementedError
@@ -16,6 +20,24 @@ class Aggregator(object):
         raise NotImplementedError
     def clear(self):
         raise NotImplementedError
+"""
+
+class NoAggregatorError(Exception):
+    """
+    raised when an item doesn't have an aggregator.
+    """
+    pass
+
+
+class Aggregator(object):
+    def fold(self):
+        raise AggregatorError("item doesn't have an aggregator.")
+    def inc(self, _val, _ruleix, _variables):
+        pass
+    def dec(self, _val, _ruleix, _variables):
+        pass
+    def clear(self):
+        pass
 
 
 class BAggregator(Counter, Aggregator):
@@ -53,6 +75,19 @@ class ColonEquals(BAggregator):
             return max(vs)[1]
 
 
+class Equals(BAggregator):
+    def inc(self, val, _ruleix, _variables):
+        self[val] += 1
+    def dec(self, val, _ruleix, _variables):
+        self[val] -= 1
+    def fold(self):
+        vs = [v for v, cnt in self.iteritems() if cnt > 0]
+        if len(vs) != 1:
+            vs.sort()   # for stability
+            raise AggregatorError('`=` got conflicting values %s' % (vs,))
+        return vs[0]
+
+
 def user_vars(variables):
     "Post process the variables past to emit (which passes them to aggregator)."
     # remove the 'u' prefix on user variables 'uX'
@@ -172,6 +207,9 @@ def aggregator(name):
     if name == ':=':
         return ColonEquals()
 
+    elif name == '=':
+        return Equals()
+
     elif name == 'dict=':
         return DictEquals()
 
index 0a9caf3c68c99bf82dcb8b6fc854c4aeead140bb..3d46866d5ee55fc61a266bb3dd4581702432bafc 100755 (executable)
@@ -7,7 +7,7 @@ from interpreter import Interpreter
 from repl import REPL
 from cStringIO import StringIO
 
-from utils import red, green
+from utils import red, green, strip_comments
 
 
 def extract(code):
@@ -21,6 +21,9 @@ def run(code):
     repl = REPL(interp)
 
     for cmd, expect in extract(code):
+        if not cmd.strip():
+            print
+            continue
         print ':-', cmd
         sys.stdout = x = StringIO()
         try:
@@ -29,7 +32,7 @@ def run(code):
             sys.stdout = sys.__stdout__
         got = x.getvalue().strip()
         expect = expect.strip()
-        if expect != got:
+        if strip_comments(expect) != strip_comments(got):
             print green % expect
             print red % got
         else:
index d7ac76d5814e8dfb616eb4129a999db2e1da75ff..aed9e25e4636af70e8b3a8287db1427f3b3b731f 100644 (file)
@@ -8,6 +8,9 @@ class DynaCompilerError(Exception):
     pass
 
 
+class AggregatorError(Exception):
+    pass
+
 class DynaInitializerException(Exception):
     def __init__(self, exception, init):
         rule = parse_attrs(init)['rule']
index f5fc997c6173fbcec49640884aeebb83c38a0dd1..ee04af91e151e233cb8843915e785b199affdc84 100644 (file)
@@ -19,10 +19,6 @@ TODO
 
    - sheebang?
 
-
- - TODO: @nwf remove comments from rule source
-
-
  - vbench: a script which tracks performace over time (= git commits).
 
  - profiler workflow
@@ -139,7 +135,7 @@ from utils import ip, red, green, blue, magenta, yellow, parse_attrs, \
 
 from prioritydict import prioritydict
 from config import dotdynadir
-from errors import crash_handler, DynaInitializerException
+from errors import crash_handler, DynaInitializerException, AggregatorError
 
 
 class Rule(object):
@@ -165,6 +161,10 @@ class foo(dict):
         self.agg_name = agg_name
         super(foo, self).__init__()
     def __missing__(self, fn):
+
+        if fn == 'contains/2':
+            return Contains()
+
         arity = int(fn.split('/')[-1])
         self[fn] = c = Chart(fn, arity, self.agg_name[fn])
         return c
@@ -174,7 +174,37 @@ def none():
     return None
 
 
-import os
+class Contains(object):
+
+    def __init__(self):
+        self.name = 'contains'
+        self.arity = 2
+
+    def __repr__(self):
+        return 'contains/2'
+
+    def __getitem__(self, s):
+        assert len(s) == self.arity + 1, \
+            'Chart %r: item width mismatch: arity %s, item %s' % (self.name, self.arity, len(s))
+        [x, xs], val = s[:-1], s[-1]
+        #assert val is True
+        if isinstance(x, slice):
+            assert not isinstance(xs, slice)
+            for a in xs.tolist():
+                term = Term('contains/2', (a, xs))
+                term.value = True
+                yield term, term.args, term.value
+
+        else:
+            # all bound membership test
+            assert not isinstance(x, slice) and not isinstance(xs, slice)
+            term = Term('contains/2', (x, xs))
+            term.value = (x in xs.tolist())
+            yield term, term.args, term.value
+
+    def insert(self, args):
+        assert False
+
 
 class Interpreter(object):
 
@@ -277,8 +307,14 @@ class Interpreter(object):
         print >> out
 
     def dump_rules(self):
+        if not self.rules:
+            return
+        print
+        print 'Rules'
+        print '====='
         for i in sorted(self.rules):
             print '%3s: %s' % (i, self.rules[i].src)
+        print
 
     def build(self, fn, *args):
         # TODO: codegen should handle true/0 is True and false/0 is False
@@ -345,9 +381,22 @@ class Interpreter(object):
             was = item.value
             try:
                 now = item.aggregator.fold()
+            except AggregatorError as e:
+                error[item] = ('failed to aggregate item `%r` because %s' % (item, e), [(e, None)])
+
+                now = self.build('$error/0')
+                changed[item] = now
+                item.value = now
+                continue
+
             except (ZeroDivisionError, TypeError, KeyboardInterrupt, NotImplementedError) as e:
                 error[item] = ('failed to aggregate %r' % item.aggregator, [(e, None)])
+
+                now = self.build('$error/0')
+                changed[item] = now
+                item.value = now
                 continue
+
             if was == now:
                 continue
             was_error = False
@@ -603,7 +652,7 @@ def main():
 
         if args.plan:
             # copy plan to tmp directory
-            plan = tmp / args.source.read_hexhash('sha1') + '.plan.py'
+            plan = interp.tmp / args.source.read_hexhash('sha1') + '.plan.py'
             args.source.copy(plan)
 
         else:
index b7ba7d5fff5201145947b00050bc000c3b70d9df..0cd8c622690d86ae42a0e8437784a11af5622a9e 100644 (file)
@@ -57,6 +57,9 @@ class REPL(cmd.Cmd, object):
         :- c += a*b.
 
         :- rules
+
+        Rules
+        =====
           0: a += 1.
           1: b += 1.
           2: c += a * b.
index f0846a3f00c0ad3cac798c7f40c451871a3081be..bc968e8e7e6ccadbf52b84e42f93c693fb53b19f 100644 (file)
@@ -1,5 +1,7 @@
 from errors import notimplemented
 from utils import _repr
+from defn import Aggregator
+
 
 # TODO: codegen should output a derived Term instance for each functor
 class Term(object):
@@ -52,6 +54,7 @@ class Cons(Term):
         self.tail = tail
         assert isinstance(tail, (Cons, _Nil)), tail
         Term.__init__(self, 'cons/2', (head, tail))
+        self.aggregator = Aggregator()
     def tolist(self):
         return [self.head] + self.tail.tolist()
     def __repr__(self):
@@ -65,6 +68,8 @@ class Cons(Term):
 class _Nil(Term):
     def __init__(self):
         Term.__init__(self, 'nil/0', ())
+        self.aggregator = Aggregator()
+
     def tolist(self):
         return []
     def __repr__(self):
index 31b7f66522b1f1cbb4af8a800a1971d3834507c3..fb02378c28e265de8ed07e59420a84d3b75e9c76 100755 (executable)
@@ -4,6 +4,6 @@ echo -e "
 a += 1.
 a." |./dyna > $0.out
 
-diff <(sed -e 's/[ -][^ -]*\/\.dyna/ /g' $0.expect) \
-     <(sed -e 's/[ -][^ -]*\/\.dyna/ /g' $0.out)    \
+diff <(sed -e 's/[ -][^ -]*\.dyna/ /g' $0.expect) \
+     <(sed -e 's/[ -][^ -]*\.dyna/ /g' $0.out)    \
  && echo pass
index 0599ae37959647b3a9d83d922c9ed1595fa3c22d..a5e4486dc7f0c1997799fd3938dfa8c5606eb621 100644 (file)
@@ -1,12 +1,24 @@
-:- :- :-   0: a += b * c.
+:- :- :- 
+Rules
+=====
+  0: a += b * c.
+
 :- =============
 b := 2
-:-   0: a += b * c.
+:- 
+Rules
+=====
+  0: a += b * c.
   1: b := 2.
+
 :- =============
 a := 6
 c := 3
-:-   0: a += b * c.
+:- 
+Rules
+=====
+  0: a += b * c.
   1: b := 2.
   2: c := 3.
+
 :- exit
index bfc109b63a47014ee17c452e7d242ba3719dcaed..001c5f400848d2b6bc87b20ff42a27a391633ea1 100644 (file)
@@ -4,9 +4,13 @@ a := 1
 b := 1
 :- =============
 a := 2
-:-   0: a += 1.
+:- 
+Rules
+=====
+  0: a += 1.
   1: b += 1.
   2: a += 1.
+
 :- 
 Solution
 ========