summaryrefslogtreecommitdiffstats
path: root/Lib/compiler/pycodegen.py
diff options
context:
space:
mode:
authorJeremy Hylton <jeremy@alum.mit.edu>2001-04-12 06:40:42 (GMT)
committerJeremy Hylton <jeremy@alum.mit.edu>2001-04-12 06:40:42 (GMT)
commit364f9b9e2f798e4d28ed21122faffb030a6ccac5 (patch)
treebc709b0d46eeba0c737bdd7bb58371ad06d70739 /Lib/compiler/pycodegen.py
parent53ee2a94c70875b7b334ddca59900933409eaa63 (diff)
downloadcpython-364f9b9e2f798e4d28ed21122faffb030a6ccac5.zip
cpython-364f9b9e2f798e4d28ed21122faffb030a6ccac5.tar.gz
cpython-364f9b9e2f798e4d28ed21122faffb030a6ccac5.tar.bz2
Preliminary support for nested scopes
XXX Still doesn't work right for classes XXX Still doesn't do sufficient error checking
Diffstat (limited to 'Lib/compiler/pycodegen.py')
-rw-r--r--Lib/compiler/pycodegen.py305
1 files changed, 228 insertions, 77 deletions
diff --git a/Lib/compiler/pycodegen.py b/Lib/compiler/pycodegen.py
index 3ba31e4..2d4564a 100644
--- a/Lib/compiler/pycodegen.py
+++ b/Lib/compiler/pycodegen.py
@@ -9,8 +9,10 @@ import types
from cStringIO import StringIO
from compiler import ast, parse, walk
-from compiler import pyassem, misc, future
-from compiler.pyassem import CO_VARARGS, CO_VARKEYWORDS, CO_NEWLOCALS, TupleArg
+from compiler import pyassem, misc, future, symbols
+from compiler.consts import SC_LOCAL, SC_GLOBAL, SC_FREE, SC_CELL
+from compiler.pyassem import CO_VARARGS, CO_VARKEYWORDS, CO_NEWLOCALS,\
+ CO_NESTED, TupleArg
# Do we have Python 1.x or Python 2.x?
try:
@@ -46,7 +48,7 @@ class Module:
tree = parse(self.source)
root, filename = os.path.split(self.filename)
if "nested_scopes" in future.find_futures(tree):
- gen = NestedScopeCodeGenerator(filename)
+ gen = NestedScopeModuleCodeGenerator(filename)
else:
gen = ModuleCodeGenerator(filename)
walk(tree, gen, 1)
@@ -70,14 +72,71 @@ class Module:
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)
+
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
def __init__(self, filename):
-## Subclasses must define a constructor that intializes self.graph
-## before calling this init function, e.g.
-## self.graph = pyassem.PyFlowGraph()
+ if self.__initialized is None:
+ self.initClass()
+ self.__class__.__initialized = 1
+ self.checkClass()
self.filename = filename
self.locals = misc.Stack()
self.loops = misc.Stack()
@@ -86,6 +145,20 @@ class CodeGenerator:
self.last_lineno = None
self._setupGraphDelegation()
+ 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, 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
@@ -139,10 +212,15 @@ class CodeGenerator:
return 0
# The first few visitor methods handle nodes that generator new
- # code objects
+ # code objects. They use class attributes to determine what
+ # specialized code generators to use.
+
+ NameFinder = LocalNameFinder
+ FunctionGen = None
+ ClassGen = None
def visitModule(self, node):
- lnf = walk(node.node, LocalNameFinder(), 0)
+ lnf = walk(node.node, self.NameFinder(), 0)
self.locals.push(lnf.getLocals())
if node.doc:
self.fixDocstring(node.node)
@@ -159,8 +237,8 @@ class CodeGenerator:
def visitLambda(self, node):
self._visitFuncOrLambda(node, isLambda=1)
- def _visitFuncOrLambda(self, node, isLambda):
- gen = FunctionCodeGenerator(node, self.filename, isLambda)
+ def _visitFuncOrLambda(self, node, isLambda=0):
+ gen = self.FunctionGen(node, self.filename, self.scopes, isLambda)
walk(node.code, gen)
gen.finish()
self.set_lineno(node)
@@ -170,7 +248,7 @@ class CodeGenerator:
self.emit('MAKE_FUNCTION', len(node.defaults))
def visitClass(self, node):
- gen = ClassCodeGenerator(node, self.filename)
+ gen = self.ClassGen(node, self.filename, self.scopes)
if node.doc:
self.fixDocstring(node.code)
walk(node.code, gen)
@@ -180,7 +258,7 @@ class CodeGenerator:
for base in node.bases:
self.visit(base)
self.emit('BUILD_TUPLE', len(node.bases))
- self.emit('LOAD_CONST', gen.getCode())
+ self.emit('LOAD_CONST', gen)
self.emit('MAKE_FUNCTION', 0)
self.emit('CALL_FUNCTION', 0)
self.emit('BUILD_CLASS')
@@ -883,34 +961,114 @@ class CodeGenerator:
self.visit(k)
self.emit('STORE_SUBSCR')
-class ModuleCodeGenerator(CodeGenerator):
- __super_init = CodeGenerator.__init__
+class NestedScopeCodeGenerator(CodeGenerator):
__super_visitModule = CodeGenerator.visitModule
-
- def __init__(self, filename):
- # XXX <module> is ? in compile.c
- self.graph = pyassem.PyFlowGraph("<module>", filename)
- self.__super_init(filename)
- self.symbols = None
+ __super_visitClass = CodeGenerator.visitClass
+ __super__visitFuncOrLambda = CodeGenerator._visitFuncOrLambda
+
+ def parseSymbols(self, tree):
+ s = symbols.SymbolVisitor()
+ walk(tree, s)
+ return s.scopes
def visitModule(self, node):
- self.symbols = self.parseSymbols(node)
+ self.scopes = self.parseSymbols(node)
+ self.scope = self.scopes[node]
self.__super_visitModule(node)
- def parseSymbols(self, node):
- # XXX not implemented
- return None
+ def _nameOp(self, prefix, 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:
+ 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)
-class NestedScopeCodeGenerator(ModuleCodeGenerator):
- pass
+ def _visitFuncOrLambda(self, node, isLambda=0):
+ gen = self.FunctionGen(node, self.filename, self.scopes, isLambda)
+ walk(node.code, gen)
+ gen.finish()
+ self.set_lineno(node)
+ for default in node.defaults:
+ self.visit(default)
+ frees = gen.scope.get_free_vars()
+ if frees:
+ for name in frees:
+ self.emit('LOAD_CLOSURE', name)
+ self.emit('LOAD_CONST', gen)
+ self.emit('MAKE_CLOSURE', len(node.defaults))
+ else:
+ self.emit('LOAD_CONST', gen)
+ self.emit('MAKE_FUNCTION', len(node.defaults))
-class FunctionCodeGenerator(CodeGenerator):
- super_init = CodeGenerator.__init__
+ def visitClass(self, node):
+ gen = self.ClassGen(node, self.filename, self.scopes)
+ if node.doc:
+ self.fixDocstring(node.code)
+ walk(node.code, gen)
+ gen.finish()
+ self.set_lineno(node)
+ self.emit('LOAD_CONST', node.name)
+ for base in node.bases:
+ self.visit(base)
+ self.emit('BUILD_TUPLE', len(node.bases))
+ frees = gen.scope.get_free_vars()
+ for name in frees:
+ self.emit('LOAD_CLOSURE', name)
+ self.emit('LOAD_CONST', gen)
+ if frees:
+ self.emit('MAKE_CLOSURE', 0)
+ else:
+ self.emit('MAKE_FUNCTION', 0)
+ self.emit('CALL_FUNCTION', 0)
+ self.emit('BUILD_CLASS')
+ self.storeName(node.name)
+
+
+class LGBScopeMixin:
+ """Defines initClass() for Python 2.1-compatible scoping"""
+ def initClass(self):
+ self.__class__.NameFinder = LocalNameFinder
+ self.__class__.FunctionGen = FunctionCodeGenerator
+ self.__class__.ClassGen = ClassCodeGenerator
+
+class NestedScopeMixin:
+ """Defines initClass() for nested scoping (Python 2.2-compatible)"""
+ def initClass(self):
+ self.__class__.NameFinder = LocalNameFinder
+ self.__class__.FunctionGen = NestedFunctionCodeGenerator
+ self.__class__.ClassGen = NestedClassCodeGenerator
+
+class ModuleCodeGenerator(LGBScopeMixin, CodeGenerator):
+ __super_init = CodeGenerator.__init__
+ scopes = None
+
+ def __init__(self, filename):
+ self.graph = pyassem.PyFlowGraph("<module>", filename)
+ self.__super_init(filename)
+
+class NestedScopeModuleCodeGenerator(NestedScopeMixin,
+ NestedScopeCodeGenerator):
+ __super_init = CodeGenerator.__init__
+
+ def __init__(self, filename):
+ self.graph = pyassem.PyFlowGraph("<module>", filename)
+ self.__super_init(filename)
+ self.graph.setFlag(CO_NESTED)
+
+class AbstractFunctionCode:
optimized = 1
lambdaCount = 0
- def __init__(self, func, filename, isLambda=0):
+ def __init__(self, func, filename, scopes, isLambda):
if isLambda:
klass = FunctionCodeGenerator
name = "<lambda.%d>" % klass.lambdaCount
@@ -926,7 +1084,7 @@ class FunctionCodeGenerator(CodeGenerator):
if not isLambda and func.doc:
self.setDocstring(func.doc)
- lnf = walk(func.code, LocalNameFinder(args), 0)
+ lnf = walk(func.code, self.NameFinder(args), 0)
self.locals.push(lnf.getLocals())
if func.varargs:
self.graph.setFlag(CO_VARARGS)
@@ -963,14 +1121,32 @@ class FunctionCodeGenerator(CodeGenerator):
unpackTuple = unpackSequence
-class ClassCodeGenerator(CodeGenerator):
- super_init = CodeGenerator.__init__
+class FunctionCodeGenerator(LGBScopeMixin, AbstractFunctionCode,
+ CodeGenerator):
+ super_init = CodeGenerator.__init__ # call be other init
+ scopes = None
+
+class NestedFunctionCodeGenerator(AbstractFunctionCode,
+ NestedScopeMixin,
+ NestedScopeCodeGenerator):
+ super_init = NestedScopeCodeGenerator.__init__ # call be other init
+ __super_init = AbstractFunctionCode.__init__
+
+ def __init__(self, func, filename, scopes, isLambda):
+ self.scopes = scopes
+ self.scope = scopes[func]
+ self.__super_init(func, filename, scopes, isLambda)
+ self.graph.setFreeVars(self.scope.get_free_vars())
+ self.graph.setCellVars(self.scope.get_cell_vars())
+ self.graph.setFlag(CO_NESTED)
+
+class AbstractClassCode:
- def __init__(self, klass, filename):
+ def __init__(self, klass, filename, scopes):
self.graph = pyassem.PyFlowGraph(klass.name, filename,
optimized=0)
self.super_init(filename)
- lnf = walk(klass.code, LocalNameFinder(), 0)
+ lnf = walk(klass.code, self.NameFinder(), 0)
self.locals.push(lnf.getLocals())
self.graph.setFlag(CO_NEWLOCALS)
if klass.doc:
@@ -981,6 +1157,24 @@ class ClassCodeGenerator(CodeGenerator):
self.emit('LOAD_LOCALS')
self.emit('RETURN_VALUE')
+class ClassCodeGenerator(LGBScopeMixin, AbstractClassCode, CodeGenerator):
+ super_init = CodeGenerator.__init__
+ scopes = None
+
+class NestedClassCodeGenerator(AbstractClassCode,
+ NestedScopeMixin,
+ NestedScopeCodeGenerator):
+ super_init = NestedScopeCodeGenerator.__init__ # call be other init
+ __super_init = AbstractClassCode.__init__
+
+ def __init__(self, klass, filename, scopes):
+ self.scopes = scopes
+ self.scope = scopes[klass]
+ self.__super_init(klass, filename, scopes)
+ self.graph.setFreeVars(self.scope.get_free_vars())
+ self.graph.setCellVars(self.scope.get_cell_vars())
+ self.graph.setFlag(CO_NESTED)
+
def generateArgList(arglist):
"""Generate an arg list marking TupleArgs"""
args = []
@@ -997,49 +1191,6 @@ def generateArgList(arglist):
raise ValueError, "unexpect argument type:", elt
return args + extra, count
-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 findOp(node):
"""Find the op (DELETE, LOAD, STORE) in an AssTuple tree"""
v = OpFinder()