from collections import Counter
from utils import drepr, _repr, user_vars, isbool, true, false
from errors import AggregatorError
+from stdlib import todyna
class NoAggregatorError(Exception):
def fold(self):
if not self.empty():
- from stdlib import todyna
return todyna([b + (('$val', v),) for (v, b), cnt in self.iteritems() if cnt > 0])
class set_equals(BAggregator):
def fold(self):
- from stdlib import todyna
s = {x for x, m in self.iteritems() if m > 0}
if len(s):
return todyna(s)
class bag_equals(BAggregator):
def fold(self):
if any(m > 0 for m in self.itervalues()):
- from stdlib import todyna
x = list(Counter(self).elements())
x.sort()
return todyna(x)
def set_aggregator(self, agg):
self.agg_name = agg
for item in self.intern.values():
- assert item.value is None # shouldn't change aggregator when non-null.
+ assert item.value is None, [item, item.value, item.aggregator] # shouldn't change aggregator when non-null.
item.aggregator = self.new_aggregator(item)
def __repr__(self):
import os
-from IPython.external.path import path
+from utils import path
dotdynadir = path('~/.dyna').expand()
if not dotdynadir.exists():
import re, os, shutil, webbrowser
from collections import defaultdict
-from utils import dynac, read_anf
+from utils import read_anf, path
from config import dynahome
-from IPython.external.path import path
from warnings import warn
try:
print >> html, '<div id="dopamine-pane" style=""></div>'
print >> html, '<div id="update-handler-pane" style=""></div>'
+ from dynac import dynac
dynac(dynafile,
out = d / 'plan',
anf = d / 'anf',
from interpreter import Interpreter
from repl import REPL
from cStringIO import StringIO
-from utils import bold, red, green, yellow, strip_comments
+from utils import bold, red, green, yellow
def diff(expect, got):
yield '\n'.join(cmd).strip(), '\n'.join(expect).strip()
+def strip_comments(src):
+ return re.compile('%.*$', re.MULTILINE).sub('', src)
+
+def remove_color(x):
+ return re.sub('\033\[[0-9;]+m', '', x)
+
def clean(x):
# remove whitespace at end of line
# remove ansi color codes
- return re.compile('(\s*)$', re.MULTILINE).sub('', re.sub('\033\[\d+m', '', strip_comments(x)).strip())
+ return re.compile('(\s*)$', re.MULTILINE).sub('', remove_color(strip_comments(x))).strip()
def run(code, out=None):
--- /dev/null
+"""
+Interface to compiler.
+
+TODO:
+ - read parser state
+ - manage temp dir/files
+ - anf
+ - recompile rule should give same index
+
+"""
+
+import re, os
+from subprocess import Popen, PIPE
+
+from utils import path
+from config import dynahome, dotdynadir
+from hashlib import sha1
+from errors import DynaCompilerError
+from utils import hide_ugly_filename, span_to_src
+
+
+def dynac(f, out, anf=None, compiler_args=()):
+ """
+ Run compiler on file, ``f``, write results to ``out``. Raises
+ ``DynaCompilerError`` on failure.
+ """
+
+ f = path(f)
+ if not f.exists():
+ raise DynaCompilerError("File '%s' does not exist." % f)
+
+ cmd = ['%s/dist/build/dyna/dyna' % dynahome,
+ '-B', 'python', '-o', out, f]
+
+ if anf is None:
+ cmd += ['--dump-anf=' + out + '.anf']
+ else:
+ cmd += ['--dump-anf=' + anf]
+
+ cmd += compiler_args
+
+ p = Popen(cmd, stdout=PIPE, stderr=PIPE)
+
+ stdout, stderr = p.communicate()
+ if p.returncode:
+ assert not stdout.strip(), [stdout, stderr]
+ stderr = hide_ugly_filename(stderr, lambda m: '\n %s\n' % span_to_src(m.group(0)))
+ raise DynaCompilerError(stderr, f)
+
+
+class Compiler(object):
+
+ def __init__(self):
+ self.files = []
+ self.tmp = tmp = (dotdynadir / 'tmp' / str(os.getpid()))
+ if tmp.exists():
+ tmp.rmtree()
+ tmp.makedirs_p()
+
+ def dynac(self, filename):
+ """
+ Compile a file full of dyna code. Note: this routine does not pass along
+ parser_state.
+ """
+ filename = path(filename)
+ self.files.append(filename)
+ out = self.tmp / filename.read_hexhash('sha1') + '.plan.py'
+ #out = filename + '.plan.py'
+ self.files.append(out)
+ dynac(filename, out)
+ return out
+
+ def dynac_code(self, code, pstate):
+ "Compile a string of dyna code."
+ x = sha1()
+ x.update(pstate)
+ x.update(code)
+ dyna = self.tmp / ('%s.dyna' % x.hexdigest())
+ with file(dyna, 'wb') as f:
+ f.write(pstate) # include parser state if any.
+ f.write(code)
+ return self.dynac(dyna)
+
+ @staticmethod
+ def parser_state(bc, rix, agg, other):
+ # TODO: this is pretty hacky. XREF:parser-state
+ lines = [':-ruleix %d.' % rix]
+ for fn in bc:
+ [(fn, arity)] = re.findall('(.*)/(\d+)', fn)
+ lines.append(":-backchain '%s'/%s." % (fn, arity))
+ for fn, agg in agg.items():
+ [(fn, arity)] = re.findall('(.*)/(\d+)', fn)
+ if agg is not None:
+ lines.append(":-iaggr '%s'/%s %s." % (fn, arity, agg))
+ lines.extend(':-%s %s.' % (k,v) for k,v in other)
+ lines.append('\n')
+ return '\n'.join(lines)
+
+ @staticmethod
+ def read_parser_state(parser_state):
+ """
+ TODO: This is pretty hacky we should have the codegen produce something
+ easier to serialize/modify/unserialize. XREF:parser-state.
+ """
+ backchain = set()
+ ruleix = None
+ iaggr = {}
+ other = []
+ for k, v in re.findall('^:-\s*(\S+) (.*?)\s*\.$', parser_state, re.MULTILINE):
+ if k == 'backchain':
+ [(fn, arity)] = re.findall("'(.*?)'/(\d+)", v)
+ backchain.add('%s/%s' % (fn, arity))
+ elif k == 'iaggr':
+ [(fn, arity, agg)] = re.findall("'(.*?)'/(\d+)\s*(.*)", v)
+ iaggr['%s/%s' % (fn, arity)] = agg
+ elif k == 'ruleix':
+ ruleix = int(v)
+ else:
+ other.append((k,v))
+ return backchain, ruleix, iaggr, other
import re, os, sys, imp, traceback
from collections import defaultdict
-from hashlib import sha1
-from IPython.external.path import path
from term import Term, Cons, Nil, MapsTo, Error
from chart import Chart
-from utils import red, parse_attrs, dynac, read_anf, strip_comments, _repr, \
- hide_ugly_filename, true, false, parse_parser_state, magenta, indent
+from utils import red, parse_attrs, read_anf, _repr, hide_ugly_filename, \
+ true, false, magenta, indent, path
from prioritydict import prioritydict
from config import dotdynadir
from errors import rule_error_context, AggregatorError, DynaCompilerError
from stdlib import todyna
-
+from dynac import Compiler
#sys.setrecursionlimit(10000)
class Interpreter(object):
- def parser_state(self, ruleix=None):
- # TODO: this is pretty hacky. XREF:parser-state
- bc, _rix, agg, other = self.pstate
- if ruleix is None:
- if not self.rules:
- rix = 0
- else:
- rix = max(self.rules) + 1 # next available
- rix = max(rix, _rix)
- else:
- rix = ruleix # override rule index from pstate
- lines = [':-ruleix %d.' % rix]
- for fn in bc:
- [(fn, arity)] = re.findall('(.*)/(\d+)', fn)
- lines.append(":-backchain '%s'/%s." % (fn, arity))
- for fn, agg in agg.items():
- [(fn, arity)] = re.findall('(.*)/(\d+)', fn)
- lines.append(":-iaggr '%s'/%s %s." % (fn, arity, agg))
- lines.extend(':-%s %s.' % (k,v) for k,v in other)
- lines.append('\n')
- return '\n'.join(lines)
-
- def set_parser_state(self, x):
- self.pstate = (bc, _rix, _agg, _other) = x
- for fn in bc:
- if fn not in self._gbc: # new backchain declaration
- for r in self.rule_by_head[fn]:
- self.needs_recompile(r)
-
def __init__(self):
- # declarations
+ self.compiler = Compiler()
+ # parser state
self.agg_name = defaultdict(none)
- self.pstate = (set(), 0, {}, [])
- self.files = []
+ self.bc = set()
+ self.other = []
+ self.ruleix = 0
# rules
self.rules = {}
self.updaters = defaultdict(list)
self.agenda = prioritydict()
self.chart = foo(self.agg_name)
self.error = {}
+ self.changed = {}
# misc
self.time_step = 0
- # interpretor needs a place for it's temporary files.
- self.tmp = tmp = (dotdynadir / 'tmp' / str(os.getpid()))
- if tmp.exists():
- tmp.rmtree()
- tmp.makedirs_p()
# coarsening of the program shows which rules might depend on each other
self.coarse_deps = defaultdict(set)
self.rule_by_head = defaultdict(set)
emits.append((item, val, ruleix, variables, False))
errors = []
-
for handler in self._gbc[item.fn]:
try:
handler(*item.args, emit=t_emit)
anf[x.ruleix] = x
# update parser state
- self.set_parser_state(parse_parser_state(env.parser_state))
+ self.set_parser_state(env.parser_state)
for k, v in env.agg_decl.items():
self.new_fn(k, v)
return new_rules
+ def set_parser_state(self, x):
+ (bc, _rix, _agg, _other) = self.compiler.read_parser_state(x)
+ # update misc parser state
+ self.other = _other
+ # update backchain predicates
+ for fn in bc:
+ self.bc.add(fn)
+ if fn not in self._gbc: # new backchain declaration
+ for r in self.rule_by_head[fn]:
+ self.needs_recompile(r)
+ # update ruleix
+ if not self.rules:
+ self.ruleix = _rix
+ else:
+ self.ruleix = max(max(self.rules) + 1, _rix) # next available
+ # update fn->aggregator map
+ for fn, agg in _agg.items():
+ self.new_fn(fn, agg)
+
def recompile_rule(self, r):
"returns a plan, it's up to you to retract the old rule and load the plan"
- pstate = self.parser_state(ruleix=r.index) # override ruleix
- code = r.src
- x = sha1()
- x.update(pstate)
- x.update(code)
- dyna = self.tmp / ('%s.dyna' % x.hexdigest())
- with file(dyna, 'wb') as f:
- f.write(pstate)
- f.write(code)
- return self.dynac(dyna)
+ pstate = self.compiler.parser_state(self.bc, r.index, self.agg_name, self.other) # override ruleix
+ return self.compiler.dynac_code(r.src, pstate)
def needs_recompile(self, r):
self.retract_rule(r.index) # clears errors
# run to fixed point.
while self.recompile:
success = set()
- failed = set()
+ failure = set()
for r in list(self.recompile):
try:
r.plan = self.recompile_rule(r)
except DynaCompilerError as e:
- failed.add(r)
+ failure.add(r)
self.set_error(r, e)
else:
success.add(r)
self.clear_error(r)
- self.recompile = failed
+ self.recompile = failure
if not success:
break
for r in success:
self.rules[index] = rule = Rule(index)
rule.span = hide_ugly_filename(parse_attrs(init or query)['Span'])
- rule.src = strip_comments(parse_attrs(init or query)['rule'])
+ rule.src = parse_attrs(init or query)['rule']
rule.anf = anf
rule.head_fn = head_fn
# fix dependents
if head_fn not in self._gbc:
-
- # quick monkey patch assertion.
- def monkey(_, _args):
- assert False, '__getitem__ should never be called because' \
- ' `%s` should be backchained' % head_fn
- self.chart.__getitem__ = monkey
-
# retract and replan rules dependent on this predicate which is
# now backchained.
for d in self.rule_dep[head_fn]:
self.chart[rule.head_fn].set_aggregator(None)
if rule.head_fn in self.agg_name:
del self.agg_name[rule.head_fn]
- if rule.head_fn in self.pstate[2]:
- del self.pstate[2][rule.head_fn] # remove fn aggr def from parser state
return self.changed
Compile a file full of dyna code. Note: this routine does not pass along
parser_state.
"""
- filename = path(filename)
- self.files.append(filename)
-
- # TODO: crashlogs/amareshj:2013-07-01:22:26:54:13412.log -- file doesn't exist.
-
- out = self.tmp / filename.read_hexhash('sha1') + '.plan.py'
- #out = filename + '.plan.py'
- self.files.append(out)
- dynac(filename, out)
- return out
+ return self.compiler.dynac(filename)
def dynac_code(self, code):
"Compile a string of dyna code."
- pstate = self.parser_state()
- x = sha1()
- x.update(pstate)
- x.update(code)
- dyna = self.tmp / ('%s.dyna' % x.hexdigest())
- with file(dyna, 'wb') as f:
- f.write(pstate) # include parser state if any.
- f.write(code)
- return self.dynac(dyna)
+ pstate = self.compiler.parser_state(self.bc, self.ruleix, self.agg_name, self.other)
+ return self.compiler.dynac_code(code, pstate)
#___________________________________________________________________________
# Routines for showing things to the user.
import re
-from IPython.external.path import path
+from utils import path
class matrix(object):
"""
import cPickle
-from IPython.external.path import path
+from utils import path
class pickled(object):
from cStringIO import StringIO
-from utils import parse_sexpr
+from utils import parse_sexpr, path
from stdlib import todyna
-from IPython.external.path import path
class sexpr(object):
import re
from utils import true
-from IPython.external.path import path
+from path import path
class tsv(object):
import argparse
-from IPython.external.path import path
+from utils import path
from errors import DynaCompilerError
from errors import crash_handler
from interpreter import Interpreter
from utils import _repr
-from aggregator import NoAggregator
# TODO: codegen should output a derived Term instance for each functor
self.head = head
self.tail = tail
Term.__init__(self, 'cons/2', (head, tail))
- self.aggregator = NoAggregator
+ self.aggregator = None
self.aslist = [self.head] + self.tail.aslist
def __cmp__(self, other):
def __init__(self):
Term.__init__(self, 'nil/0', ())
- self.aggregator = NoAggregator
+ self.aggregator = None
self.aslist = []
def __repr__(self):
import re
+from path import path # used by other modules
from IPython.frontend.terminal.embed import InteractiveShellEmbed
-from IPython.external.path import path
-from subprocess import Popen, PIPE
from config import dynahome, dotdynadir
from collections import namedtuple, defaultdict
from cStringIO import StringIO
return dict(g)
-# TODO: This is pretty hacking we should have the codegen produce something
-# easier to serialize/modify/unserialize. XREF:parser-state
-def parse_parser_state(parser_state):
- backchain = set()
- ruleix = None
- iaggr = {}
- other = []
- for k, v in re.findall('^:-\s*(\S+) (.*?)\s*\.$', parser_state, re.MULTILINE):
- if k == 'backchain':
- [(fn, arity)] = re.findall("'(.*?)'/(\d+)", v)
- backchain.add('%s/%s' % (fn, arity))
- elif k == 'iaggr':
- [(fn, arity, agg)] = re.findall("'(.*?)'/(\d+)\s*(.*)", v)
- iaggr['%s/%s' % (fn, arity)] = agg
- elif k == 'ruleix':
- ruleix = int(v)
- else:
- other.append((k,v))
- return backchain, ruleix, iaggr, other
-
-
def indent(x, indent=''):
if isinstance(x, basestring):
return re.compile('^(.*)$', flags=re.MULTILINE).sub(indent + r'\1', x)
#from fabulous.color import red, green, yellow, blue, magenta, cyan, white, bold, underline
-_comments = re.compile('%.*$', re.MULTILINE)
-def strip_comments(src):
- return _comments.sub('', src).strip()
-
-
-def dynac(f, out, anf=None, compiler_args=()):
- """
- Run compiler on file, ``f``, write results to ``out``. Raises
- ``DynaCompilerError`` on failure.
- """
- from errors import DynaCompilerError
-
- f = path(f)
- if not f.exists():
- raise DynaCompilerError("File '%s' does not exist." % f)
-
- cmd = ['%s/dist/build/dyna/dyna' % dynahome,
- '-B', 'python', '-o', out, f]
-
- if anf is None:
- cmd += ['--dump-anf=' + out + '.anf']
- else:
- cmd += ['--dump-anf=' + anf]
-
- cmd += compiler_args
-
- p = Popen(cmd, stdout=PIPE, stderr=PIPE)
-
- stdout, stderr = p.communicate()
- if p.returncode:
- assert not stdout.strip(), [stdout, stderr]
- stderr = hide_ugly_filename(stderr, lambda m: '\n %s\n' % span_to_src(m.group(0)))
- raise DynaCompilerError(stderr, f)
-
-
def hide_ugly_filename(x, replacement='<repl>'):
p = dotdynadir + '[a-z0-9/.]+\.dyna\S*'
return re.sub(p, replacement, x)