From c11d61ca35a8e405a7057e25a01a859fa500e993 Mon Sep 17 00:00:00 2001 From: Tim Vieira Date: Tue, 9 Jul 2013 15:55:22 -0400 Subject: [PATCH] small improvements to handling booleans clean up aggregators crash handler hook (write repl lines) hide ugly file names in error messages more test cases added coverage target to makefile added a few more test cases. --- Makefile | 18 ++-- src/Dyna/Backend/Python/Backend.hs | 12 +-- src/Dyna/Backend/Python/aggregator.py | 61 ++++++------ src/Dyna/Backend/Python/chart.py | 4 +- src/Dyna/Backend/Python/errors.py | 12 ++- src/Dyna/Backend/Python/interpreter.py | 33 ++++--- src/Dyna/Backend/Python/load/sexpr.py | 28 ------ src/Dyna/Backend/Python/repl.py | 70 +++++++------- src/Dyna/Backend/Python/stdlib.py | 20 +++- src/Dyna/Backend/Python/term.py | 40 ++++---- src/Dyna/Backend/Python/utils.py | 43 ++++++++- test/app/ptb.dynadoc | 2 + test/repl/boolean-aggregators.dynadoc | 11 +++ test/repl/equals-errors.dynadoc | 37 ++++---- test/repl/list.dynadoc | 124 +++++++++++++++++++++++++ test/repl/trace.dynadoc | 14 +++ 16 files changed, 358 insertions(+), 171 deletions(-) create mode 100644 test/repl/list.dynadoc diff --git a/Makefile b/Makefile index 0b1cd38..4048471 100644 --- a/Makefile +++ b/Makefile @@ -31,12 +31,12 @@ fcomp: .PHONY: clean veryclean clean: rm -rf examples/*.dyna.*.plan \ - examples/*.dyna.*.planc \ - examples/*.dyna.plan.py \ - examples/*.dyna.plan.pyc \ - examples/*.dyna.*.out \ - examples/*.dyna.d \ - examples/*.hist + examples/*.dyna.*.planc \ + examples/*.dyna.plan.py \ + examples/*.dyna.plan.pyc \ + examples/*.dyna.*.out \ + examples/*.dyna.d \ + examples/*.hist rm -rf test/*/*.out rm -f tags TAGS veryclean: clean @@ -80,7 +80,6 @@ ghcbuild: -o dist/build/dyna/dyna \ -outputdir dist/build/dyna/dyna-tmp \ -main-is Dyna.Main.Driver Dyna.Main.Driver - mkdir -p dist/build/dyna-selftests mkdir -p dist/build/dyna-selftests/dyna-selftests-tmp ghc --make -isrc \ @@ -105,3 +104,8 @@ profbuild: .PHONY: tags TAGS tags TAGS: hasktags -b src + +coverage: + (coverage run run-doctests.py \ + ; coverage html --include 'src/*' -d coverage-report \ + ; gnome-open coverage-report/index.html) diff --git a/src/Dyna/Backend/Python/Backend.hs b/src/Dyna/Backend/Python/Backend.hs index d6423eb..9bef418 100644 --- a/src/Dyna/Backend/Python/Backend.hs +++ b/src/Dyna/Backend/Python/Backend.hs @@ -51,7 +51,7 @@ aggrs :: S.Set String aggrs = S.fromList [ "max=" , "min=" , "+=" , "*=" - , "and=" , "or=" , "&=" , "|=" + , "&=" , "|=" , ":-" , "=" , "majority=" , "mean=" @@ -182,8 +182,8 @@ constants = go go ("<=",2) = Just $ PDBS $ infixOp "<=" go ("<",2) = Just $ PDBS $ infixOp "<" - go ("=",2) = Just $ PDBS $ infixOp "==" - go ("==",2) = Just $ PDBS $ infixOp "==" + go ("=",2) = Just $ PDBS $ call "equals" [] + go ("==",2) = Just $ PDBS $ call "equals" [] go (">=",2) = Just $ PDBS $ infixOp ">=" go (">",2) = Just $ PDBS $ infixOp ">" go ("!=",2) = Just $ PDBS $ infixOp "!=" @@ -252,10 +252,8 @@ piterate vs = if length vs == 0 then "_" pdope_ :: S.Set DFunctAr -> DOpAMine PyDopeBS -> State Int (Doc e) pdope_ _ (OPIndr _ _) = dynacSorry "indirect evaluation not implemented" pdope_ _ (OPAsgn v val) = return $ pretty v <+> equals <+> pretty val -pdope_ _ (OPCheq v val) = return $ "if" <+> pretty v <+> "!=" - <+> pretty val <> ": continue" -pdope_ _ (OPCkne v val) = return $ "if" <+> pretty v <+> "==" - <+> pretty val <> ": continue" +pdope_ _ (OPCheq v val) = return $ "if not equals(" <> pretty v <> ", " <> pretty val <> "): continue" +pdope_ _ (OPCkne v val) = return $ "if equals(" <> pretty v <> ", " <> pretty val <> "): continue" pdope_ _ (OPPeel vs i f _) = return $ "try:" `above` (indent 4 $ tupledOrUnderscore vs diff --git a/src/Dyna/Backend/Python/aggregator.py b/src/Dyna/Backend/Python/aggregator.py index 3a5ca30..fd3f76b 100644 --- a/src/Dyna/Backend/Python/aggregator.py +++ b/src/Dyna/Backend/Python/aggregator.py @@ -148,25 +148,25 @@ class min_equals(BAggregator): if len(s): return min(s) -class maxwithkey_equals(max_equals): - def fold(self): - m = max_equals.fold(self) - self.key = None - if m is not None: - if not hasattr(m, 'aslist') or len(m.aslist) != 2: - raise AggregatorError("argmax expects a pair of values") - self.key = m.aslist[1] - return m.aslist[0] - -class minwithkey_equals(min_equals): - def fold(self): - m = min_equals.fold(self) - self.key = None - if m is not None: - if not hasattr(m, 'aslist') or len(m.aslist) != 2: - raise AggregatorError("argmin expects a pair of values") - self.key = m.aslist[1] - return m.aslist[0] +#class maxwithkey_equals(max_equals): +# def fold(self): +# m = max_equals.fold(self) +# self.key = None +# if m is not None: +# if not hasattr(m, 'aslist') or len(m.aslist) != 2: +# raise AggregatorError("argmax expects a pair of values") +# self.key = m.aslist[1] +# return m.aslist[0] + +#class minwithkey_equals(min_equals): +# def fold(self): +# m = min_equals.fold(self) +# self.key = None +# if m is not None: +# if not hasattr(m, 'aslist') or len(m.aslist) != 2: +# raise AggregatorError("argmin expects a pair of values") +# self.key = m.aslist[1] +# return m.aslist[0] class plus_equals(BAggregator): @@ -181,18 +181,17 @@ class times_equals(BAggregator): if len(s): return reduce(operator.mul, s) -class and_equals(BAggregator): - def fold(self): - s = [k for k, m in self.iteritems() if m > 0] - if len(s): - return reduce(lambda x,y: x and y, s) - -class or_equals(BAggregator): - def fold(self): - s = [k for k, m in self.iteritems() if m > 0] - if len(s): - return reduce(lambda x,y: x or y, s) +#class and_equals(BAggregator): +# def fold(self): +# s = [k for k, m in self.iteritems() if m > 0] +# if len(s): +# return reduce(lambda x,y: x and y, s) +#class or_equals(BAggregator): +# def fold(self): +# s = [k for k, m in self.iteritems() if m > 0] +# if len(s): +# return reduce(lambda x,y: x or y, s) class boolean_or_equals(BAggregator): def fold(self): @@ -232,8 +231,6 @@ defs = { 'min=': min_equals, '+=': plus_equals, '*=': times_equals, - 'and=': and_equals, - 'or=': or_equals, '&=': boolean_and_equals, '|=': boolean_or_equals, ':-': boolean_or_equals, diff --git a/src/Dyna/Backend/Python/chart.py b/src/Dyna/Backend/Python/chart.py index a2b9c2b..8e8df05 100644 --- a/src/Dyna/Backend/Python/chart.py +++ b/src/Dyna/Backend/Python/chart.py @@ -2,7 +2,7 @@ from collections import defaultdict from aggregator import aggregator from term import Term from utils import _repr - +from stdlib import equals class Chart(object): @@ -80,7 +80,7 @@ class Chart(object): yield term, term.args, term.value else: for term in candidates: - if term.value == val: + if equals(term.value, val): yield term, term.args, term.value def insert(self, args): # TODO: rename diff --git a/src/Dyna/Backend/Python/errors.py b/src/Dyna/Backend/Python/errors.py index 7d31fb4..b775d1b 100644 --- a/src/Dyna/Backend/Python/errors.py +++ b/src/Dyna/Backend/Python/errors.py @@ -5,7 +5,9 @@ from config import dotdynadir class DynaCompilerError(Exception): - pass + def __init__(self, msg, filename): + self.filename = filename + super(DynaCompilerError, self).__init__(msg) class AggregatorError(Exception): @@ -45,8 +47,8 @@ def exception_handler(etype, evalue, tb): # chart -- because it might be too big to email); input to repl. # This should all go into a tarball. - if crash_handler.interp is not None: - crash_handler.interp() + for hook in crash_handler.hooks: + hook() print 'FATAL ERROR (%s): %s' % (etype.__name__, evalue) print 'Crash log available %s' % crashreport.name @@ -58,8 +60,8 @@ def crash_handler(): """ sys.excepthook = exception_handler -# XXX: global state... -crash_handler.interp = None + +crash_handler.hooks = [] def show_traceback(einfo=None): diff --git a/src/Dyna/Backend/Python/interpreter.py b/src/Dyna/Backend/Python/interpreter.py index e14fe4d..7f65a2c 100644 --- a/src/Dyna/Backend/Python/interpreter.py +++ b/src/Dyna/Backend/Python/interpreter.py @@ -116,7 +116,7 @@ import load, post from term import Term, Cons, Nil from chart import Chart from utils import ip, red, green, blue, magenta, yellow, parse_attrs, \ - ddict, dynac, read_anf, strip_comments, _repr + ddict, dynac, read_anf, strip_comments, _repr, hide_ugly_filename from prioritydict import prioritydict from config import dotdynadir @@ -132,7 +132,8 @@ class Rule(object): self.query = None @property def span(self): - return parse_attrs(self.init or self.query)['Span'] + span = parse_attrs(self.init or self.query)['Span'] + return hide_ugly_filename(span) @property def src(self): return strip_comments(parse_attrs(self.init or self.query)['rule']) @@ -254,7 +255,7 @@ class Interpreter(object): E[h.rule][type(e)].append((e, item, val)) # aggregation errors - for r in I: + for r in sorted(I, key=lambda r: r.index): print >> out, 'Error(s) aggregating %s:' % r for etype in I[r]: print >> out, ' %s:' % etype.__name__ @@ -266,7 +267,7 @@ class Interpreter(object): print >> out # errors pertaining to rules - for r in E: + for r in sorted(E, key=lambda r: r.index): print >> out, 'Error(s) in rule:', r.span print >> out for line in r.src.split('\n'): @@ -505,10 +506,6 @@ class Interpreter(object): item.aggregator.inc(val, ruleix, variables) self.agenda[item] = time() # FIFO - def repl(self): - import repl - repl.REPL(self).cmdloop() - def do(self, filename, initialize=True): """ Compile, load, and execute new dyna rules. @@ -595,12 +592,10 @@ class Interpreter(object): def dynac(self, filename): filename = path(filename) self.files.append(filename) - out = self.tmp / filename.read_hexhash('sha1') + '.plan.py' # out = filename + '.plan.py' - - dynac(filename, out) self.files.append(out) + dynac(filename, out) return out def dynac_code(self, code): @@ -675,9 +670,6 @@ def main(): args.source.copy(plan) else: - #plan = args.source + '.plan.py' - #interp.dynac(args.source, plan) - try: plan = interp.dynac(args.source) except DynaCompilerError as e: @@ -718,7 +710,18 @@ def main(): interp.dump_charts(args.output) # should be a post-processor if args.interactive or not args.source: - interp.repl() + from repl import REPL + repl = REPL(interp) + + def repl_crash(): + # all files the interpreter generated + with file(dotdynadir / 'crash-repl.log', 'wb') as f: + for line in repl.lines: + print >> f, line + + crash_handler.hooks.append(repl_crash) + + repl.cmdloop() if __name__ == '__main__': diff --git a/src/Dyna/Backend/Python/load/sexpr.py b/src/Dyna/Backend/Python/load/sexpr.py index 358e9b8..c02222b 100644 --- a/src/Dyna/Backend/Python/load/sexpr.py +++ b/src/Dyna/Backend/Python/load/sexpr.py @@ -45,31 +45,3 @@ class sexpr(object): variables=None, delete=False) - -# TODO: maybe really big terms should have a pretty printer -def pretty(t, initialindent=0): - "Pretty print tree as a tabbified s-expression." - f = StringIO() - out = f.write - def pp(t, indent=initialindent, indentme=True): - if indentme: - out(' '*indent) - if isinstance(t, basestring): # base case - return out('"%s"' % t) - if len(t) == 1: - if t[0]: - pp('"%s"' % t[0], indent, indentme) - return - label, children = t[0], t[1:] - label = '"%s"' % label - assert isinstance(label, basestring) - out('&t(%s, ' % label) - n = len(children) - for i, child in enumerate(children): - pp(child, indent + len(label) + 5, i != 0) # first child already indented - if i != n-1: # no newline after last child - out(',\n') - out(')') - pp(t) - out('\n') - return f.getvalue() diff --git a/src/Dyna/Backend/Python/repl.py b/src/Dyna/Backend/Python/repl.py index 6133c45..c3c19df 100644 --- a/src/Dyna/Backend/Python/repl.py +++ b/src/Dyna/Backend/Python/repl.py @@ -8,7 +8,7 @@ to help. """ import os, cmd, readline -from utils import dynac, ip, lexer, subst, drepr, _repr, get_module +from utils import ip, lexer, subst, drepr, _repr, get_module from stdlib import topython, todyna from errors import DynaCompilerError, DynaInitializerException from config import dotdynadir @@ -29,6 +29,7 @@ class REPL(cmd.Cmd, object): f.write('') readline.read_history_file(hist) self.lineno = 0 + self.lines = [] # create help routines based on doc string. for x, v in REPL.__dict__.iteritems(): @@ -111,6 +112,7 @@ class REPL(cmd.Cmd, object): been interpreted. If you want to modify the input line before execution (for example, variable substitution) do it here. """ + self.lines.append(line) return line def postcmd(self, stop, line): @@ -127,11 +129,11 @@ class REPL(cmd.Cmd, object): """Do nothing on empty input line""" pass -# def do_ip(self, _): -# """ -# Development tool. Jump into an interactive python shell. -# """ -# ip() + def do_ip(self, _): + """ + Development tool. Jump into an interactive python shell. + """ + ip() def do_debug(self, line): """ @@ -142,19 +144,15 @@ 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(self.interp.dynac(filename)) -# except DynaCompilerError as e: -# print e -# else: -# self._changed(changed) + def do_dynac(self, line): + try: + src = self.interp.dynac_code(line) # might raise DynaCompilerError + except DynaCompilerError as e: + src = e.filename + print e + finally: + print 'opening file %s' % src + os.system('emacs -nw %s' % src) def _query(self, q): @@ -261,7 +259,7 @@ class REPL(cmd.Cmd, object): print "ERROR: Line doesn't end with period." return try: - src = self.interp.dynac_code(line) # might raise DynaCompilerError + src = self.interp.dynac_code(line + ' %% repl line %s' % self.lineno) changed = self.interp.do(src) except (DynaInitializerException, DynaCompilerError) as e: @@ -289,22 +287,6 @@ class REPL(cmd.Cmd, object): print '%s = %s.' % (x, _repr(x.value)) print -# def _changed_subscriptions(self, changed): -# -# # TODO: this doesn't show changes - it redumps everything. -# -# if not changed: -# return -# for x, _ in sorted(changed.items()): -# if x.fn == '$subscribed/2': -# [i, q] = x.args -# if x.value: -# print '%s: %s' % (i, q) -# for result in x.value: -# print ' ', _repr(result.value), 'where', drepr(dict(result.variables)) -# print -# self.interp.dump_errors() - def cmdloop(self, _=None): try: super(REPL, self).cmdloop() @@ -353,6 +335,22 @@ class REPL(cmd.Cmd, object): # print ' ', _repr(result.value), 'where', drepr(dict(result.variables)) # print +# def _changed_subscriptions(self, changed): +# +# # TODO: this doesn't show changes - it redumps everything. +# +# if not changed: +# return +# for x, _ in sorted(changed.items()): +# if x.fn == '$subscribed/2': +# [i, q] = x.args +# if x.value: +# print '%s: %s' % (i, q) +# for result in x.value: +# print ' ', _repr(result.value), 'where', drepr(dict(result.variables)) +# print +# self.interp.dump_errors() + def do_help(self, line): mod = line.split() if len(mod) <= 1: diff --git a/src/Dyna/Backend/Python/stdlib.py b/src/Dyna/Backend/Python/stdlib.py index 5e8e922..921d043 100644 --- a/src/Dyna/Backend/Python/stdlib.py +++ b/src/Dyna/Backend/Python/stdlib.py @@ -1,6 +1,7 @@ import re from term import Term, Cons, Nil from collections import Counter +from utils import pretty, pretty_print try: from numpy import log, exp, sqrt @@ -12,6 +13,22 @@ except ImportError: # XXX: should probably issue a warning return _random() * (b - a) + a +def equals(x,y): + """ + My work around for discrepency in bool equality True==1 and False==0. + + >>> equals(True, 1) + False + + >>> equals(1, 1.0) + True + """ + if isinstance(x, bool) or isinstance(y, bool): + return type(x) == type(y) and x == y + else: + return x == y + + _range = range def range(*x): return todyna(_range(*x)) @@ -20,8 +37,7 @@ def split(s, delim='\s+'): return todynalist(re.split(delim, s)) def crash(): - class Crasher(Exception): - pass + class Crasher(Exception): pass raise Crasher('Hey, you asked for it!') def pycall(name, *args): diff --git a/src/Dyna/Backend/Python/term.py b/src/Dyna/Backend/Python/term.py index 0b95982..47c0df2 100644 --- a/src/Dyna/Backend/Python/term.py +++ b/src/Dyna/Backend/Python/term.py @@ -35,23 +35,23 @@ class Term(object): return fn return '%s(%s)' % (fn, ','.join(map(_repr, self.args))) - def __getstate__(self): - return (self.fn, self.args, self.value, self.aggregator) +# def __getstate__(self): +# return (self.fn, self.args, self.value, self.aggregator) - def __setstate__(self, state): - (self.fn, self.args, self.value, self.aggregator) = state +# def __setstate__(self, state): +# (self.fn, self.args, self.value, self.aggregator) = state - def __add__(self, _): - raise TypeError("Can't subtract terms.") +# def __add__(self, _): +# raise TypeError("Can't subtract terms.") - def __sub__(self, _): - raise TypeError("Can't add terms.") +# def __sub__(self, _): +# raise TypeError("Can't add terms.") - def __mul__(self, _): - raise TypeError("Can't multiply terms.") +# def __mul__(self, _): +# raise TypeError("Can't multiply terms.") - def __div__(self, _): - raise TypeError("Can't divide terms.") +# def __div__(self, _): +# raise TypeError("Can't divide terms.") class Cons(Term): @@ -90,11 +90,11 @@ class Cons(Term): def __hash__(self): return hash(tuple(self.aslist)) - def __cmp__(self, other): - try: - return cmp(self.aslist, other.aslist) - except AttributeError: - return 1 +# def __cmp__(self, other): +# try: +# return cmp(self.aslist, other.aslist) +# except AttributeError: +# return class _Nil(Term): @@ -121,6 +121,12 @@ class _Nil(Term): except AttributeError: return False +# def __cmp__(self, other): +# try: +# return cmp(self.aslist, other.aslist) +# except AttributeError: +# return 1 + Nil = _Nil() diff --git a/src/Dyna/Backend/Python/utils.py b/src/Dyna/Backend/Python/utils.py index 1cef4f7..affd0bc 100644 --- a/src/Dyna/Backend/Python/utils.py +++ b/src/Dyna/Backend/Python/utils.py @@ -4,6 +4,7 @@ from path import path from subprocess import Popen, PIPE from config import dynahome, dotdynadir from collections import namedtuple +from cStringIO import StringIO def _repr(x): @@ -80,10 +81,13 @@ def dynac(f, out, anf=None, compiler_args=()): stdout, stderr = p.communicate() if p.returncode: assert not stdout.strip(), [stdout, stderr] - # hide our temporary file's ugly sha1 file names from users. - ugly_file_name = dotdynadir + '[a-z0-9/.]+\.dyna\S*' - stderr = re.sub(ugly_file_name, '', stderr) - raise DynaCompilerError(stderr) + stderr = hide_ugly_filename(stderr) + raise DynaCompilerError(stderr, f) + + +def hide_ugly_filename(x, replacement=''): + p = dotdynadir + '[a-z0-9/.]+\.dyna\S*' + return re.sub(p, replacement, x) def lexer(term): @@ -175,6 +179,37 @@ def parse_sexpr(e): return es +def pretty_print(t): + print pretty(t) + +def pretty(t, initialindent=0): + "Pretty print tree as a tabbified s-expression." + f = StringIO() + out = f.write + def pp(t, indent=initialindent, indentme=True): + if indentme: + out(' '*indent) + if isinstance(t, basestring): # base case + return out('%s' % t) + if len(t) == 1: + if t[0]: + pp('%s' % t[0], indent, indentme) + return + label, children = t[0], t[1:] + label = '%s' % label + assert isinstance(label, basestring) + out('(%s ' % label) + n = len(children) + for i, child in enumerate(children): + pp(child, indent + len(label) + 2, i != 0) # first child already indented + if i != n-1: # no newline after last child + out('\n') + out(')') + pp(t) + out('\n') + return f.getvalue() + + class ANF(namedtuple('ANF', 'lines ruleix agg head evals unifs result')): pass diff --git a/test/app/ptb.dynadoc b/test/app/ptb.dynadoc index 30cd48c..40d17a3 100644 --- a/test/app/ptb.dynadoc +++ b/test/app/ptb.dynadoc @@ -152,3 +152,5 @@ ntrees = 23. > query errors(S) errors(12) = ["(ROOT (S (NP Laura) (VP (VP (V (V say) -s) (SBAR that (S (NP George) (VP (Modal might) (VP (V sleep)))))) (PP (P on) (NP (Det the) (N floor))))) !)", "(ROOT (S (NP Laura) (VP (V (V say) -s) (SBAR that (S (NP George) (VP (VP (Modal might) (VP (V sleep))) (PP (P on) (NP (Det the) (N floor)))))))) !)"]. errors(21) = ["(ROOT (S (NP (NP (Det the) (N (Adj (Adj fine) (@Adj and (Adj blue))) (N woman))) (@NP and (NP (Det every) (N man)))) (VP (VP (Modal must) (VP (V have) (VP (V (V eat) -ed) (NP (Det two) (N (N sandwich) -s))))) (@VP and (VP (VP (V (V sleep) -ed)) (PP (P on) (NP (Det the) (N floor))))))) .)", "(ROOT (S (NP (NP (Det the) (N (Adj (Adj fine) (@Adj and (Adj blue))) (N woman))) (@NP and (NP (Det every) (N man)))) (VP (VP (Modal must) (VP (V have) (VP (VP (V (V eat) -ed) (NP (Det two) (N (N sandwich) -s))) (@VP and (VP (V (V sleep) -ed)))))) (PP (P on) (NP (Det the) (N floor))))) .)"]. + +%> *resume* \ No newline at end of file diff --git a/test/repl/boolean-aggregators.dynadoc b/test/repl/boolean-aggregators.dynadoc index 4c98d5f..fbfc66c 100644 --- a/test/repl/boolean-aggregators.dynadoc +++ b/test/repl/boolean-aggregators.dynadoc @@ -72,6 +72,12 @@ Changes b = true. c = true. + + +> f(1,2). f(2,2). + +*ignore* + > sol Solution @@ -79,3 +85,8 @@ Solution a. b = true. c = true. + +f/2 +=== +f(1,2). +f(2,2). \ No newline at end of file diff --git a/test/repl/equals-errors.dynadoc b/test/repl/equals-errors.dynadoc index adaa7e8..3da43e5 100644 --- a/test/repl/equals-errors.dynadoc +++ b/test/repl/equals-errors.dynadoc @@ -1,36 +1,41 @@ -:- a = 2. -============= -a := 2 +> a = 2. +Changes +======= +a = 2. % It's ok to assign 2 again. - -:- a = 2. - +> a = 2. % but you can't set it to a different value, such as 1. +> a = 1. -:- a = 1. -============= -a := $error +Changes +======= +a = $error. -:- sol +> sol Solution ======== -a => $error. - +a = $error. Errors ====== -because a is "failed to aggregate item `a` because `=` got conflicting values [1, 2]": +Error(s) aggregating a/0: + AggregatorError: + `a`: `=` got conflicting values [1, 2] + +> retract_rule 2 -:- retract_rule 2 +Changes +======= +a = 2. -:- sol +> sol Solution ======== -a => 2. +a = 2. diff --git a/test/repl/list.dynadoc b/test/repl/list.dynadoc new file mode 100644 index 0000000..fe8b061 --- /dev/null +++ b/test/repl/list.dynadoc @@ -0,0 +1,124 @@ +> x = cons(1, 2). + +DynaInitializerException: +TypeError('Malformed list',) in ininitializer for rule + x = cons(1, 2). +new rule(s) were not added to program. + + +> s set= Y for Y in [3,2,1,[2,1],&f(1)]. + +Changes +======= +s = [1, 2, 3, [2, 1], f(1)]. + +> foo = 1 in s. + +Changes +======= +foo = true. + +% check comparison (sort order) of list and non-list. +> f(s). f(1). f([]). + +Changes +======= +f(1) = true. +f([1, 2, 3, [2, 1], f(1)]) = true. +f([]) = true. + +% test for empty list +> query 1 in nil + +1 in nil = false. + +> nothing set= X for X in []. + + + + +> g([1,2]). + +Changes +======= +g([1, 2]) = true. + +> a := [1,2]. + +Changes +======= +a = [1, 2]. + +> goal(X) := g([1|X]). + +Changes +======= +goal([2]) = true. + + +% list contains +> b := &f("a") in [1,2,&f("a"),3]. +| c := 2 in [1,2,3]. +| d := 4 in [1,2,3]. + +Changes +======= +b = true. +c = true. +d = false. + + +% iteration of a complex list +> things set= X for X in [1,[2,2],[3,4]]. + +Changes +======= +things = [1, [2, 2], [3, 4]]. + +% unpack structure requiring a check +> d(&X) := true for [X,X] in [1,[2,2],[3,4],[4,4]]. + +Changes +======= +d(2) = true. +d(4) = true. + +% quote is important! or else we enumerate everything! +> foo(A) := true for &f(A) in [1,2,&f("a"),3]. +| +| % this one checks if the value of f(A) is in the list, (note: 1 == True, in python). +| goo(A) := true for f(A) in [1,2,&f("a"),3]. + + +Changes +======= +foo("a") = true. +goo(1) = true. +goo([1, 2, 3, [2, 1], f(1)]) = true. +goo([]) = true. + + +% unfortunately 1 == true and 0 == false in python so the following is true +> testbool := true in [1,2]. + +Changes +======= +testbool = true. + + +% fun with set= at bag= +> thingsbag bag= "three". +| thingsbag bag= 1. +| thingsbag bag= 1. +| thingsbag bag= 2. +| +| thingset set= "three". +| thingset set= 1. +| thingset set= 1. +| thingset set= 2. + +Changes +======= +thingsbag = [1, 1, 2, "three"]. +thingset = [1, 2, "three"]. + diff --git a/test/repl/trace.dynadoc b/test/repl/trace.dynadoc index 4444362..d203024 100644 --- a/test/repl/trace.dynadoc +++ b/test/repl/trace.dynadoc @@ -97,3 +97,17 @@ bar(10,10) = 220 └─ foo(10) = 11 | └─ continue as before (shared structure) + + +> x = [1,2,3]. + +Changes +======= +x = [1, 2, 3]. + +> trace x + +x = [1, 2, 3] +| +└─ = [1, 2, 3] + x = [1, 2, 3]. \ No newline at end of file -- 2.50.1