KiBot/kibot/PcbDraw/pybars/pymeta/builder.py

327 lines
9.1 KiB
Python

# -*- test-case-name: pymeta.test.test_builder -*-
import linecache, sys
from types import ModuleType as module
import itertools, linecache, sys
class TreeBuilder(object):
"""
Produce an abstract syntax tree of OMeta operations.
"""
def __init__(self, name, grammar=None, *args):
self.name = name
def makeGrammar(self, rules):
return ["Grammar", self.name, rules]
def rule(self, name, expr):
return ["Rule", name, expr]
def apply(self, ruleName, codeName, *exprs):
return ["Apply", ruleName, codeName, exprs]
def exactly(self, expr):
return ["Exactly", expr]
def many(self, expr):
return ["Many", expr]
def many1(self, expr):
return ["Many1", expr]
def optional(self, expr):
return ["Optional", expr]
def _or(self, exprs):
return ["Or", exprs]
def _not(self, expr):
return ["Not", expr]
def lookahead(self, expr):
return ["Lookahead", expr]
def sequence(self, exprs):
return ["And", exprs]
def bind(self, expr, name):
return ["Bind", name, expr]
def pred(self, expr):
return ["Predicate", expr]
def action(self, expr):
return ["Action", expr]
def expr(self, expr):
return ["Python", expr]
def listpattern(self, exprs):
return ["List", exprs]
class PythonWriter(object):
"""
Converts an OMeta syntax tree into Python source.
"""
def __init__(self, tree):
self.tree = tree
self.lines = []
self.gensymCounter = 0
def _generate(self, retrn=False):
result = self._generateNode(self.tree)
if retrn:
self.lines.append("return (%s, self.currentError)" % (result,))
elif result:
self.lines.append(result)
return self.lines
def output(self):
return '\n'.join(self._generate())
def _generateNode(self, node):
name = node[0]
args = node[1:]
return getattr(self, "generate_"+name)(*args)
def _gensym(self, name):
"""
Produce a unique name for a variable in generated code.
"""
self.gensymCounter += 1
return "_G_%s_%s" % (name, self.gensymCounter)
def _newThunkFor(self, name, expr):
"""
Define a new function of no arguments.
@param name: The name of the rule generating this thunk.
@param expr: A list of lines of Python code.
"""
subwriter = self.__class__(expr)
flines = subwriter._generate(retrn=True)
fname = self._gensym(name)
self._writeFunction(fname, (), flines)
return fname
def _expr(self, typ, e):
"""
Generate the code needed to execute the expression, and return the
variable name bound to its value.
"""
name = self._gensym(typ)
self.lines.append("%s, lastError = %s" % (name, e))
self.lines.append("self.considerError(lastError)")
return name
def _writeFunction(self, fname, arglist, flines):
"""
Generate a function.
@param head: The initial line defining the function.
@param body: A list of lines for the function body.
"""
self.lines.append("def %s(%s):" % (fname, ", ".join(arglist)))
for line in flines:
self.lines.append((" " * 4) + line)
return fname
def compilePythonExpr(self, expr):
"""
Generate code for running embedded Python expressions.
"""
return self._expr('python', 'eval(%r, self.globals, _locals), None' %(expr,))
def generate_Apply(self, ruleName, codeName, rawArgs):
"""
Create a call to self.apply(ruleName, *args).
"""
args = [self._generateNode(x) for x in rawArgs]
if ruleName == 'super':
return self._expr('apply', 'self.superApply("%s", %s)' % (codeName,
', '.join(args)))
return self._expr('apply', 'self._apply(self.rule_%s, "%s", [%s])' % (ruleName,
ruleName,
', '.join(args)))
def generate_Exactly(self, literal):
"""
Create a call to self.exactly(expr).
"""
return self._expr('exactly', 'self.exactly(%r)' % (literal,))
def generate_Many(self, expr):
"""
Create a call to self.many(lambda: expr).
"""
fname = self._newThunkFor("many", expr)
return self._expr('many', 'self.many(%s)' % (fname,))
def generate_Many1(self, expr):
"""
Create a call to self.many(lambda: expr).
"""
fname = self._newThunkFor("many1", expr)
return self._expr('many1', 'self.many(%s, %s())' % (fname, fname))
def generate_Optional(self, expr):
"""
Try to parse an expr and continue if it fails.
"""
realf = self._newThunkFor("optional", expr)
passf = self._gensym("optional")
self._writeFunction(passf, (), ["return (None, self.input.nullError())"])
return self._expr('or', 'self._or([%s])' % (', '.join([realf, passf])))
def generate_Or(self, exprs):
"""
Create a call to
self._or([lambda: expr1, lambda: expr2, ... , lambda: exprN]).
"""
if len(exprs) > 1:
fnames = [self._newThunkFor("or", expr) for expr in exprs]
return self._expr('or', 'self._or([%s])' % (', '.join(fnames)))
else:
return self._generateNode(exprs[0])
def generate_Not(self, expr):
"""
Create a call to self._not(lambda: expr).
"""
fname = self._newThunkFor("not", expr)
return self._expr("not", "self._not(%s)" % (fname,))
def generate_Lookahead(self, expr):
"""
Create a call to self.lookahead(lambda: expr).
"""
fname = self._newThunkFor("lookahead", expr)
return self._expr("lookahead", "self.lookahead(%s)" %(fname,))
def generate_And(self, exprs):
"""
Generate code for each statement in order.
"""
v = None
for ex in exprs:
v = self._generateNode(ex)
return v
def generate_Bind(self, name, expr):
"""
Bind the value of 'expr' to a name in the _locals dict.
"""
v = self._generateNode(expr)
ref = "_locals['%s']" % (name,)
self.lines.append("%s = %s" %(ref, v))
return ref
def generate_Predicate(self, expr):
"""
Generate a call to self.pred(lambda: expr).
"""
fname = self._newThunkFor("pred", expr)
return self._expr("pred", "self.pred(%s)" %(fname,))
def generate_Action(self, expr):
"""
Generate this embedded Python expression on its own line.
"""
return self.compilePythonExpr(expr)
def generate_Python(self, expr):
"""
Generate this embedded Python expression on its own line.
"""
return self.compilePythonExpr(expr)
def generate_List(self, expr):
"""
Generate a call to self.listpattern(lambda: expr).
"""
fname = self._newThunkFor("listpattern", expr)
return self._expr("listpattern", "self.listpattern(%s)" %(fname,))
def generate_Rule(self, name, expr):
rulelines = ["_locals = {'self': self}",
"self.locals[%r] = _locals" % (name,)]
subwriter = self.__class__(expr)
flines = subwriter._generate(retrn=True)
rulelines.extend(flines)
self._writeFunction("rule_" + name, ("self",), rulelines)
def generate_Grammar(self, name, rules):
self.lines.append("class %s(GrammarBase):" % (name,))
for rule in rules:
self._generateNode(rule)
self.lines.extend(['', ''])
self.lines[1:] = [line and (' ' * 4 + line) for line in self.lines[1:]]
del self.lines[-2:]
def writePython(tree):
pw = PythonWriter(tree)
return pw.output()
class GeneratedCodeLoader(object):
"""
Object for use as a module's __loader__, to display generated
source.
"""
def __init__(self, source):
self.source = source
def get_source(self, name):
return self.source
def moduleFromGrammar(tree, className, superclass, globalsDict):
source = writePython(tree)
modname = "pymeta_grammar__" + className
filename = "/pymeta_generated_code/" + modname + ".py"
mod = module(modname)
mod.__dict__.update(globalsDict)
mod.__name__ = modname
mod.__dict__[superclass.__name__] = superclass
mod.__dict__["GrammarBase"] = superclass
mod.__loader__ = GeneratedCodeLoader(source)
code = compile(source, filename, "exec")
eval(code, mod.__dict__)
fullGlobals = dict(getattr(mod.__dict__[className], "globals", None) or {})
fullGlobals.update(globalsDict)
mod.__dict__[className].globals = fullGlobals
sys.modules[modname] = mod
linecache.getlines(filename, mod.__dict__)
return mod.__dict__[className]