view light9/effecteval/effect.py @ 1179:65405b3311f6

dead code Ignore-this: 19fc9344a2ffe34598ccccd5738bd1a6
author Drew Perttula <drewp@bigasterisk.com>
date Sun, 15 Jun 2014 07:42:51 +0000
parents da006651b531
children 92ffad96fd8a
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 prof
from light9 import Submaster
from light9 import Effects # gets reload() later
log = logging.getLogger('effect')

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

class CouldNotConvert(TypeError):
    pass

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)

    @prof.logTime
    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']))
        
    @prof.logTime
    def _resourcesAsPython(self, resources):
        """
        mapping of the local names for uris in the code to high-level
        objects (Submaster, Curve)
        """
        out = {}
        subs = prof.logTime(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)
                    # this is slow- pool these curves somewhere, maybe just with curveset
                    prof.logTime(cr.loadCurve)()
                    out[localVar] = cr.curve
                    break
                elif rdfClass == L9['Submaster']:
                    out[localVar] = subs.get_sub_by_uri(uri)
                    break
                else:
                    out[localVar] = CouldNotConvert(uri)
                    break
            else:
                out[localVar] = CouldNotConvert(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)

    @prof.logTime
    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()

        #reload(Effects)
        self.otherFuncs = prof.logTime(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']