view light9/effecteval/effect.py @ 1113:780e91cc21cc

dead code from the prototype Ignore-this: 4b9f45d89e1571fc8fc305cf00a1729e
author Drew Perttula <drewp@bigasterisk.com>
date Thu, 12 Jun 2014 08:59:52 +0000
parents 4b542d321c8f
children 57eb333e6a4c
line wrap: on
line source

import re, logging
import toposort
from rdflib import URIRef
from light9.namespaces import L9, RDF
from light9.curvecalc.curve import CurveResource
from light9 import Submaster, Effects
log = logging.getLogger('effect')

# consider http://waxeye.org/ for a parser that can be used in py and js

class CodeLine(object):
    """code string is immutable"""
    def __init__(self, graph, code):
        self.graph, self.code = graph, code

        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):
        """
        out = sub(<uri1>, intensity=<curveuri2>)
        becomes
          'out',
          'sub(_u1, intensity=curve(_u2, t))',
          {'_u1': URIRef('uri1'), '_u2': URIRef('uri2')}
        """
        lname, expr = [s.strip() for s in self.code.split('=', 1)]
        self.uriCounter = 0
        resources = {}

        def alreadyInCurveFunc(s, i):
            prefix = 'curve('
            return i >= len(prefix) and s[i-len(prefix):i] == prefix

        def repl(m):
            v = '_res%s' % self.uriCounter
            self.uriCounter += 1
            r = resources[v] = URIRef(m.group(1))
            if self._uriIsCurve(r):
                if not alreadyInCurveFunc(m.string, m.start()):
                    return 'curve(%s, t)' % v
            return v
        outExpr = re.sub(r'<(http\S*?)>', repl, expr)
        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']))
        
    def _resourcesAsPython(self, resources):
        """
        mapping of the local names for uris in the code to high-level
        objects (Submaster, Curve)
        """
        out = {}
        subs = Submaster.get_global_submasters(self.graph)
        for localVar, uri in resources.items():
            for rdfClass in self.graph.objects(uri, RDF.type):
                if rdfClass == L9['Curve']:
                    cr = CurveResource(self.graph, uri)
                    cr.loadCurve()
                    out[localVar] = cr.curve
                elif rdfClass == L9['Submaster']:
                    out[localVar] = subs.get_sub_by_uri(uri)
                else:
                    out[localVar] = uri

        return out

class EffectNode(object):
    def __init__(self, graph, uri):
        self.graph, self.uri = graph, uri
        # this is not expiring at the right time, when an effect goes away
        self.graph.addHandler(self.prepare)

    def prepare(self):
        log.info("prepare effect %s", self.uri)
        # maybe there can be multiple lines of code as multiple
        # objects here, and we sort them by dependencies
        codeStrs = list(self.graph.objects(self.uri, L9['code']))
        if not codeStrs:
            raise ValueError("effect %s has no code" % self.uri)

        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)

        ns.update(dict(
            curve=lambda c, t: c.eval(t),
            ))

        for c in self.codes:
            codeNs = ns.copy()
            codeNs.update(c.pyResources)
            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']