]> hydra-www.ietfng.org Git - dyna2/commitdiff
Stabilize output of `bag=` by sorting elements and properly handle null.
authorTim Vieira <tim.f.vieira@gmail.com>
Sat, 13 Jul 2013 03:14:00 +0000 (23:14 -0400)
committerTim Vieira <tim.f.vieira@gmail.com>
Sat, 13 Jul 2013 03:14:00 +0000 (23:14 -0400)
Uninitialized rules are kept around and appear as errors in the error dump,
until initialization succeeds.

Fixed sort order for true and false.

src/Dyna/Backend/Python/aggregator.py
src/Dyna/Backend/Python/errors.py
src/Dyna/Backend/Python/interpreter.py
src/Dyna/Backend/Python/post/trace.py
src/Dyna/Backend/Python/repl.py
src/Dyna/Backend/Python/utils.py
test/repl/list.dynadoc

index f393bd9649288fc7bfff73f3adac5b31cd252988..ecbbbdf4eb398bd2ad9eb9cf91f838e048af3e89 100644 (file)
@@ -168,7 +168,7 @@ class and_equals(BAggregator):
                 if val is false:
                     return false
             return true
-            
+
 
 class set_equals(BAggregator):
     def fold(self):
@@ -180,8 +180,11 @@ class set_equals(BAggregator):
 
 class bag_equals(BAggregator):
     def fold(self):
-        from stdlib import todyna
-        return todyna(list(Counter(self).elements()))
+        if any(m > 0 for m in self.itervalues()):
+            from stdlib import todyna
+            x = list(Counter(self).elements())
+            x.sort()
+            return todyna(x)
 
 
 # map names to functions
index 030afdf01cfe6f9aef89fa2cbce63ae8f6382983..424b24b921a21f766eec05281b5bbe5a60eb5d7e 100644 (file)
@@ -14,20 +14,6 @@ class AggregatorError(Exception):
     pass
 
 
-class DynaInitializerException(Exception):
-    def __init__(self, exception, init):
-        rule = parse_attrs(init)['rule']
-        span = parse_attrs(init)['Span']
-        if span.startswith(dotdynadir / 'tmp'):
-            # don't show users tmp files create by the repl.
-            msg = '%r in ininitializer for rule\n    %s' % \
-                (exception, rule)
-        else:
-            msg = '%r in ininitializer for rule\n  %s\n        %s' % \
-                (exception, span, rule)
-        super(DynaInitializerException, self).__init__(msg)
-
-
 
 # TODO: we should package up all relevant state including compiler version,
 # codegen output, interpreter state (possibly without the chart -- because it
index d174450cb23f9154ed55e41097d4d8c6748a7b57..9943141a13f907086177739e7ef972558ef3d6a2 100644 (file)
@@ -86,10 +86,53 @@ 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 errors import crash_handler, AggregatorError, DynaCompilerError
 from stdlib import todyna
 
 
+def rule_error_context():
+    # Move to the frame where the exception occurred, which is often not the
+    # same frame where the exception was caught.
+    tb = sys.exc_info()[2]
+    if tb is not None:
+        while 1:
+            if not tb.tb_next:
+                break
+            tb = tb.tb_next
+        f = tb.tb_frame
+    else:                             # no exception occurred
+        f = sys._getframe()
+
+    # get the stack frames
+    stack = []
+    while f:
+        stack.append(f)
+        f = f.f_back
+
+    rule_frame = None
+    for frame in stack:
+        if frame.f_code.co_name == '_':   # find frame which looks like an update handler (it's name is at least '_')
+            rule_frame = frame
+
+            if False:
+                print 'Frame %s in %s at line %s' % (frame.f_code.co_name, frame.f_code.co_filename, frame.f_lineno)
+                for key, value in frame.f_locals.iteritems():
+                    print '%20s = %r' % (key, value)
+                print '\n'
+
+    if rule_frame is not None:
+        return dict(rule_frame.f_locals.items())
+    return {}
+
+
+def render_rule_context(rule, ctx, indent=''):
+    # TODO: include an undefined or unknown marker
+    # TODO: can highlight the expression which raise the error.
+    from post.trace import Crux
+    c = Crux(head=None, rule=rule, body=None, vs = ctx)
+    return ''.join(indent + line for line in c.format())
+
+
 class Rule(object):
 
     def __init__(self, index):
@@ -98,14 +141,21 @@ class Rule(object):
         self.updaters = []
         self.query = None
 
+        self._span = None
+        self._src = None
+
     @property
     def span(self):
-        span = parse_attrs(self.init or self.query)['Span']
-        return hide_ugly_filename(span)
+        if self._span is None:
+            span = parse_attrs(self.init or self.query)['Span']
+            self._span = hide_ugly_filename(span)
+        return self._span
 
     @property
     def src(self):
-        return strip_comments(parse_attrs(self.init or self.query)['rule'])
+        if self._src is None:
+            self._src = strip_comments(parse_attrs(self.init or self.query)['rule'])
+        return self._src
 
     def __repr__(self):
         return 'Rule(%s, %r)' % (self.index, self.src)
@@ -146,6 +196,8 @@ class Interpreter(object):
         self.time_step = 0
         self.files = []
 
+        self.uninitialized_rules = []
+
         # interpretor needs a place for it's temporary files.
         self.tmp = tmp = (dotdynadir / 'tmp' / str(os.getpid()))
         if tmp.exists():
@@ -210,11 +262,11 @@ class Interpreter(object):
         if out is None:
             out = sys.stdout
         # We only dump the error chart if it's non empty.
-        if not self.error:
+        if not self.error and not self.uninitialized_rules:
             return
         print >> out
-        print >> out, 'Errors'
-        print >> out, '======'
+        print >> out, red % 'Errors'
+        print >> out, red % '======'
 
         I = defaultdict(lambda: defaultdict(list))
         E = defaultdict(lambda: defaultdict(list))
@@ -254,20 +306,21 @@ class Interpreter(object):
                     print >> out, '      %s' % (e)
                     print >> out
 
-                    # TODO: include an undefined or unknown marker
-                    # TODO: can highlight the expression which raise the error.
-                    from post.trace import Crux
-
-                    c = Crux(head=None,
-                             rule=r,
-                             body=None,
-                             vs = dict(e.exception_frame))
-
-                    for line in c.format():
-                        print '       ', line
-
+                    print >> out, render_rule_context(r, e.exception_frame, indent='      ')
                     print >> out
 
+        # uninitialized rules
+        if self.uninitialized_rules:
+            print >> out, red % 'Uninitialized rules'
+            print >> out, red % '==================='
+            for e, r in self.uninitialized_rules:
+                print >> out, 'Failed to initialize rule:'
+                rule = self.rules[r]
+                print >> out, '   ', rule.src
+                print >> out, '  due to `%s`' % e
+                print >> out, render_rule_context(rule, e.exception_frame, indent='    ')
+                print >> out
+
         print >> out
 
     def dump_rules(self):
@@ -310,6 +363,11 @@ class Interpreter(object):
         if hasattr(rule, 'item'):
             self.delete_emit(rule.item, true, ruleix=None, variables=None)
 
+        uninits = [(e, r) for e, r in self.uninitialized_rules if r != idx]
+        if len(uninits) != len(self.uninitialized_rules):
+            self.uninitialized_rules = uninits
+            return []
+
         if rule.init is not None:
             # remove update handlers
             for u in rule.updaters:
@@ -400,7 +458,6 @@ class Interpreter(object):
                 # Thus, we can skip the delete-updates.
                 self.update_dispatcher(item, was, delete=True)
 
-
             assert now is not True or now is not False  # invalid dyna types.
 
             item.value = now
@@ -429,50 +486,9 @@ class Interpreter(object):
             try:
                 handler(item, val, emit=t_emit)
             except (TypeError, ZeroDivisionError, KeyboardInterrupt, OverflowError) as e:
-
-                import traceback
-
-                # Move to the frame where the exception occurred, which is often not the
-                # same frame where the exception was caught.
-                tb = sys.exc_info()[2]
-                if tb is not None:
-                    while 1:
-                        if not tb.tb_next:
-                            break
-                        tb = tb.tb_next
-                    f = tb.tb_frame
-                else:                             # no exception occurred
-                    f = sys._getframe()
-
-                # get the stack frames
-                stack = []
-                while f:
-                    stack.append(f)
-                    f = f.f_back
-                stack.reverse()
-
-                if 0:
-                    print 'Traceback:'
-                    print '=========='
-                    print traceback.format_exc()
-
-                    print 'Locals by frame:'
-                    print '================'
-                    for frame in stack:
-                        print 'Frame %s in %s at line %s' % (frame.f_code.co_name,
-                                                             frame.f_code.co_filename,
-                                                             frame.f_lineno)
-                        for key, value in frame.f_locals.iteritems():
-                            print '%20s = %r' % (key, value)
-
-                        print
-                        print
-
-
-                e.exception_frame = stack[-1].f_locals.items()
+                e.exception_frame = rule_error_context()
                 error.append((e, handler))
 
-
         if error:
             self.error[item] = (val, error)
             return
@@ -535,7 +551,7 @@ class Interpreter(object):
         self.agenda[item] = self.time_step
         self.time_step += 1
 
-    def do(self, filename, initialize=True):
+    def do(self, filename):
         """
         Compile, load, and execute new dyna rules.
 
@@ -560,10 +576,6 @@ class Interpreter(object):
                     ('peel', peel)]:
             setattr(env, k, v)
 
-        emits = []
-        def _emit(*args):
-            emits.append(args)
-
         for k, v in env.agg_decl.items():
             self.new_fn(k, v)
 
@@ -576,33 +588,11 @@ class Interpreter(object):
         for r, h in env.initializers:
             self.new_initializer(r, h)
             new_rules.add(r)
+            self.uninitialized_rules.append((None, r))
         self.new_rules = new_rules
 
-        try:
-            if initialize:
-                # run new initializers
-                for _, init in env.initializers:
-                    init(emit=_emit)
-
-        except (TypeError, ZeroDivisionError) as e:
-
-            # remove new rules
-            for r in new_rules:
-                self.retract_rule(r)
-            self.new_rules = set()
-
-            # if multiple rules were added we reject all of them (in order to
-            # avoid an bizarre parser state.
-            raise DynaInitializerException(e, init)
-
-        else:
-
-            # process emits
-            for e in emits:
-                self.emit(*e, delete=False)
-
-            # accept the new parser state
-            self.parser_state = env.parser_state
+        # accept the new parser state
+        self.parser_state = env.parser_state
 
         # ------ $rule for fun and profit -------
         interp = self
@@ -620,8 +610,33 @@ class Interpreter(object):
                 r.item = rule(i, r.src, todyna([head, agg, result, evals, unifs]), r.init, r.query)
         #-----------------------------------------
 
+        self.run_uninitialized()
+
         return self.go()
 
+    def run_uninitialized(self):
+
+        q = list(self.uninitialized_rules)
+        self.uninitialized_rules = []
+        while q:
+            (_, r) = q.pop()
+            try:
+                emits = []
+                def _emit(*args):
+                    emits.append(args)
+
+                self.rules[r].init(emit=_emit)
+
+            except (TypeError, ZeroDivisionError) as e:
+                e.exception_frame = rule_error_context()
+                self.uninitialized_rules.append((e, r))
+
+            else:
+                # process emits
+                for e in emits:
+                    self.emit(*e, delete=False)
+
+
     def dynac(self, filename):
         filename = path(filename)
         self.files.append(filename)
@@ -676,8 +691,6 @@ def main():
                         help='run post-processor.')
     parser.add_argument('--load', nargs='*',
                         help='run loaders.')
-#    parser.add_argument('--profile', action='store_true',
-#                        help='run profiler.')
 
     args = parser.parse_args()
 
@@ -737,11 +750,7 @@ def main():
 #            os.system('pkill snakeviz; snakeviz prof &')
 #            return
 
-        try:
-            interp.do(plan)
-        except DynaInitializerException as e:
-            print e
-            exit(1)
+        interp.do(plan)
 
     if args.load:
         for cmd in args.load:
index bec70c7e88685a00970305e8337cae3dc34398a1..23cd34afa7a3d87ba6e9192003157081c4c76e8a 100644 (file)
@@ -137,6 +137,10 @@ class Crux(object):
         try:
             return _repr(eval(x.replace('\\"', '"')))
         except (SyntaxError, NameError):
+            if x.startswith('_'):  # looks like an intermediate variable and it's not in vs
+                # TODO: we could double check that this is a temp variable by
+                # looking it up in anf.
+                return '?'
             return x
 
     def fvalue(self):
index 3b2874edf51f00cc009d8b518ffa57d35d08a43f..6757c44ffc831d3d046c65e4aae4478b07780dfc 100644 (file)
@@ -10,7 +10,7 @@ to help.
 import re, os, cmd, readline
 from utils import ip, lexer, subst, drepr, _repr, get_module
 from stdlib import topython, todyna
-from errors import DynaCompilerError, DynaInitializerException
+from errors import DynaCompilerError
 from config import dotdynadir
 from errors import show_traceback
 from interpreter import Interpreter, foo, none
@@ -258,11 +258,12 @@ class REPL(cmd.Cmd, object):
         if not line.endswith('.'):
             print "ERROR: Line doesn't end with period."
             return
+
         try:
             src = self.interp.dynac_code(line + '   %% repl line %s' % self.lineno)
             changed = self.interp.do(src)
 
-        except (DynaInitializerException, DynaCompilerError) as e:
+        except DynaCompilerError as e:
             print type(e).__name__ + ':'
             print e
             print 'new rule(s) were not added to program.'
index b84c1af235d57fd8b189bdbedc84953bbf0e25fe..3bcc46a622ac22de7b789097fb5111a4065520a2 100644 (file)
@@ -13,12 +13,24 @@ class _true(object):
         return True
     def __repr__(self):
         return 'true'
+    def __eq__(self, other):
+        return self is other
+    def __cmp__(self, other):
+        if other == 1:
+            return -1
+        return cmp(True, other)
 
 class _false(object):
     def __nonzero__(self):
         return False
     def __repr__(self):
         return 'false'
+    def __eq__(self, other):
+        return self is other
+    def __cmp__(self, other):
+        if other == 0:
+            return -1
+        return cmp(False, other)
 
 true = _true()
 false = _false()
index bb719934d44f66ece380c520ffc6cd19a7ab5622..78e2da5e6f37f03e47dd05a51fc74632ef335961 100644 (file)
@@ -1,10 +1,20 @@
 > x = cons(1, 2).
 
-DynaInitializerException:
-TypeError('Malformed list',) in ininitializer for rule
+> sol
+
+Solution empty.
+
+Errors
+======
+Uninitialized rules
+===================
+Failed to initialize rule:
     x = cons(1, 2).
-new rule(s) were not added to program.
+  due to `Malformed list`
+    x = &cons(1, 2).
+
 
+> retract_rule 0
 
 > s set= Y for Y in [3,2,1,[2,1],&f(1)].
 
@@ -134,5 +144,5 @@ testbool = false.
 
 Changes
 =======
-thingsbag = [1, 1, 2, "three", true].
-thingset = [1, 2, true, "three"].
+thingsbag = [true, 1, 1, 2, "three"].
+thingset = [true, 1, 2, "three"].