Preliminary support for nested scopes

XXX Still doesn't work right for classes
XXX Still doesn't do sufficient error checking
This commit is contained in:
Jeremy Hylton 2001-04-12 06:40:42 +00:00
parent 53ee2a94c7
commit 364f9b9e2f
6 changed files with 792 additions and 178 deletions

View File

@ -99,12 +99,6 @@ class FlowGraph:
if not self.exit in order: if not self.exit in order:
order.append(self.exit) order.append(self.exit)
## for b in order:
## print repr(b)
## print "\t", b.get_children()
## print b
## print
return order return order
def getBlocks(self): def getBlocks(self):
@ -222,6 +216,7 @@ CO_OPTIMIZED = 0x0001
CO_NEWLOCALS = 0x0002 CO_NEWLOCALS = 0x0002
CO_VARARGS = 0x0004 CO_VARARGS = 0x0004
CO_VARKEYWORDS = 0x0008 CO_VARKEYWORDS = 0x0008
CO_NESTED = 0x0010
# the FlowGraph is transformed in place; it exists in one of these states # the FlowGraph is transformed in place; it exists in one of these states
RAW = "RAW" RAW = "RAW"
@ -245,6 +240,15 @@ class PyFlowGraph(FlowGraph):
self.flags = 0 self.flags = 0
self.consts = [] self.consts = []
self.names = [] self.names = []
# Free variables found by the symbol table scan, including
# variables used only in nested scopes, are included here.
self.freevars = []
self.cellvars = []
# The closure list is used to track the order of cell
# variables and free variables in the resulting code object.
# The offsets used by LOAD_CLOSURE/LOAD_DEREF refer to both
# kinds of variables.
self.closure = []
self.varnames = list(args) or [] self.varnames = list(args) or []
for i in range(len(self.varnames)): for i in range(len(self.varnames)):
var = self.varnames[i] var = self.varnames[i]
@ -260,6 +264,12 @@ class PyFlowGraph(FlowGraph):
if flag == CO_VARARGS: if flag == CO_VARARGS:
self.argcount = self.argcount - 1 self.argcount = self.argcount - 1
def setFreeVars(self, names):
self.freevars = list(names)
def setCellVars(self, names):
self.cellvars = names
def getCode(self): def getCode(self):
"""Get a Python code object""" """Get a Python code object"""
if self.stage == RAW: if self.stage == RAW:
@ -335,6 +345,7 @@ class PyFlowGraph(FlowGraph):
"""Convert arguments from symbolic to concrete form""" """Convert arguments from symbolic to concrete form"""
assert self.stage == FLAT assert self.stage == FLAT
self.consts.insert(0, self.docstring) self.consts.insert(0, self.docstring)
self.sort_cellvars()
for i in range(len(self.insts)): for i in range(len(self.insts)):
t = self.insts[i] t = self.insts[i]
if len(t) == 2: if len(t) == 2:
@ -345,6 +356,19 @@ class PyFlowGraph(FlowGraph):
self.insts[i] = opname, conv(self, oparg) self.insts[i] = opname, conv(self, oparg)
self.stage = CONV self.stage = CONV
def sort_cellvars(self):
"""Sort cellvars in the order of varnames and prune from freevars.
"""
cells = {}
for name in self.cellvars:
cells[name] = 1
self.cellvars = [name for name in self.varnames
if cells.has_key(name)]
for name in self.cellvars:
del cells[name]
self.cellvars = self.cellvars + cells.keys()
self.closure = self.cellvars + self.freevars
def _lookupName(self, name, list): def _lookupName(self, name, list):
"""Return index of name in list, appending if necessary""" """Return index of name in list, appending if necessary"""
t = type(name) t = type(name)
@ -382,6 +406,17 @@ class PyFlowGraph(FlowGraph):
_convert_STORE_GLOBAL = _convert_NAME _convert_STORE_GLOBAL = _convert_NAME
_convert_DELETE_GLOBAL = _convert_NAME _convert_DELETE_GLOBAL = _convert_NAME
def _convert_DEREF(self, arg):
self._lookupName(arg, self.names)
self._lookupName(arg, self.varnames)
return self._lookupName(arg, self.closure)
_convert_LOAD_DEREF = _convert_DEREF
_convert_STORE_DEREF = _convert_DEREF
def _convert_LOAD_CLOSURE(self, arg):
self._lookupName(arg, self.varnames)
return self._lookupName(arg, self.closure)
_cmp = list(dis.cmp_op) _cmp = list(dis.cmp_op)
def _convert_COMPARE_OP(self, arg): def _convert_COMPARE_OP(self, arg):
return self._cmp.index(arg) return self._cmp.index(arg)
@ -432,7 +467,8 @@ class PyFlowGraph(FlowGraph):
self.lnotab.getCode(), self.getConsts(), self.lnotab.getCode(), self.getConsts(),
tuple(self.names), tuple(self.varnames), tuple(self.names), tuple(self.varnames),
self.filename, self.name, self.lnotab.firstline, self.filename, self.name, self.lnotab.firstline,
self.lnotab.getTable()) self.lnotab.getTable(), tuple(self.freevars),
tuple(self.cellvars))
def getConsts(self): def getConsts(self):
"""Return a tuple for the const slot of the code object """Return a tuple for the const slot of the code object

View File

@ -9,8 +9,10 @@ import types
from cStringIO import StringIO from cStringIO import StringIO
from compiler import ast, parse, walk from compiler import ast, parse, walk
from compiler import pyassem, misc, future from compiler import pyassem, misc, future, symbols
from compiler.pyassem import CO_VARARGS, CO_VARKEYWORDS, CO_NEWLOCALS, TupleArg 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? # Do we have Python 1.x or Python 2.x?
try: try:
@ -46,7 +48,7 @@ class Module:
tree = parse(self.source) tree = parse(self.source)
root, filename = os.path.split(self.filename) root, filename = os.path.split(self.filename)
if "nested_scopes" in future.find_futures(tree): if "nested_scopes" in future.find_futures(tree):
gen = NestedScopeCodeGenerator(filename) gen = NestedScopeModuleCodeGenerator(filename)
else: else:
gen = ModuleCodeGenerator(filename) gen = ModuleCodeGenerator(filename)
walk(tree, gen, 1) walk(tree, gen, 1)
@ -70,14 +72,71 @@ class Module:
mtime = struct.pack('i', mtime) mtime = struct.pack('i', mtime)
return self.MAGIC + 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: 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? optimized = 0 # is namespace access optimized?
__initialized = None
def __init__(self, filename): def __init__(self, filename):
## Subclasses must define a constructor that intializes self.graph if self.__initialized is None:
## before calling this init function, e.g. self.initClass()
## self.graph = pyassem.PyFlowGraph() self.__class__.__initialized = 1
self.checkClass()
self.filename = filename self.filename = filename
self.locals = misc.Stack() self.locals = misc.Stack()
self.loops = misc.Stack() self.loops = misc.Stack()
@ -86,6 +145,20 @@ class CodeGenerator:
self.last_lineno = None self.last_lineno = None
self._setupGraphDelegation() 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): def _setupGraphDelegation(self):
self.emit = self.graph.emit self.emit = self.graph.emit
self.newBlock = self.graph.newBlock self.newBlock = self.graph.newBlock
@ -139,10 +212,15 @@ class CodeGenerator:
return 0 return 0
# The first few visitor methods handle nodes that generator new # 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): def visitModule(self, node):
lnf = walk(node.node, LocalNameFinder(), 0) lnf = walk(node.node, self.NameFinder(), 0)
self.locals.push(lnf.getLocals()) self.locals.push(lnf.getLocals())
if node.doc: if node.doc:
self.fixDocstring(node.node) self.fixDocstring(node.node)
@ -159,8 +237,8 @@ class CodeGenerator:
def visitLambda(self, node): def visitLambda(self, node):
self._visitFuncOrLambda(node, isLambda=1) self._visitFuncOrLambda(node, isLambda=1)
def _visitFuncOrLambda(self, node, isLambda): def _visitFuncOrLambda(self, node, isLambda=0):
gen = FunctionCodeGenerator(node, self.filename, isLambda) gen = self.FunctionGen(node, self.filename, self.scopes, isLambda)
walk(node.code, gen) walk(node.code, gen)
gen.finish() gen.finish()
self.set_lineno(node) self.set_lineno(node)
@ -170,7 +248,7 @@ class CodeGenerator:
self.emit('MAKE_FUNCTION', len(node.defaults)) self.emit('MAKE_FUNCTION', len(node.defaults))
def visitClass(self, node): def visitClass(self, node):
gen = ClassCodeGenerator(node, self.filename) gen = self.ClassGen(node, self.filename, self.scopes)
if node.doc: if node.doc:
self.fixDocstring(node.code) self.fixDocstring(node.code)
walk(node.code, gen) walk(node.code, gen)
@ -180,7 +258,7 @@ class CodeGenerator:
for base in node.bases: for base in node.bases:
self.visit(base) self.visit(base)
self.emit('BUILD_TUPLE', len(node.bases)) 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('MAKE_FUNCTION', 0)
self.emit('CALL_FUNCTION', 0) self.emit('CALL_FUNCTION', 0)
self.emit('BUILD_CLASS') self.emit('BUILD_CLASS')
@ -883,34 +961,114 @@ class CodeGenerator:
self.visit(k) self.visit(k)
self.emit('STORE_SUBSCR') self.emit('STORE_SUBSCR')
class ModuleCodeGenerator(CodeGenerator): class NestedScopeCodeGenerator(CodeGenerator):
__super_init = CodeGenerator.__init__
__super_visitModule = CodeGenerator.visitModule __super_visitModule = CodeGenerator.visitModule
__super_visitClass = CodeGenerator.visitClass
__super__visitFuncOrLambda = CodeGenerator._visitFuncOrLambda
def __init__(self, filename): def parseSymbols(self, tree):
# XXX <module> is ? in compile.c s = symbols.SymbolVisitor()
self.graph = pyassem.PyFlowGraph("<module>", filename) walk(tree, s)
self.__super_init(filename) return s.scopes
self.symbols = None
def visitModule(self, node): def visitModule(self, node):
self.symbols = self.parseSymbols(node) self.scopes = self.parseSymbols(node)
self.scope = self.scopes[node]
self.__super_visitModule(node) self.__super_visitModule(node)
def parseSymbols(self, node): def _nameOp(self, prefix, name):
# XXX not implemented scope = self.scope.check_name(name)
return None 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): def _visitFuncOrLambda(self, node, isLambda=0):
pass 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): def visitClass(self, node):
super_init = CodeGenerator.__init__ 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 optimized = 1
lambdaCount = 0 lambdaCount = 0
def __init__(self, func, filename, isLambda=0): def __init__(self, func, filename, scopes, isLambda):
if isLambda: if isLambda:
klass = FunctionCodeGenerator klass = FunctionCodeGenerator
name = "<lambda.%d>" % klass.lambdaCount name = "<lambda.%d>" % klass.lambdaCount
@ -926,7 +1084,7 @@ class FunctionCodeGenerator(CodeGenerator):
if not isLambda and func.doc: if not isLambda and func.doc:
self.setDocstring(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()) self.locals.push(lnf.getLocals())
if func.varargs: if func.varargs:
self.graph.setFlag(CO_VARARGS) self.graph.setFlag(CO_VARARGS)
@ -963,14 +1121,32 @@ class FunctionCodeGenerator(CodeGenerator):
unpackTuple = unpackSequence unpackTuple = unpackSequence
class ClassCodeGenerator(CodeGenerator): class FunctionCodeGenerator(LGBScopeMixin, AbstractFunctionCode,
super_init = CodeGenerator.__init__ CodeGenerator):
super_init = CodeGenerator.__init__ # call be other init
scopes = None
def __init__(self, klass, filename): 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, scopes):
self.graph = pyassem.PyFlowGraph(klass.name, filename, self.graph = pyassem.PyFlowGraph(klass.name, filename,
optimized=0) optimized=0)
self.super_init(filename) self.super_init(filename)
lnf = walk(klass.code, LocalNameFinder(), 0) lnf = walk(klass.code, self.NameFinder(), 0)
self.locals.push(lnf.getLocals()) self.locals.push(lnf.getLocals())
self.graph.setFlag(CO_NEWLOCALS) self.graph.setFlag(CO_NEWLOCALS)
if klass.doc: if klass.doc:
@ -981,6 +1157,24 @@ class ClassCodeGenerator(CodeGenerator):
self.emit('LOAD_LOCALS') self.emit('LOAD_LOCALS')
self.emit('RETURN_VALUE') 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): def generateArgList(arglist):
"""Generate an arg list marking TupleArgs""" """Generate an arg list marking TupleArgs"""
args = [] args = []
@ -997,49 +1191,6 @@ def generateArgList(arglist):
raise ValueError, "unexpect argument type:", elt raise ValueError, "unexpect argument type:", elt
return args + extra, count 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): def findOp(node):
"""Find the op (DELETE, LOAD, STORE) in an AssTuple tree""" """Find the op (DELETE, LOAD, STORE) in an AssTuple tree"""
v = OpFinder() v = OpFinder()

View File

@ -1,8 +1,11 @@
"""Module symbol-table generator""" """Module symbol-table generator"""
from compiler import ast from compiler import ast
from compiler.consts import SC_LOCAL, SC_GLOBAL, SC_FREE, SC_CELL, SC_UNKNOWN
import types import types
import sys
MANGLE_LEN = 256 MANGLE_LEN = 256
class Scope: class Scope:
@ -14,7 +17,12 @@ class Scope:
self.uses = {} self.uses = {}
self.globals = {} self.globals = {}
self.params = {} self.params = {}
self.frees = {}
self.cells = {}
self.children = [] self.children = []
# nested is true if the class could contain free variables,
# i.e. if it is nested within another function.
self.nested = None
self.klass = None self.klass = None
if klass is not None: if klass is not None:
for i in range(len(klass)): for i in range(len(klass)):
@ -70,13 +78,112 @@ class Scope:
def get_children(self): def get_children(self):
return self.children return self.children
def DEBUG(self):
return
print >> sys.stderr, self.name, self.nested and "nested" or ""
print >> sys.stderr, "\tglobals: ", self.globals
print >> sys.stderr, "\tcells: ", self.cells
print >> sys.stderr, "\tdefs: ", self.defs
print >> sys.stderr, "\tuses: ", self.uses
print >> sys.stderr, "\tfrees:", self.frees
def check_name(self, name):
"""Return scope of name.
The scope of a name could be LOCAL, GLOBAL, FREE, or CELL.
"""
if self.globals.has_key(name):
return SC_GLOBAL
if self.cells.has_key(name):
return SC_CELL
if self.defs.has_key(name):
return SC_LOCAL
if self.nested and (self.frees.has_key(name) or
self.uses.has_key(name)):
return SC_FREE
if self.nested:
return SC_UNKNOWN
else:
return SC_GLOBAL
def get_free_vars(self):
if not self.nested:
return ()
free = {}
free.update(self.frees)
for name in self.uses.keys():
if not (self.defs.has_key(name) or
self.globals.has_key(name)):
free[name] = 1
return free.keys()
def handle_children(self):
for child in self.children:
frees = child.get_free_vars()
globals = self.add_frees(frees)
for name in globals:
child.force_global(name)
def force_global(self, name):
"""Force name to be global in scope.
Some child of the current node had a free reference to name.
When the child was processed, it was labelled a free
variable. Now that all its enclosing scope have been
processed, the name is known to be a global or builtin. So
walk back down the child chain and set the name to be global
rather than free.
Be careful to stop if a child does not think the name is
free.
"""
self.globals[name] = 1
if self.frees.has_key(name):
del self.frees[name]
for child in self.children:
if child.check_name(name) == SC_FREE:
child.force_global(name)
def add_frees(self, names):
"""Process list of free vars from nested scope.
Returns a list of names that are either 1) declared global in the
parent or 2) undefined in a top-level parent. In either case,
the nested scope should treat them as globals.
"""
child_globals = []
for name in names:
sc = self.check_name(name)
if self.nested:
if sc == SC_UNKNOWN or sc == SC_FREE \
or isinstance(self, ClassScope):
self.frees[name] = 1
elif sc == SC_GLOBAL:
child_globals.append(name)
elif isinstance(self, FunctionScope) and sc == SC_LOCAL:
self.cells[name] = 1
else:
child_globals.append(name)
else:
if sc == SC_LOCAL:
self.cells[name] = 1
else:
child_globals.append(name)
return child_globals
def get_cell_vars(self):
return self.cells.keys()
class ModuleScope(Scope): class ModuleScope(Scope):
__super_init = Scope.__init__ __super_init = Scope.__init__
def __init__(self): def __init__(self):
self.__super_init("global", self) self.__super_init("global", self)
class LambdaScope(Scope): class FunctionScope(Scope):
pass
class LambdaScope(FunctionScope):
__super_init = Scope.__init__ __super_init = Scope.__init__
__counter = 1 __counter = 1
@ -86,9 +193,6 @@ class LambdaScope(Scope):
self.__counter += 1 self.__counter += 1
self.__super_init("lambda.%d" % i, module, klass) self.__super_init("lambda.%d" % i, module, klass)
class FunctionScope(Scope):
pass
class ClassScope(Scope): class ClassScope(Scope):
__super_init = Scope.__init__ __super_init = Scope.__init__
@ -111,17 +215,24 @@ class SymbolVisitor:
for n in node.defaults: for n in node.defaults:
self.visit(n, parent) self.visit(n, parent)
scope = FunctionScope(node.name, self.module, self.klass) scope = FunctionScope(node.name, self.module, self.klass)
if parent.nested or isinstance(parent, FunctionScope):
scope.nested = 1
self.scopes[node] = scope self.scopes[node] = scope
self._do_args(scope, node.argnames) self._do_args(scope, node.argnames)
self.visit(node.code, scope) self.visit(node.code, scope)
self.handle_free_vars(scope, parent)
scope.DEBUG()
def visitLambda(self, node, parent): def visitLambda(self, node, parent):
for n in node.defaults: for n in node.defaults:
self.visit(n, parent) self.visit(n, parent)
scope = LambdaScope(self.module, self.klass) scope = LambdaScope(self.module, self.klass)
if parent.nested or isinstance(parent, FunctionScope):
scope.nested = 1
self.scopes[node] = scope self.scopes[node] = scope
self._do_args(scope, node.argnames) self._do_args(scope, node.argnames)
self.visit(node.code, scope) self.visit(node.code, scope)
self.handle_free_vars(scope, parent)
def _do_args(self, scope, args): def _do_args(self, scope, args):
for name in args: for name in args:
@ -130,16 +241,25 @@ class SymbolVisitor:
else: else:
scope.add_param(name) scope.add_param(name)
def handle_free_vars(self, scope, parent):
parent.add_child(scope)
if scope.children:
scope.DEBUG()
scope.handle_children()
def visitClass(self, node, parent): def visitClass(self, node, parent):
parent.add_def(node.name) parent.add_def(node.name)
for n in node.bases: for n in node.bases:
self.visit(n, parent) self.visit(n, parent)
scope = ClassScope(node.name, self.module) scope = ClassScope(node.name, self.module)
if parent.nested or isinstance(parent, FunctionScope):
scope.nested = 1
self.scopes[node] = scope self.scopes[node] = scope
prev = self.klass prev = self.klass
self.klass = node.name self.klass = node.name
self.visit(node.code, scope) self.visit(node.code, scope)
self.klass = prev self.klass = prev
self.handle_free_vars(scope, parent)
# name can be a def or a use # name can be a def or a use

View File

@ -99,12 +99,6 @@ class FlowGraph:
if not self.exit in order: if not self.exit in order:
order.append(self.exit) order.append(self.exit)
## for b in order:
## print repr(b)
## print "\t", b.get_children()
## print b
## print
return order return order
def getBlocks(self): def getBlocks(self):
@ -222,6 +216,7 @@ CO_OPTIMIZED = 0x0001
CO_NEWLOCALS = 0x0002 CO_NEWLOCALS = 0x0002
CO_VARARGS = 0x0004 CO_VARARGS = 0x0004
CO_VARKEYWORDS = 0x0008 CO_VARKEYWORDS = 0x0008
CO_NESTED = 0x0010
# the FlowGraph is transformed in place; it exists in one of these states # the FlowGraph is transformed in place; it exists in one of these states
RAW = "RAW" RAW = "RAW"
@ -245,6 +240,15 @@ class PyFlowGraph(FlowGraph):
self.flags = 0 self.flags = 0
self.consts = [] self.consts = []
self.names = [] self.names = []
# Free variables found by the symbol table scan, including
# variables used only in nested scopes, are included here.
self.freevars = []
self.cellvars = []
# The closure list is used to track the order of cell
# variables and free variables in the resulting code object.
# The offsets used by LOAD_CLOSURE/LOAD_DEREF refer to both
# kinds of variables.
self.closure = []
self.varnames = list(args) or [] self.varnames = list(args) or []
for i in range(len(self.varnames)): for i in range(len(self.varnames)):
var = self.varnames[i] var = self.varnames[i]
@ -260,6 +264,12 @@ class PyFlowGraph(FlowGraph):
if flag == CO_VARARGS: if flag == CO_VARARGS:
self.argcount = self.argcount - 1 self.argcount = self.argcount - 1
def setFreeVars(self, names):
self.freevars = list(names)
def setCellVars(self, names):
self.cellvars = names
def getCode(self): def getCode(self):
"""Get a Python code object""" """Get a Python code object"""
if self.stage == RAW: if self.stage == RAW:
@ -335,6 +345,7 @@ class PyFlowGraph(FlowGraph):
"""Convert arguments from symbolic to concrete form""" """Convert arguments from symbolic to concrete form"""
assert self.stage == FLAT assert self.stage == FLAT
self.consts.insert(0, self.docstring) self.consts.insert(0, self.docstring)
self.sort_cellvars()
for i in range(len(self.insts)): for i in range(len(self.insts)):
t = self.insts[i] t = self.insts[i]
if len(t) == 2: if len(t) == 2:
@ -345,6 +356,19 @@ class PyFlowGraph(FlowGraph):
self.insts[i] = opname, conv(self, oparg) self.insts[i] = opname, conv(self, oparg)
self.stage = CONV self.stage = CONV
def sort_cellvars(self):
"""Sort cellvars in the order of varnames and prune from freevars.
"""
cells = {}
for name in self.cellvars:
cells[name] = 1
self.cellvars = [name for name in self.varnames
if cells.has_key(name)]
for name in self.cellvars:
del cells[name]
self.cellvars = self.cellvars + cells.keys()
self.closure = self.cellvars + self.freevars
def _lookupName(self, name, list): def _lookupName(self, name, list):
"""Return index of name in list, appending if necessary""" """Return index of name in list, appending if necessary"""
t = type(name) t = type(name)
@ -382,6 +406,17 @@ class PyFlowGraph(FlowGraph):
_convert_STORE_GLOBAL = _convert_NAME _convert_STORE_GLOBAL = _convert_NAME
_convert_DELETE_GLOBAL = _convert_NAME _convert_DELETE_GLOBAL = _convert_NAME
def _convert_DEREF(self, arg):
self._lookupName(arg, self.names)
self._lookupName(arg, self.varnames)
return self._lookupName(arg, self.closure)
_convert_LOAD_DEREF = _convert_DEREF
_convert_STORE_DEREF = _convert_DEREF
def _convert_LOAD_CLOSURE(self, arg):
self._lookupName(arg, self.varnames)
return self._lookupName(arg, self.closure)
_cmp = list(dis.cmp_op) _cmp = list(dis.cmp_op)
def _convert_COMPARE_OP(self, arg): def _convert_COMPARE_OP(self, arg):
return self._cmp.index(arg) return self._cmp.index(arg)
@ -432,7 +467,8 @@ class PyFlowGraph(FlowGraph):
self.lnotab.getCode(), self.getConsts(), self.lnotab.getCode(), self.getConsts(),
tuple(self.names), tuple(self.varnames), tuple(self.names), tuple(self.varnames),
self.filename, self.name, self.lnotab.firstline, self.filename, self.name, self.lnotab.firstline,
self.lnotab.getTable()) self.lnotab.getTable(), tuple(self.freevars),
tuple(self.cellvars))
def getConsts(self): def getConsts(self):
"""Return a tuple for the const slot of the code object """Return a tuple for the const slot of the code object

View File

@ -9,8 +9,10 @@ import types
from cStringIO import StringIO from cStringIO import StringIO
from compiler import ast, parse, walk from compiler import ast, parse, walk
from compiler import pyassem, misc, future from compiler import pyassem, misc, future, symbols
from compiler.pyassem import CO_VARARGS, CO_VARKEYWORDS, CO_NEWLOCALS, TupleArg 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? # Do we have Python 1.x or Python 2.x?
try: try:
@ -46,7 +48,7 @@ class Module:
tree = parse(self.source) tree = parse(self.source)
root, filename = os.path.split(self.filename) root, filename = os.path.split(self.filename)
if "nested_scopes" in future.find_futures(tree): if "nested_scopes" in future.find_futures(tree):
gen = NestedScopeCodeGenerator(filename) gen = NestedScopeModuleCodeGenerator(filename)
else: else:
gen = ModuleCodeGenerator(filename) gen = ModuleCodeGenerator(filename)
walk(tree, gen, 1) walk(tree, gen, 1)
@ -70,14 +72,71 @@ class Module:
mtime = struct.pack('i', mtime) mtime = struct.pack('i', mtime)
return self.MAGIC + 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: 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? optimized = 0 # is namespace access optimized?
__initialized = None
def __init__(self, filename): def __init__(self, filename):
## Subclasses must define a constructor that intializes self.graph if self.__initialized is None:
## before calling this init function, e.g. self.initClass()
## self.graph = pyassem.PyFlowGraph() self.__class__.__initialized = 1
self.checkClass()
self.filename = filename self.filename = filename
self.locals = misc.Stack() self.locals = misc.Stack()
self.loops = misc.Stack() self.loops = misc.Stack()
@ -86,6 +145,20 @@ class CodeGenerator:
self.last_lineno = None self.last_lineno = None
self._setupGraphDelegation() 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): def _setupGraphDelegation(self):
self.emit = self.graph.emit self.emit = self.graph.emit
self.newBlock = self.graph.newBlock self.newBlock = self.graph.newBlock
@ -139,10 +212,15 @@ class CodeGenerator:
return 0 return 0
# The first few visitor methods handle nodes that generator new # 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): def visitModule(self, node):
lnf = walk(node.node, LocalNameFinder(), 0) lnf = walk(node.node, self.NameFinder(), 0)
self.locals.push(lnf.getLocals()) self.locals.push(lnf.getLocals())
if node.doc: if node.doc:
self.fixDocstring(node.node) self.fixDocstring(node.node)
@ -159,8 +237,8 @@ class CodeGenerator:
def visitLambda(self, node): def visitLambda(self, node):
self._visitFuncOrLambda(node, isLambda=1) self._visitFuncOrLambda(node, isLambda=1)
def _visitFuncOrLambda(self, node, isLambda): def _visitFuncOrLambda(self, node, isLambda=0):
gen = FunctionCodeGenerator(node, self.filename, isLambda) gen = self.FunctionGen(node, self.filename, self.scopes, isLambda)
walk(node.code, gen) walk(node.code, gen)
gen.finish() gen.finish()
self.set_lineno(node) self.set_lineno(node)
@ -170,7 +248,7 @@ class CodeGenerator:
self.emit('MAKE_FUNCTION', len(node.defaults)) self.emit('MAKE_FUNCTION', len(node.defaults))
def visitClass(self, node): def visitClass(self, node):
gen = ClassCodeGenerator(node, self.filename) gen = self.ClassGen(node, self.filename, self.scopes)
if node.doc: if node.doc:
self.fixDocstring(node.code) self.fixDocstring(node.code)
walk(node.code, gen) walk(node.code, gen)
@ -180,7 +258,7 @@ class CodeGenerator:
for base in node.bases: for base in node.bases:
self.visit(base) self.visit(base)
self.emit('BUILD_TUPLE', len(node.bases)) 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('MAKE_FUNCTION', 0)
self.emit('CALL_FUNCTION', 0) self.emit('CALL_FUNCTION', 0)
self.emit('BUILD_CLASS') self.emit('BUILD_CLASS')
@ -883,34 +961,114 @@ class CodeGenerator:
self.visit(k) self.visit(k)
self.emit('STORE_SUBSCR') self.emit('STORE_SUBSCR')
class ModuleCodeGenerator(CodeGenerator): class NestedScopeCodeGenerator(CodeGenerator):
__super_init = CodeGenerator.__init__
__super_visitModule = CodeGenerator.visitModule __super_visitModule = CodeGenerator.visitModule
__super_visitClass = CodeGenerator.visitClass
__super__visitFuncOrLambda = CodeGenerator._visitFuncOrLambda
def __init__(self, filename): def parseSymbols(self, tree):
# XXX <module> is ? in compile.c s = symbols.SymbolVisitor()
self.graph = pyassem.PyFlowGraph("<module>", filename) walk(tree, s)
self.__super_init(filename) return s.scopes
self.symbols = None
def visitModule(self, node): def visitModule(self, node):
self.symbols = self.parseSymbols(node) self.scopes = self.parseSymbols(node)
self.scope = self.scopes[node]
self.__super_visitModule(node) self.__super_visitModule(node)
def parseSymbols(self, node): def _nameOp(self, prefix, name):
# XXX not implemented scope = self.scope.check_name(name)
return None 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): def _visitFuncOrLambda(self, node, isLambda=0):
pass 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): def visitClass(self, node):
super_init = CodeGenerator.__init__ 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 optimized = 1
lambdaCount = 0 lambdaCount = 0
def __init__(self, func, filename, isLambda=0): def __init__(self, func, filename, scopes, isLambda):
if isLambda: if isLambda:
klass = FunctionCodeGenerator klass = FunctionCodeGenerator
name = "<lambda.%d>" % klass.lambdaCount name = "<lambda.%d>" % klass.lambdaCount
@ -926,7 +1084,7 @@ class FunctionCodeGenerator(CodeGenerator):
if not isLambda and func.doc: if not isLambda and func.doc:
self.setDocstring(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()) self.locals.push(lnf.getLocals())
if func.varargs: if func.varargs:
self.graph.setFlag(CO_VARARGS) self.graph.setFlag(CO_VARARGS)
@ -963,14 +1121,32 @@ class FunctionCodeGenerator(CodeGenerator):
unpackTuple = unpackSequence unpackTuple = unpackSequence
class ClassCodeGenerator(CodeGenerator): class FunctionCodeGenerator(LGBScopeMixin, AbstractFunctionCode,
super_init = CodeGenerator.__init__ CodeGenerator):
super_init = CodeGenerator.__init__ # call be other init
scopes = None
def __init__(self, klass, filename): 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, scopes):
self.graph = pyassem.PyFlowGraph(klass.name, filename, self.graph = pyassem.PyFlowGraph(klass.name, filename,
optimized=0) optimized=0)
self.super_init(filename) self.super_init(filename)
lnf = walk(klass.code, LocalNameFinder(), 0) lnf = walk(klass.code, self.NameFinder(), 0)
self.locals.push(lnf.getLocals()) self.locals.push(lnf.getLocals())
self.graph.setFlag(CO_NEWLOCALS) self.graph.setFlag(CO_NEWLOCALS)
if klass.doc: if klass.doc:
@ -981,6 +1157,24 @@ class ClassCodeGenerator(CodeGenerator):
self.emit('LOAD_LOCALS') self.emit('LOAD_LOCALS')
self.emit('RETURN_VALUE') 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): def generateArgList(arglist):
"""Generate an arg list marking TupleArgs""" """Generate an arg list marking TupleArgs"""
args = [] args = []
@ -997,49 +1191,6 @@ def generateArgList(arglist):
raise ValueError, "unexpect argument type:", elt raise ValueError, "unexpect argument type:", elt
return args + extra, count 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): def findOp(node):
"""Find the op (DELETE, LOAD, STORE) in an AssTuple tree""" """Find the op (DELETE, LOAD, STORE) in an AssTuple tree"""
v = OpFinder() v = OpFinder()

View File

@ -1,8 +1,11 @@
"""Module symbol-table generator""" """Module symbol-table generator"""
from compiler import ast from compiler import ast
from compiler.consts import SC_LOCAL, SC_GLOBAL, SC_FREE, SC_CELL, SC_UNKNOWN
import types import types
import sys
MANGLE_LEN = 256 MANGLE_LEN = 256
class Scope: class Scope:
@ -14,7 +17,12 @@ class Scope:
self.uses = {} self.uses = {}
self.globals = {} self.globals = {}
self.params = {} self.params = {}
self.frees = {}
self.cells = {}
self.children = [] self.children = []
# nested is true if the class could contain free variables,
# i.e. if it is nested within another function.
self.nested = None
self.klass = None self.klass = None
if klass is not None: if klass is not None:
for i in range(len(klass)): for i in range(len(klass)):
@ -70,13 +78,112 @@ class Scope:
def get_children(self): def get_children(self):
return self.children return self.children
def DEBUG(self):
return
print >> sys.stderr, self.name, self.nested and "nested" or ""
print >> sys.stderr, "\tglobals: ", self.globals
print >> sys.stderr, "\tcells: ", self.cells
print >> sys.stderr, "\tdefs: ", self.defs
print >> sys.stderr, "\tuses: ", self.uses
print >> sys.stderr, "\tfrees:", self.frees
def check_name(self, name):
"""Return scope of name.
The scope of a name could be LOCAL, GLOBAL, FREE, or CELL.
"""
if self.globals.has_key(name):
return SC_GLOBAL
if self.cells.has_key(name):
return SC_CELL
if self.defs.has_key(name):
return SC_LOCAL
if self.nested and (self.frees.has_key(name) or
self.uses.has_key(name)):
return SC_FREE
if self.nested:
return SC_UNKNOWN
else:
return SC_GLOBAL
def get_free_vars(self):
if not self.nested:
return ()
free = {}
free.update(self.frees)
for name in self.uses.keys():
if not (self.defs.has_key(name) or
self.globals.has_key(name)):
free[name] = 1
return free.keys()
def handle_children(self):
for child in self.children:
frees = child.get_free_vars()
globals = self.add_frees(frees)
for name in globals:
child.force_global(name)
def force_global(self, name):
"""Force name to be global in scope.
Some child of the current node had a free reference to name.
When the child was processed, it was labelled a free
variable. Now that all its enclosing scope have been
processed, the name is known to be a global or builtin. So
walk back down the child chain and set the name to be global
rather than free.
Be careful to stop if a child does not think the name is
free.
"""
self.globals[name] = 1
if self.frees.has_key(name):
del self.frees[name]
for child in self.children:
if child.check_name(name) == SC_FREE:
child.force_global(name)
def add_frees(self, names):
"""Process list of free vars from nested scope.
Returns a list of names that are either 1) declared global in the
parent or 2) undefined in a top-level parent. In either case,
the nested scope should treat them as globals.
"""
child_globals = []
for name in names:
sc = self.check_name(name)
if self.nested:
if sc == SC_UNKNOWN or sc == SC_FREE \
or isinstance(self, ClassScope):
self.frees[name] = 1
elif sc == SC_GLOBAL:
child_globals.append(name)
elif isinstance(self, FunctionScope) and sc == SC_LOCAL:
self.cells[name] = 1
else:
child_globals.append(name)
else:
if sc == SC_LOCAL:
self.cells[name] = 1
else:
child_globals.append(name)
return child_globals
def get_cell_vars(self):
return self.cells.keys()
class ModuleScope(Scope): class ModuleScope(Scope):
__super_init = Scope.__init__ __super_init = Scope.__init__
def __init__(self): def __init__(self):
self.__super_init("global", self) self.__super_init("global", self)
class LambdaScope(Scope): class FunctionScope(Scope):
pass
class LambdaScope(FunctionScope):
__super_init = Scope.__init__ __super_init = Scope.__init__
__counter = 1 __counter = 1
@ -86,9 +193,6 @@ class LambdaScope(Scope):
self.__counter += 1 self.__counter += 1
self.__super_init("lambda.%d" % i, module, klass) self.__super_init("lambda.%d" % i, module, klass)
class FunctionScope(Scope):
pass
class ClassScope(Scope): class ClassScope(Scope):
__super_init = Scope.__init__ __super_init = Scope.__init__
@ -111,17 +215,24 @@ class SymbolVisitor:
for n in node.defaults: for n in node.defaults:
self.visit(n, parent) self.visit(n, parent)
scope = FunctionScope(node.name, self.module, self.klass) scope = FunctionScope(node.name, self.module, self.klass)
if parent.nested or isinstance(parent, FunctionScope):
scope.nested = 1
self.scopes[node] = scope self.scopes[node] = scope
self._do_args(scope, node.argnames) self._do_args(scope, node.argnames)
self.visit(node.code, scope) self.visit(node.code, scope)
self.handle_free_vars(scope, parent)
scope.DEBUG()
def visitLambda(self, node, parent): def visitLambda(self, node, parent):
for n in node.defaults: for n in node.defaults:
self.visit(n, parent) self.visit(n, parent)
scope = LambdaScope(self.module, self.klass) scope = LambdaScope(self.module, self.klass)
if parent.nested or isinstance(parent, FunctionScope):
scope.nested = 1
self.scopes[node] = scope self.scopes[node] = scope
self._do_args(scope, node.argnames) self._do_args(scope, node.argnames)
self.visit(node.code, scope) self.visit(node.code, scope)
self.handle_free_vars(scope, parent)
def _do_args(self, scope, args): def _do_args(self, scope, args):
for name in args: for name in args:
@ -130,16 +241,25 @@ class SymbolVisitor:
else: else:
scope.add_param(name) scope.add_param(name)
def handle_free_vars(self, scope, parent):
parent.add_child(scope)
if scope.children:
scope.DEBUG()
scope.handle_children()
def visitClass(self, node, parent): def visitClass(self, node, parent):
parent.add_def(node.name) parent.add_def(node.name)
for n in node.bases: for n in node.bases:
self.visit(n, parent) self.visit(n, parent)
scope = ClassScope(node.name, self.module) scope = ClassScope(node.name, self.module)
if parent.nested or isinstance(parent, FunctionScope):
scope.nested = 1
self.scopes[node] = scope self.scopes[node] = scope
prev = self.klass prev = self.klass
self.klass = node.name self.klass = node.name
self.visit(node.code, scope) self.visit(node.code, scope)
self.klass = prev self.klass = prev
self.handle_free_vars(scope, parent)
# name can be a def or a use # name can be a def or a use