From: Tim Vieira Date: Sat, 13 Jul 2013 03:14:00 +0000 (-0400) Subject: Stabilize output of `bag=` by sorting elements and properly handle null. X-Git-Url: https://hydra-www.ietfng.org/gitweb/?a=commitdiff_plain;h=a63d297b455476f2aaeae419c8de0d2d996666f8;p=dyna2 Stabilize output of `bag=` by sorting elements and properly handle null. Uninitialized rules are kept around and appear as errors in the error dump, until initialization succeeds. Fixed sort order for true and false. --- diff --git a/src/Dyna/Backend/Python/aggregator.py b/src/Dyna/Backend/Python/aggregator.py index f393bd9..ecbbbdf 100644 --- a/src/Dyna/Backend/Python/aggregator.py +++ b/src/Dyna/Backend/Python/aggregator.py @@ -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 diff --git a/src/Dyna/Backend/Python/errors.py b/src/Dyna/Backend/Python/errors.py index 030afdf..424b24b 100644 --- a/src/Dyna/Backend/Python/errors.py +++ b/src/Dyna/Backend/Python/errors.py @@ -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 diff --git a/src/Dyna/Backend/Python/interpreter.py b/src/Dyna/Backend/Python/interpreter.py index d174450..9943141 100644 --- a/src/Dyna/Backend/Python/interpreter.py +++ b/src/Dyna/Backend/Python/interpreter.py @@ -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: diff --git a/src/Dyna/Backend/Python/post/trace.py b/src/Dyna/Backend/Python/post/trace.py index bec70c7..23cd34a 100644 --- a/src/Dyna/Backend/Python/post/trace.py +++ b/src/Dyna/Backend/Python/post/trace.py @@ -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): diff --git a/src/Dyna/Backend/Python/repl.py b/src/Dyna/Backend/Python/repl.py index 3b2874e..6757c44 100644 --- a/src/Dyna/Backend/Python/repl.py +++ b/src/Dyna/Backend/Python/repl.py @@ -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.' diff --git a/src/Dyna/Backend/Python/utils.py b/src/Dyna/Backend/Python/utils.py index b84c1af..3bcc46a 100644 --- a/src/Dyna/Backend/Python/utils.py +++ b/src/Dyna/Backend/Python/utils.py @@ -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() diff --git a/test/repl/list.dynadoc b/test/repl/list.dynadoc index bb71993..78e2da5 100644 --- a/test/repl/list.dynadoc +++ b/test/repl/list.dynadoc @@ -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"].