.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
-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 \
.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)
aggrs = S.fromList
[ "max=" , "min="
, "+=" , "*="
- , "and=" , "or=" , "&=" , "|="
+ , "&=" , "|="
, ":-"
, "="
, "majority=" , "mean="
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 "!="
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
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):
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):
'min=': min_equals,
'+=': plus_equals,
'*=': times_equals,
- 'and=': and_equals,
- 'or=': or_equals,
'&=': boolean_and_equals,
'|=': boolean_or_equals,
':-': boolean_or_equals,
from aggregator import aggregator
from term import Term
from utils import _repr
-
+from stdlib import equals
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
class DynaCompilerError(Exception):
- pass
+ def __init__(self, msg, filename):
+ self.filename = filename
+ super(DynaCompilerError, self).__init__(msg)
class AggregatorError(Exception):
# 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
"""
sys.excepthook = exception_handler
-# XXX: global state...
-crash_handler.interp = None
+
+crash_handler.hooks = []
def show_traceback(einfo=None):
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
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'])
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__
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'):
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.
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):
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:
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__':
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()
"""
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
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():
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):
"""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):
"""
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):
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:
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()
# 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:
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
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))
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):
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):
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):
except AttributeError:
return False
+# def __cmp__(self, other):
+# try:
+# return cmp(self.aslist, other.aslist)
+# except AttributeError:
+# return 1
+
Nil = _Nil()
from subprocess import Popen, PIPE
from config import dynahome, dotdynadir
from collections import namedtuple
+from cStringIO import StringIO
def _repr(x):
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, '<repl>', stderr)
- raise DynaCompilerError(stderr)
+ stderr = hide_ugly_filename(stderr)
+ raise DynaCompilerError(stderr, f)
+
+
+def hide_ugly_filename(x, replacement='<repl>'):
+ p = dotdynadir + '[a-z0-9/.]+\.dyna\S*'
+ return re.sub(p, replacement, x)
def lexer(term):
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
> 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
b = true.
c = true.
+
+
+> f(1,2). f(2,2).
+
+*ignore*
+
> sol
Solution
a.
b = true.
c = true.
+
+f/2
+===
+f(1,2).
+f(2,2).
\ No newline at end of file
-:- 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.
--- /dev/null
+> 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"].
+
└─ 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