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_IMPLICIT, SC_GLOBAL_EXPLICIT, \
from compiler.consts import (CO_VARARGS, CO_VARKEYWORDS, CO_NEWLOCALS,
CO_NESTED, CO_GENERATOR, CO_FUTURE_DIVISION,
CO_FUTURE_ABSIMPORT, CO_FUTURE_WITH_STATEMENT, CO_FUTURE_PRINT_FUNCTION)
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?
VERSION = sys.version_info[0]
# (Have *args, Have **args) : opcode
(1,0) : "CALL_FUNCTION_VAR",
(0,1) : "CALL_FUNCTION_KW",
(1,1) : "CALL_FUNCTION_VAR_KW",
def compileFile(filename, display=0):
mod = Module(buf, filename)
f = open(filename + "c", "wb")
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"
gen = Interactive(source, filename)
gen = Module(source, filename)
gen = Expression(source, filename)
raise ValueError("compile() 3rd arg must be 'exec' or "
class AbstractCompileMode:
mode = None # defined by subclass
def __init__(self, source, filename):
tree = parse(self.source, self.mode)
misc.set_filename(self.filename, tree)
pass # implemented by subclass
class Expression(AbstractCompileMode):
gen = ExpressionCodeGenerator(tree)
self.code = gen.getCode()
class Interactive(AbstractCompileMode):
gen = InteractiveCodeGenerator(tree)
self.code = gen.getCode()
class Module(AbstractCompileMode):
def compile(self, display=0):
gen = ModuleCodeGenerator(tree)
print pprint.pprint(tree)
self.code = gen.getCode()
f.write(self.getPycHeader())
marshal.dump(self.code, f)
# 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
"""Find local names in scope"""
def __init__(self, names=()):
self.globals = misc.Set()
# XXX list comprehensions and for loops
for elt in self.globals.elements():
if self.names.has_elt(elt):
def visitDict(self, node):
def visitGlobal(self, node):
def visitFunction(self, node):
self.names.add(node.name)
def visitLambda(self, node):
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):
"""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
optimized = 0 # is namespace access optimized?
class_name = None # provide default for instance variable
if self.__initialized is None:
self.__class__.__initialized = 1
self.locals = misc.Stack()
self.setups = misc.Stack()
self._setupGraphDelegation()
self._div_op = "BINARY_DIVIDE"
# XXX set flags based on future features
futures = self.get_module().futures
if feature == "division":
self.graph.setFlag(CO_FUTURE_DIVISION)
self._div_op = "BINARY_TRUE_DIVIDE"
elif feature == "absolute_import":
self.graph.setFlag(CO_FUTURE_ABSIMPORT)
elif feature == "with_statement":
self.graph.setFlag(CO_FUTURE_WITH_STATEMENT)
elif feature == "print_function":
self.graph.setFlag(CO_FUTURE_PRINT_FUNCTION)
"""This method is called once for each class"""
"""Verify that class is constructed correctly"""
assert hasattr(self, 'graph')
assert getattr(self, 'NameFinder')
assert getattr(self, 'FunctionGen')
assert getattr(self, 'ClassGen')
except AssertionError, 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
"""Return a code object"""
return self.graph.getCode()
if self.class_name is not None:
return misc.mangle(name, self.class_name)
def parseSymbols(self, tree):
s = symbols.SymbolVisitor()
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)
self._nameOp('DELETE', name)
def _nameOp(self, prefix, name):
scope = self.scope.check_name(name)
self.emit(prefix + '_NAME', name)
self.emit(prefix + '_FAST', name)
elif scope == SC_GLOBAL_EXPLICIT:
self.emit(prefix + '_GLOBAL', name)
elif scope == SC_GLOBAL_IMPLICIT:
self.emit(prefix + '_NAME', name)
self.emit(prefix + '_GLOBAL', name)
elif scope == SC_FREE or scope == SC_CELL:
self.emit(prefix + '_DEREF', name)
raise RuntimeError, "unsupported scope for var %s: %d" % \
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.
self.emit(prefix + '_FAST', name)
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
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
self.emit('SET_LINENO', lineno)
self.last_lineno = lineno
# 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
def visitModule(self, node):
self.scopes = self.parseSymbols(node)
self.scope = self.scopes[node]
self.emit('SET_LINENO', 0)
self.emit('LOAD_CONST', node.doc)
self.storeName('__doc__')
lnf = walk(node.node, self.NameFinder(), verbose=0)
self.locals.push(lnf.getLocals())
self.emit('LOAD_CONST', None)
self.emit('RETURN_VALUE')
def visitExpression(self, node):
self.scopes = self.parseSymbols(node)
self.scope = self.scopes[node]
self.emit('RETURN_VALUE')
def visitFunction(self, node):
self._visitFuncOrLambda(node, isLambda=0)
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:
ndecorators = len(node.decorators.nodes)
gen = self.FunctionGen(node, self.scopes, isLambda,
self.class_name, self.get_module())
for default in node.defaults:
self._makeClosure(gen, len(node.defaults))
for i in range(ndecorators):
self.emit('CALL_FUNCTION', 1)
def visitClass(self, node):
gen = self.ClassGen(node, self.scopes,
self.emit('LOAD_CONST', node.name)
self.emit('BUILD_TUPLE', len(node.bases))
self._makeClosure(gen, 0)
self.emit('CALL_FUNCTION', 0)
self.storeName(node.name)
# The rest are standard visitor methods
# The next few implement control-flow statements
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
nextTest = self.newBlock()
self.emit('POP_JUMP_IF_FALSE', nextTest)
self.emit('JUMP_FORWARD', end)
self.startBlock(nextTest)
def visitWhile(self, node):
self.emit('SETUP_LOOP', after)
self.setups.push((LOOP, loop))
self.set_lineno(node, force=True)
self.emit('POP_JUMP_IF_FALSE', else_ or after)
self.emit('JUMP_ABSOLUTE', loop)
self.startBlock(else_) # or just the POPs if not else clause
def visitFor(self, node):
self.setups.push((LOOP, start))
self.emit('SETUP_LOOP', after)
self.set_lineno(node, force=1)
self.emit('FOR_ITER', anchor)
self.emit('JUMP_ABSOLUTE', start)
def visitBreak(self, node):
raise SyntaxError, "'break' outside loop (%s, %d)" % \
(node.filename, node.lineno)
def visitContinue(self, node):
raise SyntaxError, "'continue' outside loop (%s, %d)" % \
(node.filename, node.lineno)
kind, block = self.setups.top()
self.emit('JUMP_ABSOLUTE', block)
elif kind == EXCEPT or kind == TRY_FINALLY: