]> hydra-www.ietfng.org Git - dyna2/commitdiff
Overhaul error message displays
authorTim Vieira <tim.f.vieira@gmail.com>
Wed, 3 Jul 2013 20:57:56 +0000 (16:57 -0400)
committerTim Vieira <tim.f.vieira@gmail.com>
Wed, 3 Jul 2013 20:57:56 +0000 (16:57 -0400)
 - "pop-time": aggregation errors (e.g. two distinct values following into an
   `=` aggregator) are group by functor/arity, then by type and only 10
   instances are shown.

 - "push-time": errors are grouped by rule, then error type (e.g. TypeError,
   DivisionByZero), only 10 instances of the error are displayed.

 - These changes help avoid an overwhelming cascade of very similar
   errors. Users should be able to trace the sources of these errors using other
   tools (e.g. `trace`)

Resolve issue #36

Program rules available in $rule -- doesn't do anything yet however.

Tweaks to doctest runner.

examples/errors.dyna
src/Dyna/Backend/Python/defn.py
src/Dyna/Backend/Python/dyna-doctest.py
src/Dyna/Backend/Python/interpreter.py
src/Dyna/Backend/Python/load/sexpr.py
src/Dyna/Backend/Python/post/draw_circuit.py
src/Dyna/Backend/Python/post/trace.py
src/Dyna/Backend/Python/repl.py
src/Dyna/Backend/Python/stdlib.py
src/Dyna/Backend/Python/utils.py
test/repl/retract-rule.doctest

index 084a021083f2fc6ca9da0efb28ac8432e734ba42..aa3943601ee6f27bca7c79fe66b2e80489b0e05d 100644 (file)
@@ -15,3 +15,12 @@ e := 0.
 
 d += null.
 d += 1.
+
+
+a(X) := f(X,Y).
+
+f(1,1) := 1.
+f(1,2) := 2.
+
+f(2,1) := 1.
+f(2,2) := 2.
index a0a33563e37175af524028af451701a81057e287..87fd23c48e1094ea3666def402f87b8f8b8504b8 100644 (file)
@@ -7,7 +7,7 @@ from __future__ import division
 
 import operator
 from collections import Counter
-from utils import drepr, _repr
+from utils import drepr, _repr, user_vars
 from errors import AggregatorError
 
 """
@@ -43,36 +43,43 @@ class Aggregator(object):
 class BAggregator(Counter, Aggregator):
 #    def __init__(self):
 #        super(BAggregator, self).__init__()
-    def inc(self, val, ruleix, variables):
+    def inc(self, val, _ruleix, _variables):
         self[val] += 1
-    def dec(self, val, ruleix, variables):
+    def dec(self, val, _ruleix, _variables):
         self[val] -= 1
     def fromkeys(self, *_):
         assert False, "This method should never be called."
 
 
-class PlusEquals(object):
-    __slots__ = 'pos', 'neg'
-    def __init__(self):
-        self.pos = 0
-        self.neg = 0
-    def inc(self, val, ruleix, variables):
-        self.pos += val
-    def dec(self, val, ruleix, variables):
-        self.neg += val
-    def fold(self):
-        return self.pos - self.neg
+#class PlusEquals(object):
+#    __slots__ = 'pos', 'neg'
+#    def __init__(self):
+#        self.pos = 0
+#        self.neg = 0
+#    def inc(self, val, ruleix, variables):
+#        self.pos += val
+#    def dec(self, val, ruleix, variables):
+#        self.neg += val
+#    def fold(self):
+#        return self.pos - self.neg
 
 
 class ColonEquals(BAggregator):
-    def inc(self, val, ruleix, variables):
+    def inc(self, val, ruleix, _variables):
         self[ruleix, val] += 1
-    def dec(self, val, ruleix, variables):
+    def dec(self, val, ruleix, _variables):
         self[ruleix, val] -= 1
     def fold(self):
-        vs = [v for v, cnt in self.iteritems() if cnt > 0]
+        vs = [v for v, m in self.iteritems() if m > 0]
         if vs:
-            return max(vs)[1]
+            [i, v] = max(vs)
+            vs = {v for (r, v) in vs if r == i}   # filter down to max rule index
+            if len(vs) == 1:
+                return v
+            else:
+                vs = list(vs)   # for stability
+                vs.sort()
+                raise AggregatorError('`:=` got conflicting values %s for rule index %s' % (vs, i))
 
 
 class Equals(BAggregator):
@@ -82,19 +89,15 @@ class Equals(BAggregator):
         self[val] -= 1
     def fold(self):
         vs = [v for v, cnt in self.iteritems() if cnt > 0]
-        if len(vs) != 1:
+        if len(vs) == 0:
+            return
+        if len(vs) == 1:
+            return vs[0]
+        else:
             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'
-    # Note: We also ignore user variables with an underscore prefix
-    return tuple((name[1:], val) for name, val in variables
-                 if name.startswith('u') and not name.startswith('u_'))
-
 
 from collections import namedtuple
 class Result(namedtuple('Result', 'value variables')):
@@ -104,13 +107,12 @@ class Result(namedtuple('Result', 'value variables')):
 
 class DictEquals(BAggregator):
 
-    def inc(self, val, ruleix, variables):
-        # I think we only want user variables -- XXX: are we guaranteed to have
-        # all of the user variables?
+    def inc(self, val, _ruleix, variables):
+        # I think we only want user variables
         vs = user_vars(variables)
         self[val, vs] += 1
 
-    def dec(self, val, ruleix, variables):
+    def dec(self, val, _ruleix, variables):
         vs = user_vars(variables)
         self[val, vs] -= 1
 
@@ -200,15 +202,15 @@ class b_or_equals(BAggregator):
 
 class set_equals(BAggregator):
     def fold(self):
-        from stdlib import todynalist
+        from stdlib import todyna
         s = {x for x, m in self.iteritems() if m > 0}
         if len(s):
-            return todynalist(s)
+            return todyna(s)
 
 class bag_equals(BAggregator):
     def fold(self):
-        from stdlib import todynalist
-        return todynalist(Counter(self).elements())
+        from stdlib import todyna
+        return todyna(list(Counter(self).elements()))
 
 
 # map names to functions
index 3d46866d5ee55fc61a266bb3dd4581702432bafc..9be0c3fd59c0bce0190b4d97fec13ac85a41662b 100755 (executable)
@@ -7,11 +7,11 @@ from interpreter import Interpreter
 from repl import REPL
 from cStringIO import StringIO
 
-from utils import red, green, strip_comments
+from utils import red, green, yellow, strip_comments
 
 
 def extract(code):
-    for block in re.compile('^:- ', re.MULTILINE).split(code):
+    for block in re.compile('^> ', re.MULTILINE).split(code):
         for cmd, expect in re.findall('(.*?)\n([\w\W]*)$', block):
             yield cmd, expect
 
@@ -19,12 +19,12 @@ def extract(code):
 def run(code):
     interp = Interpreter()
     repl = REPL(interp)
-
+    errors = []
     for cmd, expect in extract(code):
-        if not cmd.strip():
+        if not strip_comments(cmd).strip():
             print
             continue
-        print ':-', cmd
+        print yellow % '> %s' % cmd
         sys.stdout = x = StringIO()
         try:
             repl.onecmd(cmd)
@@ -35,10 +35,16 @@ def run(code):
         if strip_comments(expect) != strip_comments(got):
             print green % expect
             print red % got
+            errors.append(cmd, expect, got)
         else:
             print x.getvalue().rstrip()
         print
 
+    if not errors:
+        print green % 'PASS!'
+    else:
+        print red % '%s errors' % len(errors)
+    print
 
 if __name__ == '__main__':
     for filename in sys.argv[1:]:
index 50f1824f70e5cc838f3322dbec7582938b41ce8f..6c0cc9b0f8bb6a50f4930433fd98faeb34260843 100644 (file)
@@ -122,7 +122,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, AggregatorError, DynaCompilerError
-
+from stdlib import todyna
 
 class Rule(object):
     def __init__(self, idx):
@@ -235,6 +235,10 @@ class Interpreter(object):
         if nullary:
             print >> out
         for x in others:
+
+            if x.startswith('$rule/'):
+                continue
+
             y = str(self.chart[x])   # skip empty chart
             if y:
                 print >> out, y
@@ -249,12 +253,52 @@ class Interpreter(object):
         print >> out
         print >> out, 'Errors'
         print >> out, '======'
+
+        I = defaultdict(lambda: defaultdict(list))
+        E = defaultdict(lambda: defaultdict(list))
         for item, (val, es) in self.error.items():
-            print >> out,  'because %r is %s:' % (item, _repr(val))
             for e, h in es:
-                if h is not None:
-                    r = h.rule
-                    print >> out, '    %s\n        in rule %s\n            %s' % (e, r.span, r.src)
+                if h is None:
+                    I[item.fn][type(e)].append((e, item, val))
+                else:
+                    E[h.rule][type(e)].append((e, item, val))
+
+        # aggregation errors
+        for r in I:
+            print >> out, 'Error(s) aggregating %s:' % r
+            for etype in I[r]:
+                print >> out, '  %s:' % etype.__name__
+                for i, (e, item, value) in enumerate(sorted(I[r][etype])):                       # todo: probably don't want to show ten million errors
+                    if i >= 5:
+                        print >> out, '    %s more ...' % (len(I[r][etype]) - i)
+                        break
+                    print >> out, '    when `%s` = %s' % (item, _repr(value))
+                    print >> out, '      %s' % (e)
+                print >> out
+
+        # errors pertaining to rules
+        for r in E:
+            print >> out, 'Error(s) in rule:', r.span
+            print >> out
+            for line in r.src.split('\n'):
+                print >> out, '   ', line
+            print >> out
+            for etype in E[r]:
+                print >> out, '  %s:' % etype.__name__
+                for i, (e, item, value) in enumerate(sorted(E[r][etype])):                       # todo: probably don't want to show ten million errors
+                    if i >= 5:
+                        print >> out, '    %s more ...' % (len(E[r][etype]) - i)
+                        break
+                    print >> out, '    when `%s` = %s' % (item, _repr(value))
+                    print >> out, '      %s' % (e)
+                print >> out
+
+#        for item, (val, es) in self.error.items():
+#            print >> out,  'because %r is %s:' % (item, _repr(val))
+#            for e, h in es:
+#                if h is not None:
+#                    r = h.rule
+#                    print >> out, '    %s\n        in rule %s\n            %s' % (e, r.span, r.src)
         print >> out
 
     def dump_rules(self):
@@ -300,6 +344,11 @@ class Interpreter(object):
         except KeyError:
             print 'Rule %s not found.' % idx
             return
+
+        # remove $rule
+        if hasattr(rule, 'item'):
+            self.delete_emit(rule.item, True, ruleix=None, variables=None)
+
         # Step 1: remove update handlers
         for u in rule.updaters:
             for xs in self.updaters.values():
@@ -309,10 +358,12 @@ class Interpreter(object):
         # Step 2: run initializer in delete mode
         if rule.init is not None:
             rule.init(emit=self.delete_emit)
-            # Step 3; go!
-            return self.go()
         # TODO: probably have to blast any memos from BC computations
 
+        # Step 3; go!
+        return self.go()
+
+
     def go(self):
         try:
             return self._go()
@@ -334,7 +385,7 @@ class Interpreter(object):
                 now = item.aggregator.fold()
 
             except AggregatorError as e:
-                error[item] = ('failed to aggregate item `%r` because %s' % (item, e), [(e, None)])
+                error[item] = (None, [(e, None)])
 
                 now = self.build('$error/0')   # XXX: should go an agenda or run delete?
                 changed[item] = now
@@ -342,7 +393,7 @@ class Interpreter(object):
                 continue
 
             except (ZeroDivisionError, TypeError, KeyboardInterrupt, NotImplementedError) as e:
-                error[item] = ('failed to aggregate %r' % item.aggregator, [(e, None)])
+                error[item] = (None, [(e, None)])
 
                 now = self.build('$error/0')   # XXX: should go an agenda or run delete?
                 changed[item] = now
@@ -481,8 +532,6 @@ class Interpreter(object):
         """
         assert os.path.exists(filename)
 
-
-
         env = imp.load_source('dynamically_loaded_module', filename)
 
         if path(filename + '.anf').exists():       # XXX: should have codegen provide this in plan.py
@@ -536,6 +585,21 @@ class Interpreter(object):
             for e in emits:
                 self.emit(*e, delete=False)
 
+        # ------ $rule for fun and profit -------
+        interp = self
+        def rule(ix, *a):
+            fn = '$rule/%s' % (len(a) + 1)
+            if interp.agg_name[fn] is None:
+                interp.new_fn(fn, ':=')
+            item = interp.build(fn, ix, *a)
+            interp.emit(item, True, ruleix=None, variables=None, delete=False)
+            return item
+        for i in new_rules:
+            r = self.rules[i]
+            agg, head, evals, unifs, result = r.anf[2:]
+            r.item = rule(i, r.src, todyna([head, agg, result, evals, unifs]), r.init, r.query)
+        #-----------------------------------------
+
         return self.go()
 
     def dynac(self, filename):
index 1653b2d54148df90f35a355c9d569e730bfa409d..358e9b8a19fbd22bd79471e64d43c248477580fa 100644 (file)
@@ -1,6 +1,6 @@
 from cStringIO import StringIO
 from utils import parse_sexpr
-from stdlib import todynalist
+from stdlib import todyna
 
 
 class sexpr(object):
@@ -33,20 +33,14 @@ class sexpr(object):
         def obj(*a):
             fn = '%s/%s' % (name, len(a))
             if interp.agg_name[fn] is None:
-                interp.new_fn(fn, ':=')
+                interp.new_fn(fn, '=')
             return interp.build(fn, *a)
 
-        def t(xs):
-            if isinstance(xs, basestring):
-                return xs
-            else:
-                return todynalist([t(x) for x in xs])
-
         contents = file(filename).read()
 
         for i, x in enumerate(parse_sexpr(contents)):
             interp.emit(obj(i),
-                        t(x),
+                        todyna(x),
                         ruleix=None,
                         variables=None,
                         delete=False)
index add8c478e2e4da7f844470e6da4b75282d7bda3b..6509e5b8f6459480a83db8344264a27d99abceec 100644 (file)
@@ -28,7 +28,10 @@ def infer_edges(interp):
 
     for r in interp.rules.values():
         if r.init is not None:
-            r.init(emit=_emit)
+            try:
+                r.init(emit=_emit)
+            except (TypeError, ValueError, AssertionError, ZeroDivisionError):
+                pass
         else:
             assert r.query is not None
 
index 302424b18d5f4704b81315444c3a14531862f1c8..67be4cc1e3dc2f0f8a72aed9e300ccd002b3dd1c 100644 (file)
@@ -89,8 +89,7 @@ def dig(head, visited, tail, groups, interp):
 
     return ['%s = %s' % (yellow % head, cyan % _repr(head.value))] \
         + ['|'] \
-        + branch(contribs) #\
-#        + ['']
+        + branch(contribs)
 
 
 def branch(xs):
@@ -135,9 +134,9 @@ class Crux(object):
     def format(self):
         rule = self.rule
         #src = rule.src.replace('\n',' ').strip()
-        graph = self.graph
         #user_vars = dict(defn.user_vars(self.vs.items()))
 
+        graph = self.graph
         side = [self.get_function(x) for x in graph.outputs if x != rule.anf.result and x != rule.anf.head]
 
         explode = ('%s %s %s' % (self.get_function(rule.anf.head)[1:],  # drop quote on head
@@ -159,7 +158,6 @@ class Crux(object):
 
         return lines
 
-
     def get_function(self, x):
         """
         String of symbolic representation of ``x``, a variable or function, in
index 93b5e2352fd8361b3c6ec82e40f1e09f1d05560f..0a9ee1436c84342da84ff81ee71a6c9f086ae680 100644 (file)
@@ -279,6 +279,8 @@ class REPL(cmd.Cmd, object):
         print 'Changes'
         print '======='
         for x, v in sorted(changed.items()):
+            if x.fn.startswith('$rule/'):
+                continue
             print '%s = %s.' % (x, _repr(v))
         print
 
index 0b626688cc3181c0bac2281b9021f98e57628b3a..336a5a5a5c19d1db82a2dfb5c1866ac0ecb74944 100644 (file)
@@ -27,31 +27,28 @@ def pycall(name, *args):
     x = eval(name)(*args)
     return todyna(x)
 
-def todyna(x):
-    if isinstance(x, (list, tuple, set, Counter)):
-        return todynalist(x)
-    return x
-
 def topython(x):
     if isinstance(x, Cons) or x is Nil:
         return x.aslist
     return x
 
-def todynalist(x):
+
+def todynalist(x):    # TODO: get rid of this.
+    return todyna(x)
+
+def todyna(x):
     if isinstance(x, (set, Counter)):
         x = list(x)
         x.sort()
-        return todynalist(x)
-    return _todynalist(list(x))
+        return todyna(x)
+    elif isinstance(x, (list, tuple)):
+        c = Nil
+        for y in reversed(x):
+            c = Cons(todyna(y), c)
+        return c
+    else:
+        return x
 
-def _todynalist(x):
-#    if not x:
-#        return Nil
-#    return Cons(x[0], _todynalist(x[1:]))
-    c = Nil
-    for y in reversed(x):
-        c = Cons(y, c)
-    return c
 
 def get(x, i):
     return x[i]
index b1299b35532abbf754b230ac354c8babbe23cff0..1cef4f7d8858a8f94cd3efd9156aab8b349ff03d 100644 (file)
@@ -24,6 +24,14 @@ def drepr(vs):
     return '{%s}' %  ', '.join('%s=%s' % (k, _repr(v)) for k,v in vs.iteritems())
 
 
+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
+                 if name.startswith('u') and not name.startswith('u_'))
+
+
 # interactive IPython shell
 ip = InteractiveShellEmbed(banner1 = 'Dropping into IPython\n')
 
index 5e3313cf144fc8f8211cd063c370e764d50245f7..039fcd54d4f872afc431718048b441960321661e 100644 (file)
@@ -1,32 +1,50 @@
-:- a += 1.
-=============
-a := 1
+> a += 1.
 
-:- b += 1.
-=============
-b := 1
+Changes
+=======
+a = 1.
 
-:- a += 1.
-=============
-a := 2
+> b += 1.
 
-:- rules
+Changes
+=======
+b = 1.
 
-0: a += 1.
+> a += 1.
+
+Changes
+=======
+a = 2.
+
+> rules
+
+Rules
+=====
+  0: a += 1.
   1: b += 1.
   2: a += 1.
 
-:- sol
+> sol
 
 Solution
 ========
-a := 2
-b := 1
+a = 2.
+b = 1.
+
+> retract_rule 0
+
+Changes
+=======
+a = 1.
+
+> retract_rule 1
+
+Changes
+=======
+b = null.
 
-:- retract_rule 0
-:- retract_rule 1
-:- sol
+> sol
 
 Solution
 ========
-a := 1
+a = 1.