diff --git a/bin/effecteval b/bin/effecteval
--- a/bin/effecteval
+++ b/bin/effecteval
@@ -104,8 +104,12 @@ class EffectUpdates(cyclone.websocket.We
def updateClient(self):
# todo: if client has dropped, abort and don't get any more
# graph updates
- self.sendMessage({'codeLines':
- list(self.graph.objects(self.uri, L9['code']))})
+
+ # EffectNode knows how to put them in order. Somehow this is
+ # not triggering an update when the order changes.
+ en = EffectNode(self.graph, self.uri)
+ codeLines = [c.code for c in en.codes]
+ self.sendMessage({'codeLines': codeLines})
def connectionLost(self, reason):
log.info("websocket closed")
diff --git a/light9/effecteval/effect.py b/light9/effecteval/effect.py
--- a/light9/effecteval/effect.py
+++ b/light9/effecteval/effect.py
@@ -1,4 +1,5 @@
import re, logging
+import toposort
from rdflib import URIRef
from light9.namespaces import L9, RDF
from light9.curvecalc.curve import CurveResource
@@ -22,8 +23,9 @@ class CodeLine(object):
def __init__(self, graph, code):
self.graph, self.code = graph, code
- self.outName, self.expr, self.resources = self._asPython()
+ self.outName, self.inExpr, self.expr, self.resources = self._asPython()
self.pyResources = self._resourcesAsPython(self.resources)
+ self.possibleVars = self.findVars(self.inExpr)
def _asPython(self):
"""
@@ -50,8 +52,15 @@ class CodeLine(object):
return 'curve(%s, t)' % v
return v
outExpr = re.sub(r'<(http\S*?)>', repl, expr)
- return lname, outExpr, resources
+ return lname, expr, outExpr, resources
+ def findVars(self, expr):
+ """may return some more strings than just the vars"""
+ withoutUris = re.sub(r'<(http\S*?)>', 'None', expr)
+ tokens = set(re.findall(r'\b([a-zA-Z_]\w*)\b', withoutUris))
+ tokens.discard('None')
+ return tokens
+
def _uriIsCurve(self, uri):
# this result could vary with graph changes (rare)
return self.graph.contains((uri, RDF.type, L9['Curve']))
@@ -92,8 +101,21 @@ class EffectNode(object):
self.codes = [CodeLine(self.graph, s) for s in codeStrs]
+ self.sortCodes()
+
self.otherFuncs = Effects.configExprGlobals()
-
+
+ def sortCodes(self):
+ """put self.codes in a working evaluation order"""
+ codeFromOutput = dict((c.outName, c) for c in self.codes)
+ deps = {}
+ for c in self.codes:
+ outName = c.outName
+ inNames = c.possibleVars.intersection(codeFromOutput.keys())
+ inNames.discard(outName)
+ deps[outName] = inNames
+ self.codes = [codeFromOutput[n] for n in toposort.toposort_flatten(deps)]
+
def eval(self, songTime):
ns = {'t': songTime}
ns.update(self.otherFuncs)
@@ -101,13 +123,13 @@ class EffectNode(object):
ns.update(dict(
curve=lambda c, t: c.eval(t),
))
- # loop over lines in order, merging in outputs
- # merge in named outputs from previous lines
for c in self.codes:
codeNs = ns.copy()
codeNs.update(c.pyResources)
- if c.outName == 'out':
- out = eval(c.expr, codeNs)
- return out
+ lineOut = eval(c.expr, codeNs)
+ ns[c.outName] = lineOut
+ if 'out' not in ns:
+ log.error("ran code for %s, didn't make an 'out' value", self.uri)
+ return ns['out']
diff --git a/light9/effecteval/effectloop.py b/light9/effecteval/effectloop.py
--- a/light9/effecteval/effectloop.py
+++ b/light9/effecteval/effectloop.py
@@ -78,9 +78,6 @@ class EffectLoop(object):
now = time.time()
if now > self.lastErrorLog + 5:
log.error("effect %s: %s" % (e.uri, exc))
- log.error(" expr: %s", e.code.expr)
- log.error(" resources: %r",
- getattr(e, 'resourceMap', '?'))
self.lastErrorLog = now
out = Submaster.sub_maxes(*outSubs)
diff --git a/light9/effecteval/test_effect.py b/light9/effecteval/test_effect.py
--- a/light9/effecteval/test_effect.py
+++ b/light9/effecteval/test_effect.py
@@ -10,43 +10,56 @@ def isCurve(self, uri):
return 'curve' in uri
@mock.patch('light9.effecteval.effect.CodeLine._uriIsCurve', new=isCurve)
+@mock.patch('light9.effecteval.effect.CodeLine._resourcesAsPython',
+ new=lambda self, r: self.expr)
class TestAsPython(unittest.TestCase):
def test_gets_lname(self):
ec = CodeLine(graph=None, code='x = y+1')
- self.assertEqual('x', ec._asPython()[0])
+ self.assertEqual('x', ec.outName)
def test_gets_simple_code(self):
ec = CodeLine(graph=None, code='x = y+1')
- self.assertEqual('y+1', ec._asPython()[1])
- self.assertEqual({}, ec._asPython()[2])
+ self.assertEqual('y+1', ec._asPython()[2])
+ self.assertEqual({}, ec._asPython()[3])
def test_converts_uri_to_var(self):
ec = CodeLine(graph=None, code='x = ')
- _, expr, uris = ec._asPython()
+ _, inExpr, expr, uris = ec._asPython()
self.assertEqual('_res0', expr)
self.assertEqual({'_res0': URIRef('http://example.com/')}, uris)
def test_converts_multiple_uris(self):
ec = CodeLine(graph=None, code='x = + ')
- _, expr, uris = ec._asPython()
+ _, inExpr, expr, uris = ec._asPython()
self.assertEqual('_res0 + _res1', expr)
self.assertEqual({'_res0': URIRef('http://example.com/'),
'_res1': URIRef('http://other')}, uris)
def test_doesnt_fall_for_brackets(self):
ec = CodeLine(graph=None, code='x = 1<2>3< h')
- _, expr, uris = ec._asPython()
+ _, inExpr, expr, uris = ec._asPython()
self.assertEqual('1<2>3< h', expr)
self.assertEqual({}, uris)
def test_curve_uri_expands_to_curve_eval_func(self):
ec = CodeLine(graph=None, code='x = ')
- _, expr, uris = ec._asPython()
+ _, inExpr, expr, uris = ec._asPython()
self.assertEqual('curve(_res0, t)', expr)
self.assertEqual({'_res0': URIRef('http://example/curve1')}, uris)
def test_curve_doesnt_double_wrap(self):
ec = CodeLine(graph=None, code='x = curve(, t+.01)')
- _, expr, uris = ec._asPython()
+ _, inExpr, expr, uris = ec._asPython()
self.assertEqual('curve(_res0, t+.01)', expr)
self.assertEqual({'_res0': URIRef('http://example/curve1')}, uris)
+
+@mock.patch('light9.effecteval.effect.CodeLine._uriIsCurve', new=isCurve)
+@mock.patch('light9.effecteval.effect.CodeLine._resourcesAsPython',
+ new=lambda self, r: self.expr)
+class TestPossibleVars(unittest.TestCase):
+ def test1(self):
+ self.assertEqual(set([]), CodeLine(None, 'a1 = 1').possibleVars)
+ def test2(self):
+ self.assertEqual(set(['a2']), CodeLine(None, 'a1 = a2').possibleVars)
+ def test3(self):
+ self.assertEqual(set(['a2', 'a3']), CodeLine(None, 'a1 = a2 + a3').possibleVars)
diff --git a/pydeps b/pydeps
--- a/pydeps
+++ b/pydeps
@@ -20,4 +20,6 @@ service_identity==0.2
Pillow==2.4.0
faulthandler==2.3
treq==0.2.1
-mock==1.0.1
\ No newline at end of file
+mock==1.0.1
+toposort==1.0
+