mirror of https://github.com/python/cpython
1581 lines
48 KiB
Python
1581 lines
48 KiB
Python
import imp
|
|
import os
|
|
import marshal
|
|
import struct
|
|
import sys
|
|
from cStringIO import StringIO
|
|
|
|
from compiler import ast, parse, walk, syntax
|
|
from compiler import pyassem, misc, future, symbols
|
|
from compiler.consts import SC_LOCAL, SC_GLOBAL, SC_FREE, SC_CELL
|
|
from compiler.consts import (CO_VARARGS, CO_VARKEYWORDS, CO_NEWLOCALS,
|
|
CO_NESTED, CO_GENERATOR, CO_FUTURE_DIVISION,
|
|
CO_FUTURE_ABSIMPORT, CO_FUTURE_WITH_STATEMENT)
|
|
from compiler.pyassem import TupleArg
|
|
|
|
# XXX The version-specific code can go, since this code only works with 2.x.
|
|
# Do we have Python 1.x or Python 2.x?
|
|
try:
|
|
VERSION = sys.version_info[0]
|
|
except AttributeError:
|
|
VERSION = 1
|
|
|
|
callfunc_opcode_info = {
|
|
# (Have *args, Have **args) : opcode
|
|
(0,0) : "CALL_FUNCTION",
|
|
(1,0) : "CALL_FUNCTION_VAR",
|
|
(0,1) : "CALL_FUNCTION_KW",
|
|
(1,1) : "CALL_FUNCTION_VAR_KW",
|
|
}
|
|
|
|
LOOP = 1
|
|
EXCEPT = 2
|
|
TRY_FINALLY = 3
|
|
END_FINALLY = 4
|
|
|
|
def compileFile(filename, display=0):
|
|
f = open(filename, 'U')
|
|
buf = f.read()
|
|
f.close()
|
|
mod = Module(buf, filename)
|
|
try:
|
|
mod.compile(display)
|
|
except SyntaxError:
|
|
raise
|
|
else:
|
|
f = open(filename + "c", "wb")
|
|
mod.dump(f)
|
|
f.close()
|
|
|
|
def compile(source, filename, mode, flags=None, dont_inherit=None):
|
|
"""Replacement for builtin compile() function"""
|
|
if flags is not None or dont_inherit is not None:
|
|
raise RuntimeError, "not implemented yet"
|
|
|
|
if mode == "single":
|
|
gen = Interactive(source, filename)
|
|
elif mode == "exec":
|
|
gen = Module(source, filename)
|
|
elif mode == "eval":
|
|
gen = Expression(source, filename)
|
|
else:
|
|
raise ValueError("compile() 3rd arg must be 'exec' or "
|
|
"'eval' or 'single'")
|
|
gen.compile()
|
|
return gen.code
|
|
|
|
class AbstractCompileMode:
|
|
|
|
mode = None # defined by subclass
|
|
|
|
def __init__(self, source, filename):
|
|
self.source = source
|
|
self.filename = filename
|
|
self.code = None
|
|
|
|
def _get_tree(self):
|
|
tree = parse(self.source, self.mode)
|
|
misc.set_filename(self.filename, tree)
|
|
syntax.check(tree)
|
|
return tree
|
|
|
|
def compile(self):
|
|
pass # implemented by subclass
|
|
|
|
def getCode(self):
|
|
return self.code
|
|
|
|
class Expression(AbstractCompileMode):
|
|
|
|
mode = "eval"
|
|
|
|
def compile(self):
|
|
tree = self._get_tree()
|
|
gen = ExpressionCodeGenerator(tree)
|
|
self.code = gen.getCode()
|
|
|
|
class Interactive(AbstractCompileMode):
|
|
|
|
mode = "single"
|
|
|
|
def compile(self):
|
|
tree = self._get_tree()
|
|
gen = InteractiveCodeGenerator(tree)
|
|
self.code = gen.getCode()
|
|
|
|
class Module(AbstractCompileMode):
|
|
|
|
mode = "exec"
|
|
|
|
def compile(self, display=0):
|
|
tree = self._get_tree()
|
|
gen = ModuleCodeGenerator(tree)
|
|
if display:
|
|
import pprint
|
|
print(pprint.pprint(tree))
|
|
self.code = gen.getCode()
|
|
|
|
def dump(self, f):
|
|
f.write(self.getPycHeader())
|
|
marshal.dump(self.code, f)
|
|
|
|
MAGIC = imp.get_magic()
|
|
|
|
def getPycHeader(self):
|
|
# compile.c uses marshal to write a long directly, with
|
|
# calling the interface that would also generate a 1-byte code
|
|
# to indicate the type of the value. simplest way to get the
|
|
# same effect is to call marshal and then skip the code.
|
|
mtime = os.path.getmtime(self.filename)
|
|
mtime = struct.pack('<i', mtime)
|
|
return self.MAGIC + mtime
|
|
|
|
class LocalNameFinder:
|
|
"""Find local names in scope"""
|
|
def __init__(self, names=()):
|
|
self.names = misc.Set()
|
|
self.globals = misc.Set()
|
|
for name in names:
|
|
self.names.add(name)
|
|
|
|
# XXX list comprehensions and for loops
|
|
|
|
def getLocals(self):
|
|
for elt in self.globals.elements():
|
|
if self.names.has_elt(elt):
|
|
self.names.remove(elt)
|
|
return self.names
|
|
|
|
def visitDict(self, node):
|
|
pass
|
|
|
|
def visitGlobal(self, node):
|
|
for name in node.names:
|
|
self.globals.add(name)
|
|
|
|
def visitFunction(self, node):
|
|
self.names.add(node.name)
|
|
|
|
def visitLambda(self, node):
|
|
pass
|
|
|
|
def visitImport(self, node):
|
|
for name, alias in node.names:
|
|
self.names.add(alias or name)
|
|
|
|
def visitFrom(self, node):
|
|
for name, alias in node.names:
|
|
self.names.add(alias or name)
|
|
|
|
def visitClass(self, node):
|
|
self.names.add(node.name)
|
|
|
|
def visitAssName(self, node):
|
|
self.names.add(node.name)
|
|
|
|
def is_constant_false(node):
|
|
if isinstance(node, ast.Const):
|
|
if not node.value:
|
|
return 1
|
|
return 0
|
|
|
|
class CodeGenerator:
|
|
"""Defines basic code generator for Python bytecode
|
|
|
|
This class is an abstract base class. Concrete subclasses must
|
|
define an __init__() that defines self.graph and then calls the
|
|
__init__() defined in this class.
|
|
|
|
The concrete class must also define the class attributes
|
|
NameFinder, FunctionGen, and ClassGen. These attributes can be
|
|
defined in the initClass() method, which is a hook for
|
|
initializing these methods after all the classes have been
|
|
defined.
|
|
"""
|
|
|
|
optimized = 0 # is namespace access optimized?
|
|
__initialized = None
|
|
class_name = None # provide default for instance variable
|
|
|
|
def __init__(self):
|
|
if self.__initialized is None:
|
|
self.initClass()
|
|
self.__class__.__initialized = 1
|
|
self.checkClass()
|
|
self.locals = misc.Stack()
|
|
self.setups = misc.Stack()
|
|
self.last_lineno = None
|
|
self._setupGraphDelegation()
|
|
|
|
# XXX set flags based on future features
|
|
futures = self.get_module().futures
|
|
for feature in futures:
|
|
if feature == "division":
|
|
self.graph.setFlag(CO_FUTURE_DIVISION)
|
|
elif feature == "absolute_import":
|
|
self.graph.setFlag(CO_FUTURE_ABSIMPORT)
|
|
elif feature == "with_statement":
|
|
self.graph.setFlag(CO_FUTURE_WITH_STATEMENT)
|
|
|
|
def initClass(self):
|
|
"""This method is called once for each class"""
|
|
|
|
def checkClass(self):
|
|
"""Verify that class is constructed correctly"""
|
|
try:
|
|
assert hasattr(self, 'graph')
|
|
assert getattr(self, 'NameFinder')
|
|
assert getattr(self, 'FunctionGen')
|
|
assert getattr(self, 'ClassGen')
|
|
except AssertionError as msg:
|
|
intro = "Bad class construction for %s" % self.__class__.__name__
|
|
raise AssertionError, intro
|
|
|
|
def _setupGraphDelegation(self):
|
|
self.emit = self.graph.emit
|
|
self.newBlock = self.graph.newBlock
|
|
self.startBlock = self.graph.startBlock
|
|
self.nextBlock = self.graph.nextBlock
|
|
self.setDocstring = self.graph.setDocstring
|
|
|
|
def getCode(self):
|
|
"""Return a code object"""
|
|
return self.graph.getCode()
|
|
|
|
def mangle(self, name):
|
|
if self.class_name is not None:
|
|
return misc.mangle(name, self.class_name)
|
|
else:
|
|
return name
|
|
|
|
def parseSymbols(self, tree):
|
|
s = symbols.SymbolVisitor()
|
|
walk(tree, s)
|
|
return s.scopes
|
|
|
|
def get_module(self):
|
|
raise RuntimeError, "should be implemented by subclasses"
|
|
|
|
# Next five methods handle name access
|
|
|
|
def isLocalName(self, name):
|
|
return self.locals.top().has_elt(name)
|
|
|
|
def storeName(self, name):
|
|
self._nameOp('STORE', name)
|
|
|
|
def loadName(self, name):
|
|
self._nameOp('LOAD', name)
|
|
|
|
def delName(self, name):
|
|
self._nameOp('DELETE', name)
|
|
|
|
def _nameOp(self, prefix, name):
|
|
name = self.mangle(name)
|
|
scope = self.scope.check_name(name)
|
|
if scope == SC_LOCAL:
|
|
if not self.optimized:
|
|
self.emit(prefix + '_NAME', name)
|
|
else:
|
|
self.emit(prefix + '_FAST', name)
|
|
elif scope == SC_GLOBAL:
|
|
if not self.optimized:
|
|
self.emit(prefix + '_NAME', name)
|
|
else:
|
|
self.emit(prefix + '_GLOBAL', name)
|
|
elif scope == SC_FREE or scope == SC_CELL:
|
|
self.emit(prefix + '_DEREF', name)
|
|
else:
|
|
raise RuntimeError, "unsupported scope for var %s: %d" % \
|
|
(name, scope)
|
|
|
|
def _implicitNameOp(self, prefix, name):
|
|
"""Emit name ops for names generated implicitly by for loops
|
|
|
|
The interpreter generates names that start with a period or
|
|
dollar sign. The symbol table ignores these names because
|
|
they aren't present in the program text.
|
|
"""
|
|
if self.optimized:
|
|
self.emit(prefix + '_FAST', name)
|
|
else:
|
|
self.emit(prefix + '_NAME', name)
|
|
|
|
# The set_lineno() function and the explicit emit() calls for
|
|
# SET_LINENO below are only used to generate the line number table.
|
|
# As of Python 2.3, the interpreter does not have a SET_LINENO
|
|
# instruction. pyassem treats SET_LINENO opcodes as a special case.
|
|
|
|
def set_lineno(self, node, force=False):
|
|
"""Emit SET_LINENO if necessary.
|
|
|
|
The instruction is considered necessary if the node has a
|
|
lineno attribute and it is different than the last lineno
|
|
emitted.
|
|
|
|
Returns true if SET_LINENO was emitted.
|
|
|
|
There are no rules for when an AST node should have a lineno
|
|
attribute. The transformer and AST code need to be reviewed
|
|
and a consistent policy implemented and documented. Until
|
|
then, this method works around missing line numbers.
|
|
"""
|
|
lineno = getattr(node, 'lineno', None)
|
|
if lineno is not None and (lineno != self.last_lineno
|
|
or force):
|
|
self.emit('SET_LINENO', lineno)
|
|
self.last_lineno = lineno
|
|
return True
|
|
return False
|
|
|
|
# The first few visitor methods handle nodes that generator new
|
|
# code objects. They use class attributes to determine what
|
|
# specialized code generators to use.
|
|
|
|
NameFinder = LocalNameFinder
|
|
FunctionGen = None
|
|
ClassGen = None
|
|
|
|
def visitModule(self, node):
|
|
self.scopes = self.parseSymbols(node)
|
|
self.scope = self.scopes[node]
|
|
self.emit('SET_LINENO', 0)
|
|
if node.doc:
|
|
self.emit('LOAD_CONST', node.doc)
|
|
self.storeName('__doc__')
|
|
lnf = walk(node.node, self.NameFinder(), verbose=0)
|
|
self.locals.push(lnf.getLocals())
|
|
self.visit(node.node)
|
|
self.emit('LOAD_CONST', None)
|
|
self.emit('RETURN_VALUE')
|
|
|
|
def visitExpression(self, node):
|
|
self.set_lineno(node)
|
|
self.scopes = self.parseSymbols(node)
|
|
self.scope = self.scopes[node]
|
|
self.visit(node.node)
|
|
self.emit('RETURN_VALUE')
|
|
|
|
def visitFunction(self, node):
|
|
self._visitFuncOrLambda(node, isLambda=0)
|
|
if node.doc:
|
|
self.setDocstring(node.doc)
|
|
self.storeName(node.name)
|
|
|
|
def visitLambda(self, node):
|
|
self._visitFuncOrLambda(node, isLambda=1)
|
|
|
|
def _visitFuncOrLambda(self, node, isLambda=0):
|
|
if not isLambda and node.decorators:
|
|
for decorator in node.decorators.nodes:
|
|
self.visit(decorator)
|
|
ndecorators = len(node.decorators.nodes)
|
|
else:
|
|
ndecorators = 0
|
|
|
|
gen = self.FunctionGen(node, self.scopes, isLambda,
|
|
self.class_name, self.get_module())
|
|
walk(node.code, gen)
|
|
gen.finish()
|
|
self.set_lineno(node)
|
|
num_kwargs = 0
|
|
for keyword in node.kwonlyargs:
|
|
default = keyword.expr
|
|
if isinstance(default, ast.EmptyNode):
|
|
continue
|
|
self.emit('LOAD_CONST', keyword.arg.name)
|
|
self.visit(default)
|
|
num_kwargs += 1
|
|
for default in node.defaults:
|
|
self.visit(default)
|
|
|
|
num_annotations = self._visit_annotations(node)
|
|
|
|
oparg = len(node.defaults)
|
|
oparg |= num_kwargs << 8
|
|
oparg |= num_annotations << 16
|
|
|
|
self._makeClosure(gen, oparg)
|
|
for i in range(ndecorators):
|
|
self.emit('CALL_FUNCTION', 1)
|
|
|
|
def _visit_annotations(self, node):
|
|
# emit code, return num_annotations
|
|
annotations = []
|
|
annotations.extend(self._visit_argument_annotations(node.arguments))
|
|
annotations.extend(self._visit_kwarg_annotations(node.kwonlyargs))
|
|
if node.returns:
|
|
self.visit(node.returns)
|
|
annotations.append('return')
|
|
if not annotations:
|
|
return 0
|
|
self.emit('LOAD_CONST', tuple(annotations))
|
|
return len(annotations) + 1
|
|
|
|
def _visit_argument_annotations(self, arguments):
|
|
for arg in arguments:
|
|
if isinstance(arg, ast.SimpleArg):
|
|
if arg.annotation:
|
|
self.visit(arg.annotation)
|
|
yield arg.name
|
|
else:
|
|
for name in self._visit_argument_annotations(arg.args):
|
|
yield name
|
|
|
|
def _visit_kwarg_annotations(self, kwargs):
|
|
for kwarg in kwargs:
|
|
arg = kwarg.arg
|
|
if arg.annotation:
|
|
self.visit(arg.annotation)
|
|
yield arg.name
|
|
|
|
def visitClass(self, node):
|
|
gen = self.ClassGen(node, self.scopes,
|
|
self.get_module())
|
|
walk(node.code, gen)
|
|
gen.finish()
|
|
self.set_lineno(node)
|
|
self.emit('LOAD_BUILD_CLASS')
|
|
self._makeClosure(gen, 0)
|
|
self.emit('LOAD_CONST', node.name)
|
|
self.finish_visit_call(node, 2)
|
|
self.storeName(node.name)
|
|
|
|
# The rest are standard visitor methods
|
|
|
|
# The next few implement control-flow statements
|
|
|
|
def visitIf(self, node):
|
|
end = self.newBlock()
|
|
numtests = len(node.tests)
|
|
for i in range(numtests):
|
|
test, suite = node.tests[i]
|
|
if is_constant_false(test):
|
|
# XXX will need to check generator stuff here
|
|
continue
|
|
self.set_lineno(test)
|
|
self.visit(test)
|
|
nextTest = self.newBlock()
|
|
self.emit('JUMP_IF_FALSE', nextTest)
|
|
self.nextBlock()
|
|
self.emit('POP_TOP')
|
|
self.visit(suite)
|
|
self.emit('JUMP_FORWARD', end)
|
|
self.startBlock(nextTest)
|
|
self.emit('POP_TOP')
|
|
if node.else_:
|
|
self.visit(node.else_)
|
|
self.nextBlock(end)
|
|
|
|
def visitWhile(self, node):
|
|
self.set_lineno(node)
|
|
|
|
loop = self.newBlock()
|
|
else_ = self.newBlock()
|
|
|
|
after = self.newBlock()
|
|
self.emit('SETUP_LOOP', after)
|
|
|
|
self.nextBlock(loop)
|
|
self.setups.push((LOOP, loop))
|
|
|
|
self.set_lineno(node, force=True)
|
|
self.visit(node.test)
|
|
self.emit('JUMP_IF_FALSE', else_ or after)
|
|
|
|
self.nextBlock()
|
|
self.emit('POP_TOP')
|
|
self.visit(node.body)
|
|
self.emit('JUMP_ABSOLUTE', loop)
|
|
|
|
self.startBlock(else_) # or just the POPs if not else clause
|
|
self.emit('POP_TOP')
|
|
self.emit('POP_BLOCK')
|
|
self.setups.pop()
|
|
if node.else_:
|
|
self.visit(node.else_)
|
|
self.nextBlock(after)
|
|
|
|
def visitFor(self, node):
|
|
start = self.newBlock()
|
|
anchor = self.newBlock()
|
|
after = self.newBlock()
|
|
self.setups.push((LOOP, start))
|
|
|
|
self.set_lineno(node)
|
|
self.emit('SETUP_LOOP', after)
|
|
self.visit(node.list)
|
|
self.emit('GET_ITER')
|
|
|
|
self.nextBlock(start)
|
|
self.set_lineno(node, force=1)
|
|
self.emit('FOR_ITER', anchor)
|
|
self.visit(node.assign)
|
|
self.visit(node.body)
|
|
self.emit('JUMP_ABSOLUTE', start)
|
|
self.nextBlock(anchor)
|
|
self.emit('POP_BLOCK')
|
|
self.setups.pop()
|
|
if node.else_:
|
|
self.visit(node.else_)
|
|
self.nextBlock(after)
|
|
|
|
def visitBreak(self, node):
|
|
if not self.setups:
|
|
raise SyntaxError, "'break' outside loop (%s, %d)" % \
|
|
(node.filename, node.lineno)
|
|
self.set_lineno(node)
|
|
self.emit('BREAK_LOOP')
|
|
|
|
def visitContinue(self, node):
|
|
if not self.setups:
|
|
raise SyntaxError, "'continue' outside loop (%s, %d)" % \
|
|
(node.filename, node.lineno)
|
|
kind, block = self.setups.top()
|
|
if kind == LOOP:
|
|
self.set_lineno(node)
|
|
self.emit('JUMP_ABSOLUTE', block)
|
|
self.nextBlock()
|
|
elif kind == EXCEPT or kind == TRY_FINALLY:
|
|
self.set_lineno(node)
|
|
# find the block that starts the loop
|
|
top = len(self.setups)
|
|
while top > 0:
|
|
top = top - 1
|
|
kind, loop_block = self.setups[top]
|
|
if kind == LOOP:
|
|
break
|
|
if kind != LOOP:
|
|
raise SyntaxError, "'continue' outside loop (%s, %d)" % \
|
|
(node.filename, node.lineno)
|
|
self.emit('CONTINUE_LOOP', loop_block)
|
|
self.nextBlock()
|
|
elif kind == END_FINALLY:
|
|
msg = "'continue' not allowed inside 'finally' clause (%s, %d)"
|
|
raise SyntaxError, msg % (node.filename, node.lineno)
|
|
|
|
def visitTest(self, node, jump):
|
|
end = self.newBlock()
|
|
for child in node.nodes[:-1]:
|
|
self.visit(child)
|
|
self.emit(jump, end)
|
|
self.nextBlock()
|
|
self.emit('POP_TOP')
|
|
self.visit(node.nodes[-1])
|
|
self.nextBlock(end)
|
|
|
|
def visitAnd(self, node):
|
|
self.visitTest(node, 'JUMP_IF_FALSE')
|
|
|
|
def visitOr(self, node):
|
|
self.visitTest(node, 'JUMP_IF_TRUE')
|
|
|
|
def visitIfExp(self, node):
|
|
endblock = self.newBlock()
|
|
elseblock = self.newBlock()
|
|
self.visit(node.test)
|
|
self.emit('JUMP_IF_FALSE', elseblock)
|
|
self.emit('POP_TOP')
|
|
self.visit(node.then)
|
|
self.emit('JUMP_FORWARD', endblock)
|
|
self.nextBlock(elseblock)
|
|
self.emit('POP_TOP')
|
|
self.visit(node.else_)
|
|
self.nextBlock(endblock)
|
|
|
|
def visitCompare(self, node):
|
|
self.visit(node.expr)
|
|
cleanup = self.newBlock()
|
|
for op, code in node.ops[:-1]:
|
|
self.visit(code)
|
|
self.emit('DUP_TOP')
|
|
self.emit('ROT_THREE')
|
|
self.emit('COMPARE_OP', op)
|
|
self.emit('JUMP_IF_FALSE', cleanup)
|
|
self.nextBlock()
|
|
self.emit('POP_TOP')
|
|
# now do the last comparison
|
|
if node.ops:
|
|
op, code = node.ops[-1]
|
|
self.visit(code)
|
|
self.emit('COMPARE_OP', op)
|
|
if len(node.ops) > 1:
|
|
end = self.newBlock()
|
|
self.emit('JUMP_FORWARD', end)
|
|
self.startBlock(cleanup)
|
|
self.emit('ROT_TWO')
|
|
self.emit('POP_TOP')
|
|
self.nextBlock(end)
|
|
|
|
# list comprehensions
|
|
__list_count = 0
|
|
|
|
def visitListComp(self, node):
|
|
self.set_lineno(node)
|
|
# setup list
|
|
tmpname = "$list%d" % self.__list_count
|
|
self.__list_count = self.__list_count + 1
|
|
self.emit('BUILD_LIST', 0)
|
|
self.emit('DUP_TOP')
|
|
self._implicitNameOp('STORE', tmpname)
|
|
|
|
stack = []
|
|
for i, for_ in zip(range(len(node.quals)), node.quals):
|
|
start, anchor = self.visit(for_)
|
|
cont = None
|
|
for if_ in for_.ifs:
|
|
if cont is None:
|
|
cont = self.newBlock()
|
|
self.visit(if_, cont)
|
|
stack.insert(0, (start, cont, anchor))
|
|
|
|
self._implicitNameOp('LOAD', tmpname)
|
|
self.visit(node.expr)
|
|
self.emit('LIST_APPEND')
|
|
|
|
for start, cont, anchor in stack:
|
|
if cont:
|
|
skip_one = self.newBlock()
|
|
self.emit('JUMP_FORWARD', skip_one)
|
|
self.startBlock(cont)
|
|
self.emit('POP_TOP')
|
|
self.nextBlock(skip_one)
|
|
self.emit('JUMP_ABSOLUTE', start)
|
|
self.startBlock(anchor)
|
|
self._implicitNameOp('DELETE', tmpname)
|
|
|
|
self.__list_count = self.__list_count - 1
|
|
|
|
def visitListCompFor(self, node):
|
|
start = self.newBlock()
|
|
anchor = self.newBlock()
|
|
|
|
self.visit(node.list)
|
|
self.emit('GET_ITER')
|
|
self.nextBlock(start)
|
|
self.set_lineno(node, force=True)
|
|
self.emit('FOR_ITER', anchor)
|
|
self.nextBlock()
|
|
self.visit(node.assign)
|
|
return start, anchor
|
|
|
|
def visitListCompIf(self, node, branch):
|
|
self.set_lineno(node, force=True)
|
|
self.visit(node.test)
|
|
self.emit('JUMP_IF_FALSE', branch)
|
|
self.newBlock()
|
|
self.emit('POP_TOP')
|
|
|
|
def _makeClosure(self, gen, args):
|
|
frees = gen.scope.get_free_vars()
|
|
if frees:
|
|
for name in frees:
|
|
self.emit('LOAD_CLOSURE', name)
|
|
self.emit('BUILD_TUPLE', len(frees))
|
|
self.emit('LOAD_CONST', gen)
|
|
self.emit('MAKE_CLOSURE', args)
|
|
else:
|
|
self.emit('LOAD_CONST', gen)
|
|
self.emit('MAKE_FUNCTION', args)
|
|
|
|
def visitGenExpr(self, node):
|
|
gen = GenExprCodeGenerator(node, self.scopes, self.class_name,
|
|
self.get_module())
|
|
walk(node.code, gen)
|
|
gen.finish()
|
|
self.set_lineno(node)
|
|
self._makeClosure(gen, 0)
|
|
# precomputation of outmost iterable
|
|
self.visit(node.code.quals[0].iter)
|
|
self.emit('GET_ITER')
|
|
self.emit('CALL_FUNCTION', 1)
|
|
|
|
def visitGenExprInner(self, node):
|
|
self.set_lineno(node)
|
|
# setup list
|
|
|
|
stack = []
|
|
for i, for_ in zip(range(len(node.quals)), node.quals):
|
|
start, anchor, end = self.visit(for_)
|
|
cont = None
|
|
for if_ in for_.ifs:
|
|
if cont is None:
|
|
cont = self.newBlock()
|
|
self.visit(if_, cont)
|
|
stack.insert(0, (start, cont, anchor, end))
|
|
|
|
self.visit(node.expr)
|
|
self.emit('YIELD_VALUE')
|
|
self.emit('POP_TOP')
|
|
|
|
for start, cont, anchor, end in stack:
|
|
if cont:
|
|
skip_one = self.newBlock()
|
|
self.emit('JUMP_FORWARD', skip_one)
|
|
self.startBlock(cont)
|
|
self.emit('POP_TOP')
|
|
self.nextBlock(skip_one)
|
|
self.emit('JUMP_ABSOLUTE', start)
|
|
self.startBlock(anchor)
|
|
self.emit('POP_BLOCK')
|
|
self.setups.pop()
|
|
self.startBlock(end)
|
|
|
|
self.emit('LOAD_CONST', None)
|
|
|
|
def visitGenExprFor(self, node):
|
|
start = self.newBlock()
|
|
anchor = self.newBlock()
|
|
end = self.newBlock()
|
|
|
|
self.setups.push((LOOP, start))
|
|
self.emit('SETUP_LOOP', end)
|
|
|
|
if node.is_outmost:
|
|
self.loadName('.0')
|
|
else:
|
|
self.visit(node.iter)
|
|
self.emit('GET_ITER')
|
|
|
|
self.nextBlock(start)
|
|
self.set_lineno(node, force=True)
|
|
self.emit('FOR_ITER', anchor)
|
|
self.nextBlock()
|
|
self.visit(node.assign)
|
|
return start, anchor, end
|
|
|
|
def visitGenExprIf(self, node, branch):
|
|
self.set_lineno(node, force=True)
|
|
self.visit(node.test)
|
|
self.emit('JUMP_IF_FALSE', branch)
|
|
self.newBlock()
|
|
self.emit('POP_TOP')
|
|
|
|
# exception related
|
|
|
|
def visitAssert(self, node):
|
|
# XXX would be interesting to implement this via a
|
|
# transformation of the AST before this stage
|
|
if __debug__:
|
|
end = self.newBlock()
|
|
self.set_lineno(node)
|
|
# XXX AssertionError appears to be special case -- it is always
|
|
# loaded as a global even if there is a local name. I guess this
|
|
# is a sort of renaming op.
|
|
self.nextBlock()
|
|
self.visit(node.test)
|
|
self.emit('JUMP_IF_TRUE', end)
|
|
self.nextBlock()
|
|
self.emit('POP_TOP')
|
|
self.emit('LOAD_GLOBAL', 'AssertionError')
|
|
if node.fail:
|
|
self.visit(node.fail)
|
|
self.emit('RAISE_VARARGS', 2)
|
|
else:
|
|
self.emit('RAISE_VARARGS', 1)
|
|
self.nextBlock(end)
|
|
self.emit('POP_TOP')
|
|
|
|
def visitRaise(self, node):
|
|
self.set_lineno(node)
|
|
n = 0
|
|
if node.expr1:
|
|
self.visit(node.expr1)
|
|
n = n + 1
|
|
if node.expr2:
|
|
self.visit(node.expr2)
|
|
n = n + 1
|
|
if node.expr3:
|
|
self.visit(node.expr3)
|
|
n = n + 1
|
|
self.emit('RAISE_VARARGS', n)
|
|
|
|
def visitTryExcept(self, node):
|
|
body = self.newBlock()
|
|
handlers = self.newBlock()
|
|
end = self.newBlock()
|
|
if node.else_:
|
|
lElse = self.newBlock()
|
|
else:
|
|
lElse = end
|
|
self.set_lineno(node)
|
|
self.emit('SETUP_EXCEPT', handlers)
|
|
self.nextBlock(body)
|
|
self.setups.push((EXCEPT, body))
|
|
self.visit(node.body)
|
|
self.emit('POP_BLOCK')
|
|
self.setups.pop()
|
|
self.emit('JUMP_FORWARD', lElse)
|
|
self.startBlock(handlers)
|
|
|
|
last = len(node.handlers) - 1
|
|
for i in range(len(node.handlers)):
|
|
expr, target, body = node.handlers[i]
|
|
self.set_lineno(expr)
|
|
if expr:
|
|
self.emit('DUP_TOP')
|
|
self.visit(expr)
|
|
self.emit('COMPARE_OP', 'exception match')
|
|
next = self.newBlock()
|
|
self.emit('JUMP_IF_FALSE', next)
|
|
self.nextBlock()
|
|
self.emit('POP_TOP')
|
|
self.emit('POP_TOP')
|
|
if target:
|
|
cleanup_body = self.newBlock()
|
|
cleanup_final = self.newBlock()
|
|
target_name = target[1]
|
|
|
|
self.storeName(target_name)
|
|
self.emit('POP_TOP')
|
|
self.emit('SETUP_FINALLY', cleanup_final)
|
|
self.nextBlock(cleanup_body)
|
|
self.setups.push((TRY_FINALLY, cleanup_body))
|
|
self.visit(body)
|
|
self.emit('POP_BLOCK')
|
|
self.setups.pop()
|
|
self.emit('LOAD_CONST', None)
|
|
self.nextBlock(cleanup_final)
|
|
self.setups.push((END_FINALLY, cleanup_final))
|
|
|
|
|
|
self.emit('LOAD_CONST', None)
|
|
self.storeName(target_name)
|
|
self._implicitNameOp('DELETE', target_name)
|
|
|
|
self.emit('END_FINALLY')
|
|
self.setups.pop()
|
|
else:
|
|
self.emit('POP_TOP')
|
|
self.emit('POP_TOP')
|
|
self.visit(body)
|
|
self.emit('JUMP_FORWARD', end)
|
|
if expr:
|
|
self.nextBlock(next)
|
|
else:
|
|
self.nextBlock()
|
|
if expr: # XXX
|
|
self.emit('POP_TOP')
|
|
self.emit('END_FINALLY')
|
|
if node.else_:
|
|
self.nextBlock(lElse)
|
|
self.visit(node.else_)
|
|
self.nextBlock(end)
|
|
|
|
def visitTryFinally(self, node):
|
|
body = self.newBlock()
|
|
final = self.newBlock()
|
|
self.set_lineno(node)
|
|
self.emit('SETUP_FINALLY', final)
|
|
self.nextBlock(body)
|
|
self.setups.push((TRY_FINALLY, body))
|
|
self.visit(node.body)
|
|
self.emit('POP_BLOCK')
|
|
self.setups.pop()
|
|
self.emit('LOAD_CONST', None)
|
|
self.nextBlock(final)
|
|
self.setups.push((END_FINALLY, final))
|
|
self.visit(node.final)
|
|
self.emit('END_FINALLY')
|
|
self.setups.pop()
|
|
|
|
__with_count = 0
|
|
|
|
def visitWith(self, node):
|
|
body = self.newBlock()
|
|
final = self.newBlock()
|
|
exitvar = "$exit%d" % self.__with_count
|
|
valuevar = "$value%d" % self.__with_count
|
|
self.__with_count += 1
|
|
self.set_lineno(node)
|
|
self.visit(node.expr)
|
|
self.emit('DUP_TOP')
|
|
self.emit('LOAD_ATTR', '__exit__')
|
|
self._implicitNameOp('STORE', exitvar)
|
|
self.emit('LOAD_ATTR', '__enter__')
|
|
self.emit('CALL_FUNCTION', 0)
|
|
if node.vars is None:
|
|
self.emit('POP_TOP')
|
|
else:
|
|
self._implicitNameOp('STORE', valuevar)
|
|
self.emit('SETUP_FINALLY', final)
|
|
self.nextBlock(body)
|
|
self.setups.push((TRY_FINALLY, body))
|
|
if node.vars is not None:
|
|
self._implicitNameOp('LOAD', valuevar)
|
|
self._implicitNameOp('DELETE', valuevar)
|
|
self.visit(node.vars)
|
|
self.visit(node.body)
|
|
self.emit('POP_BLOCK')
|
|
self.setups.pop()
|
|
self.emit('LOAD_CONST', None)
|
|
self.nextBlock(final)
|
|
self.setups.push((END_FINALLY, final))
|
|
self._implicitNameOp('LOAD', exitvar)
|
|
self._implicitNameOp('DELETE', exitvar)
|
|
self.emit('WITH_CLEANUP')
|
|
self.emit('END_FINALLY')
|
|
self.setups.pop()
|
|
self.__with_count -= 1
|
|
|
|
# misc
|
|
|
|
def visitDiscard(self, node):
|
|
self.set_lineno(node)
|
|
self.visit(node.expr)
|
|
self.emit('POP_TOP')
|
|
|
|
def visitConst(self, node):
|
|
self.emit('LOAD_CONST', node.value)
|
|
|
|
def visitBytes(self, node):
|
|
self.emit('LOAD_CONST', node.value)
|
|
self.emit('MAKE_BYTES')
|
|
|
|
def visitKeyword(self, node):
|
|
self.emit('LOAD_CONST', node.name)
|
|
self.visit(node.expr)
|
|
|
|
def visitGlobal(self, node):
|
|
# no code to generate
|
|
pass
|
|
|
|
def visitName(self, node):
|
|
self.set_lineno(node)
|
|
self.loadName(node.name)
|
|
|
|
def visitPass(self, node):
|
|
self.set_lineno(node)
|
|
|
|
def visitImport(self, node):
|
|
self.set_lineno(node)
|
|
level = 0 if self.graph.checkFlag(CO_FUTURE_ABSIMPORT) else -1
|
|
for name, alias in node.names:
|
|
if VERSION > 1:
|
|
self.emit('LOAD_CONST', level)
|
|
self.emit('LOAD_CONST', None)
|
|
self.emit('IMPORT_NAME', name)
|
|
mod = name.split(".")[0]
|
|
if alias:
|
|
self._resolveDots(name)
|
|
self.storeName(alias)
|
|
else:
|
|
self.storeName(mod)
|
|
|
|
def visitFrom(self, node):
|
|
self.set_lineno(node)
|
|
level = node.level
|
|
if level == 0 and not self.graph.checkFlag(CO_FUTURE_ABSIMPORT):
|
|
level = -1
|
|
fromlist = map(lambda (name, alias): name, node.names)
|
|
if VERSION > 1:
|
|
self.emit('LOAD_CONST', level)
|
|
self.emit('LOAD_CONST', tuple(fromlist))
|
|
self.emit('IMPORT_NAME', node.modname)
|
|
for name, alias in node.names:
|
|
if VERSION > 1:
|
|
if name == '*':
|
|
self.namespace = 0
|
|
self.emit('IMPORT_STAR')
|
|
# There can only be one name w/ from ... import *
|
|
assert len(node.names) == 1
|
|
return
|
|
else:
|
|
self.emit('IMPORT_FROM', name)
|
|
self._resolveDots(name)
|
|
self.storeName(alias or name)
|
|
else:
|
|
self.emit('IMPORT_FROM', name)
|
|
self.emit('POP_TOP')
|
|
|
|
def _resolveDots(self, name):
|
|
elts = name.split(".")
|
|
if len(elts) == 1:
|
|
return
|
|
for elt in elts[1:]:
|
|
self.emit('LOAD_ATTR', elt)
|
|
|
|
def visitGetattr(self, node):
|
|
self.visit(node.expr)
|
|
self.emit('LOAD_ATTR', self.mangle(node.attrname))
|
|
|
|
# next five implement assignments
|
|
|
|
def visitAssign(self, node):
|
|
self.set_lineno(node)
|
|
self.visit(node.expr)
|
|
dups = len(node.nodes) - 1
|
|
for i in range(len(node.nodes)):
|
|
elt = node.nodes[i]
|
|
if i < dups:
|
|
self.emit('DUP_TOP')
|
|
if isinstance(elt, ast.Node):
|
|
self.visit(elt)
|
|
|
|
def visitAssName(self, node):
|
|
if node.flags == 'OP_ASSIGN':
|
|
self.storeName(node.name)
|
|
elif node.flags == 'OP_DELETE':
|
|
self.set_lineno(node)
|
|
self.delName(node.name)
|
|
else:
|
|
print("oops", node.flags)
|
|
|
|
def visitAssAttr(self, node):
|
|
self.visit(node.expr)
|
|
if node.flags == 'OP_ASSIGN':
|
|
self.emit('STORE_ATTR', self.mangle(node.attrname))
|
|
elif node.flags == 'OP_DELETE':
|
|
self.emit('DELETE_ATTR', self.mangle(node.attrname))
|
|
else:
|
|
print("warning: unexpected flags:", node.flags)
|
|
print(node)
|
|
|
|
def _visitAssSequence(self, node, op='UNPACK_SEQUENCE'):
|
|
if findOp(node) != 'OP_DELETE':
|
|
self.emit(op, len(node.nodes))
|
|
for child in node.nodes:
|
|
self.visit(child)
|
|
|
|
if VERSION > 1:
|
|
visitAssTuple = _visitAssSequence
|
|
visitAssList = _visitAssSequence
|
|
else:
|
|
def visitAssTuple(self, node):
|
|
self._visitAssSequence(node, 'UNPACK_TUPLE')
|
|
|
|
def visitAssList(self, node):
|
|
self._visitAssSequence(node, 'UNPACK_LIST')
|
|
|
|
# augmented assignment
|
|
|
|
def visitAugAssign(self, node):
|
|
self.set_lineno(node)
|
|
aug_node = wrap_aug(node.node)
|
|
self.visit(aug_node, "load")
|
|
self.visit(node.expr)
|
|
self.emit(self._augmented_opcode[node.op])
|
|
self.visit(aug_node, "store")
|
|
|
|
_augmented_opcode = {
|
|
'+=' : 'INPLACE_ADD',
|
|
'-=' : 'INPLACE_SUBTRACT',
|
|
'*=' : 'INPLACE_MULTIPLY',
|
|
'/=' : 'INPLACE_TRUE_DIVIDE',
|
|
'//=': 'INPLACE_FLOOR_DIVIDE',
|
|
'%=' : 'INPLACE_MODULO',
|
|
'**=': 'INPLACE_POWER',
|
|
'>>=': 'INPLACE_RSHIFT',
|
|
'<<=': 'INPLACE_LSHIFT',
|
|
'&=' : 'INPLACE_AND',
|
|
'^=' : 'INPLACE_XOR',
|
|
'|=' : 'INPLACE_OR',
|
|
}
|
|
|
|
def visitAugName(self, node, mode):
|
|
if mode == "load":
|
|
self.loadName(node.name)
|
|
elif mode == "store":
|
|
self.storeName(node.name)
|
|
|
|
def visitAugGetattr(self, node, mode):
|
|
if mode == "load":
|
|
self.visit(node.expr)
|
|
self.emit('DUP_TOP')
|
|
self.emit('LOAD_ATTR', self.mangle(node.attrname))
|
|
elif mode == "store":
|
|
self.emit('ROT_TWO')
|
|
self.emit('STORE_ATTR', self.mangle(node.attrname))
|
|
|
|
def visitAugSlice(self, node, mode):
|
|
if mode == "load":
|
|
self.visitSlice(node, 1)
|
|
elif mode == "store":
|
|
slice = 0
|
|
if node.lower:
|
|
slice = slice | 1
|
|
if node.upper:
|
|
slice = slice | 2
|
|
if slice == 0:
|
|
self.emit('ROT_TWO')
|
|
elif slice == 3:
|
|
self.emit('ROT_FOUR')
|
|
else:
|
|
self.emit('ROT_THREE')
|
|
self.emit('STORE_SLICE+%d' % slice)
|
|
|
|
def visitAugSubscript(self, node, mode):
|
|
if mode == "load":
|
|
self.visitSubscript(node, 1)
|
|
elif mode == "store":
|
|
self.emit('ROT_THREE')
|
|
self.emit('STORE_SUBSCR')
|
|
|
|
def visitCallFunc(self, node):
|
|
self.set_lineno(node)
|
|
self.visit(node.node)
|
|
self.finish_visit_call(node)
|
|
|
|
def finish_visit_call(self, node, pos=0, kw=0):
|
|
for arg in node.args:
|
|
self.visit(arg)
|
|
if isinstance(arg, ast.Keyword):
|
|
kw = kw + 1
|
|
else:
|
|
pos = pos + 1
|
|
if node.star_args is not None:
|
|
self.visit(node.star_args)
|
|
if node.dstar_args is not None:
|
|
self.visit(node.dstar_args)
|
|
have_star = node.star_args is not None
|
|
have_dstar = node.dstar_args is not None
|
|
opcode = callfunc_opcode_info[have_star, have_dstar]
|
|
self.emit(opcode, kw << 8 | pos)
|
|
|
|
def visitReturn(self, node):
|
|
self.set_lineno(node)
|
|
self.visit(node.value)
|
|
self.emit('RETURN_VALUE')
|
|
|
|
def visitYield(self, node):
|
|
self.set_lineno(node)
|
|
self.visit(node.value)
|
|
self.emit('YIELD_VALUE')
|
|
|
|
# slice and subscript stuff
|
|
|
|
def visitSlice(self, node, aug_flag=None):
|
|
# aug_flag is used by visitAugSlice
|
|
self.visit(node.expr)
|
|
slice = 0
|
|
if node.lower:
|
|
self.visit(node.lower)
|
|
slice = slice | 1
|
|
if node.upper:
|
|
self.visit(node.upper)
|
|
slice = slice | 2
|
|
if aug_flag:
|
|
if slice == 0:
|
|
self.emit('DUP_TOP')
|
|
elif slice == 3:
|
|
self.emit('DUP_TOPX', 3)
|
|
else:
|
|
self.emit('DUP_TOPX', 2)
|
|
if node.flags == 'OP_APPLY':
|
|
self.emit('SLICE+%d' % slice)
|
|
elif node.flags == 'OP_ASSIGN':
|
|
self.emit('STORE_SLICE+%d' % slice)
|
|
elif node.flags == 'OP_DELETE':
|
|
self.emit('DELETE_SLICE+%d' % slice)
|
|
else:
|
|
print("weird slice", node.flags)
|
|
raise
|
|
|
|
def visitSubscript(self, node, aug_flag=None):
|
|
self.visit(node.expr)
|
|
for sub in node.subs:
|
|
self.visit(sub)
|
|
if len(node.subs) > 1:
|
|
self.emit('BUILD_TUPLE', len(node.subs))
|
|
if aug_flag:
|
|
self.emit('DUP_TOPX', 2)
|
|
if node.flags == 'OP_APPLY':
|
|
self.emit('BINARY_SUBSCR')
|
|
elif node.flags == 'OP_ASSIGN':
|
|
self.emit('STORE_SUBSCR')
|
|
elif node.flags == 'OP_DELETE':
|
|
self.emit('DELETE_SUBSCR')
|
|
|
|
# binary ops
|
|
|
|
def binaryOp(self, node, op):
|
|
self.visit(node.left)
|
|
self.visit(node.right)
|
|
self.emit(op)
|
|
|
|
def visitAdd(self, node):
|
|
return self.binaryOp(node, 'BINARY_ADD')
|
|
|
|
def visitSub(self, node):
|
|
return self.binaryOp(node, 'BINARY_SUBTRACT')
|
|
|
|
def visitMul(self, node):
|
|
return self.binaryOp(node, 'BINARY_MULTIPLY')
|
|
|
|
def visitDiv(self, node):
|
|
return self.binaryOp(node, 'BINARY_TRUE_DIVIDE')
|
|
|
|
def visitFloorDiv(self, node):
|
|
return self.binaryOp(node, 'BINARY_FLOOR_DIVIDE')
|
|
|
|
def visitMod(self, node):
|
|
return self.binaryOp(node, 'BINARY_MODULO')
|
|
|
|
def visitPower(self, node):
|
|
return self.binaryOp(node, 'BINARY_POWER')
|
|
|
|
def visitLeftShift(self, node):
|
|
return self.binaryOp(node, 'BINARY_LSHIFT')
|
|
|
|
def visitRightShift(self, node):
|
|
return self.binaryOp(node, 'BINARY_RSHIFT')
|
|
|
|
# unary ops
|
|
|
|
def unaryOp(self, node, op):
|
|
self.visit(node.expr)
|
|
self.emit(op)
|
|
|
|
def visitInvert(self, node):
|
|
return self.unaryOp(node, 'UNARY_INVERT')
|
|
|
|
def visitUnarySub(self, node):
|
|
return self.unaryOp(node, 'UNARY_NEGATIVE')
|
|
|
|
def visitUnaryAdd(self, node):
|
|
return self.unaryOp(node, 'UNARY_POSITIVE')
|
|
|
|
def visitUnaryInvert(self, node):
|
|
return self.unaryOp(node, 'UNARY_INVERT')
|
|
|
|
def visitNot(self, node):
|
|
return self.unaryOp(node, 'UNARY_NOT')
|
|
|
|
# bit ops
|
|
|
|
def bitOp(self, nodes, op):
|
|
self.visit(nodes[0])
|
|
for node in nodes[1:]:
|
|
self.visit(node)
|
|
self.emit(op)
|
|
|
|
def visitBitand(self, node):
|
|
return self.bitOp(node.nodes, 'BINARY_AND')
|
|
|
|
def visitBitor(self, node):
|
|
return self.bitOp(node.nodes, 'BINARY_OR')
|
|
|
|
def visitBitxor(self, node):
|
|
return self.bitOp(node.nodes, 'BINARY_XOR')
|
|
|
|
# object constructors
|
|
|
|
def visitTuple(self, node):
|
|
self.set_lineno(node)
|
|
for elt in node.nodes:
|
|
self.visit(elt)
|
|
self.emit('BUILD_TUPLE', len(node.nodes))
|
|
|
|
def visitList(self, node):
|
|
self.set_lineno(node)
|
|
for elt in node.nodes:
|
|
self.visit(elt)
|
|
self.emit('BUILD_LIST', len(node.nodes))
|
|
|
|
def visitSet(self, node):
|
|
self.set_lineno(node)
|
|
for elt in node.items:
|
|
self.visit(elt)
|
|
self.emit('BUILD_SET', len(node.items))
|
|
|
|
def visitSliceobj(self, node):
|
|
for child in node.nodes:
|
|
self.visit(child)
|
|
self.emit('BUILD_SLICE', len(node.nodes))
|
|
|
|
def visitDict(self, node):
|
|
self.set_lineno(node)
|
|
self.emit('BUILD_MAP', 0)
|
|
for k, v in node.items:
|
|
self.emit('DUP_TOP')
|
|
self.visit(k)
|
|
self.visit(v)
|
|
self.emit('ROT_THREE')
|
|
self.emit('STORE_SUBSCR')
|
|
|
|
class NestedScopeMixin:
|
|
"""Defines initClass() for nested scoping (Python 2.2-compatible)"""
|
|
def initClass(self):
|
|
self.__class__.NameFinder = LocalNameFinder
|
|
self.__class__.FunctionGen = FunctionCodeGenerator
|
|
self.__class__.ClassGen = ClassCodeGenerator
|
|
|
|
class ModuleCodeGenerator(NestedScopeMixin, CodeGenerator):
|
|
__super_init = CodeGenerator.__init__
|
|
|
|
scopes = None
|
|
|
|
def __init__(self, tree):
|
|
self.graph = pyassem.PyFlowGraph("<module>", tree.filename)
|
|
self.futures = future.find_futures(tree)
|
|
self.__super_init()
|
|
walk(tree, self)
|
|
|
|
def get_module(self):
|
|
return self
|
|
|
|
class ExpressionCodeGenerator(NestedScopeMixin, CodeGenerator):
|
|
__super_init = CodeGenerator.__init__
|
|
|
|
scopes = None
|
|
futures = ()
|
|
|
|
def __init__(self, tree):
|
|
self.graph = pyassem.PyFlowGraph("<expression>", tree.filename)
|
|
self.__super_init()
|
|
walk(tree, self)
|
|
|
|
def get_module(self):
|
|
return self
|
|
|
|
class InteractiveCodeGenerator(NestedScopeMixin, CodeGenerator):
|
|
|
|
__super_init = CodeGenerator.__init__
|
|
|
|
scopes = None
|
|
futures = ()
|
|
|
|
def __init__(self, tree):
|
|
self.graph = pyassem.PyFlowGraph("<interactive>", tree.filename)
|
|
self.__super_init()
|
|
self.set_lineno(tree)
|
|
walk(tree, self)
|
|
self.emit('RETURN_VALUE')
|
|
|
|
def get_module(self):
|
|
return self
|
|
|
|
def visitDiscard(self, node):
|
|
# XXX Discard means it's an expression. Perhaps this is a bad
|
|
# name.
|
|
self.visit(node.expr)
|
|
self.emit('PRINT_EXPR')
|
|
|
|
class AbstractFunctionCode:
|
|
optimized = 1
|
|
lambdaCount = 0
|
|
|
|
def __init__(self, func, scopes, isLambda, class_name, mod):
|
|
self.class_name = class_name
|
|
self.module = mod
|
|
if isLambda:
|
|
klass = FunctionCodeGenerator
|
|
name = "<lambda.%d>" % klass.lambdaCount
|
|
klass.lambdaCount = klass.lambdaCount + 1
|
|
else:
|
|
name = func.name
|
|
|
|
args, hasTupleArg = generateArgList(func.arguments)
|
|
kwonlyargs = generateKwonlyArgList(func.kwonlyargs)
|
|
self.graph = pyassem.PyFlowGraph(name, func.filename, args,
|
|
kwonlyargs=kwonlyargs,
|
|
optimized=1)
|
|
self.isLambda = isLambda
|
|
self.super_init()
|
|
|
|
if not isLambda and func.doc:
|
|
self.setDocstring(func.doc)
|
|
|
|
lnf = walk(func.code, self.NameFinder(args+kwonlyargs), verbose=0)
|
|
self.locals.push(lnf.getLocals())
|
|
if func.varargs:
|
|
self.graph.setFlag(CO_VARARGS)
|
|
if func.kwargs:
|
|
self.graph.setFlag(CO_VARKEYWORDS)
|
|
self.set_lineno(func)
|
|
if hasTupleArg:
|
|
self.generateArgUnpack(func.arguments)
|
|
|
|
def get_module(self):
|
|
return self.module
|
|
|
|
def finish(self):
|
|
self.graph.startExitBlock()
|
|
if not self.isLambda:
|
|
self.emit('LOAD_CONST', None)
|
|
self.emit('RETURN_VALUE')
|
|
|
|
def generateArgUnpack(self, args):
|
|
for i in range(len(args)):
|
|
arg = args[i]
|
|
if isinstance(arg, ast.NestedArgs):
|
|
self.emit('LOAD_FAST', '.%d' % (i * 2))
|
|
self.unpackSequence(tuple(_nested_names(arg)))
|
|
|
|
def unpackSequence(self, tup):
|
|
if VERSION > 1:
|
|
self.emit('UNPACK_SEQUENCE', len(tup))
|
|
else:
|
|
self.emit('UNPACK_TUPLE', len(tup))
|
|
for elt in tup:
|
|
if isinstance(elt, tuple):
|
|
self.unpackSequence(elt)
|
|
else:
|
|
self._nameOp('STORE', elt)
|
|
|
|
unpackTuple = unpackSequence
|
|
|
|
class FunctionCodeGenerator(NestedScopeMixin, AbstractFunctionCode,
|
|
CodeGenerator):
|
|
super_init = CodeGenerator.__init__ # call be other init
|
|
scopes = None
|
|
|
|
__super_init = AbstractFunctionCode.__init__
|
|
|
|
def __init__(self, func, scopes, isLambda, class_name, mod):
|
|
self.scopes = scopes
|
|
self.scope = scopes[func]
|
|
self.__super_init(func, scopes, isLambda, class_name, mod)
|
|
self.graph.setFreeVars(self.scope.get_free_vars())
|
|
self.graph.setCellVars(self.scope.get_cell_vars())
|
|
if self.scope.generator is not None:
|
|
self.graph.setFlag(CO_GENERATOR)
|
|
|
|
class GenExprCodeGenerator(NestedScopeMixin, AbstractFunctionCode,
|
|
CodeGenerator):
|
|
super_init = CodeGenerator.__init__ # call be other init
|
|
scopes = None
|
|
|
|
__super_init = AbstractFunctionCode.__init__
|
|
|
|
def __init__(self, gexp, scopes, class_name, mod):
|
|
self.scopes = scopes
|
|
self.scope = scopes[gexp]
|
|
self.__super_init(gexp, scopes, 1, class_name, mod)
|
|
self.graph.setFreeVars(self.scope.get_free_vars())
|
|
self.graph.setCellVars(self.scope.get_cell_vars())
|
|
self.graph.setFlag(CO_GENERATOR)
|
|
|
|
class AbstractClassCode:
|
|
|
|
def __init__(self, klass, scopes, module):
|
|
self.class_name = klass.name
|
|
self.module = module
|
|
self.graph = pyassem.PyFlowGraph(klass.name, klass.filename,
|
|
optimized=0, klass=1)
|
|
self.super_init()
|
|
lnf = walk(klass.code, self.NameFinder(), verbose=0)
|
|
self.locals.push(lnf.getLocals())
|
|
self.graph.setFlag(CO_NEWLOCALS)
|
|
if klass.doc:
|
|
self.setDocstring(klass.doc)
|
|
|
|
def get_module(self):
|
|
return self.module
|
|
|
|
def finish(self):
|
|
self.graph.startExitBlock()
|
|
self.emit('LOAD_CONST', None)
|
|
self.emit('RETURN_VALUE')
|
|
|
|
class ClassCodeGenerator(NestedScopeMixin, AbstractClassCode, CodeGenerator):
|
|
super_init = CodeGenerator.__init__
|
|
scopes = None
|
|
|
|
__super_init = AbstractClassCode.__init__
|
|
|
|
def __init__(self, klass, scopes, module):
|
|
self.scopes = scopes
|
|
self.scope = scopes[klass]
|
|
self.__super_init(klass, scopes, module)
|
|
self.graph.setFreeVars(self.scope.get_free_vars())
|
|
self.graph.setCellVars(self.scope.get_cell_vars())
|
|
self.set_lineno(klass)
|
|
self.emit("LOAD_GLOBAL", "__name__")
|
|
self.storeName("__module__")
|
|
if klass.doc:
|
|
self.emit("LOAD_CONST", klass.doc)
|
|
self.storeName('__doc__')
|
|
|
|
def generateArgList(arglist):
|
|
"""Generate an arg list marking TupleArgs"""
|
|
args = []
|
|
extra = []
|
|
count = 0
|
|
for i in range(len(arglist)):
|
|
elt = arglist[i]
|
|
if isinstance(elt, ast.SimpleArg):
|
|
args.append(elt.name)
|
|
elif isinstance(elt, ast.NestedArgs):
|
|
t = tuple(_nested_names(elt))
|
|
args.append(TupleArg(i * 2, t))
|
|
extra.extend(misc.flatten(t))
|
|
count = count + 1
|
|
else:
|
|
raise ValueError, "unexpect argument type:", elt
|
|
return args + extra, count
|
|
|
|
def _nested_names(elt):
|
|
for arg in elt.args:
|
|
if isinstance(arg, ast.SimpleArg):
|
|
yield arg.name
|
|
elif isinstance(arg, ast.NestedArgs):
|
|
yield tuple(_nested_names(arg))
|
|
|
|
def generateKwonlyArgList(keywordOnlyArgs):
|
|
kwonlyargs = []
|
|
for elt in keywordOnlyArgs:
|
|
assert isinstance(elt, ast.Kwarg)
|
|
kwonlyargs.append(elt.arg.name)
|
|
return kwonlyargs
|
|
|
|
def findOp(node):
|
|
"""Find the op (DELETE, LOAD, STORE) in an AssTuple tree"""
|
|
v = OpFinder()
|
|
walk(node, v, verbose=0)
|
|
return v.op
|
|
|
|
class OpFinder:
|
|
def __init__(self):
|
|
self.op = None
|
|
def visitAssName(self, node):
|
|
if self.op is None:
|
|
self.op = node.flags
|
|
elif self.op != node.flags:
|
|
raise ValueError, "mixed ops in stmt"
|
|
visitAssAttr = visitAssName
|
|
visitSubscript = visitAssName
|
|
|
|
class Delegator:
|
|
"""Base class to support delegation for augmented assignment nodes
|
|
|
|
To generator code for augmented assignments, we use the following
|
|
wrapper classes. In visitAugAssign, the left-hand expression node
|
|
is visited twice. The first time the visit uses the normal method
|
|
for that node . The second time the visit uses a different method
|
|
that generates the appropriate code to perform the assignment.
|
|
These delegator classes wrap the original AST nodes in order to
|
|
support the variant visit methods.
|
|
"""
|
|
def __init__(self, obj):
|
|
self.obj = obj
|
|
|
|
def __getattr__(self, attr):
|
|
return getattr(self.obj, attr)
|
|
|
|
class AugGetattr(Delegator):
|
|
pass
|
|
|
|
class AugName(Delegator):
|
|
pass
|
|
|
|
class AugSlice(Delegator):
|
|
pass
|
|
|
|
class AugSubscript(Delegator):
|
|
pass
|
|
|
|
wrapper = {
|
|
ast.Getattr: AugGetattr,
|
|
ast.Name: AugName,
|
|
ast.Slice: AugSlice,
|
|
ast.Subscript: AugSubscript,
|
|
}
|
|
|
|
def wrap_aug(node):
|
|
return wrapper[node.__class__](node)
|
|
|
|
if __name__ == "__main__":
|
|
for file in sys.argv[1:]:
|
|
compileFile(file)
|