add ExampleASTVisitor:

* prints out examples of nodes that are handled by visitor.  simply a
  development convenience

remove NestedCodeGenerator -- it was bogus after all
replace with generateFunctionCode, a method to call to generate code
  for a function instead of a top-level module

fix impl of visitDiscard (most pop stack)
emit lineno for pass

handle the following new node types: Import, From, Getattr, Subscript,
Slice, AssAttr, AssTuple, Mod, Not, And, Or, List

LocalNameFinder: remove names declared as globals for locals

PythonVMCode: pass arg names to constructor, force varnames to contain
them all (even if they aren't referenced)

add -q option on command line to disable stdout
This commit is contained in:
Jeremy Hylton 2000-02-10 00:47:08 +00:00
parent 69926eaee0
commit 5e0ce53e0e
2 changed files with 400 additions and 112 deletions

View File

@ -23,15 +23,24 @@ def parse(path):
t = transformer.Transformer()
return t.parsesuite(src)
def walk(tree, visitor, verbose=None):
def walk(tree, visitor, verbose=None, walker=None):
print visitor, "start"
w = ASTVisitor()
if walker:
w = walker()
else:
w = ASTVisitor()
if verbose is not None:
w.VERBOSE = verbose
w.preorder(tree, visitor)
print visitor, "finish"
return w.visitor
def dumpNode(node):
print node.__class__
for attr in dir(node):
if attr[0] != '_':
print "\t", "%-10.10s" % attr, getattr(node, attr)
class ASTVisitor:
"""Performs a depth-first walk of the AST
@ -112,6 +121,35 @@ class ASTVisitor:
if meth:
return meth(node)
class ExampleASTVisitor(ASTVisitor):
"""Prints examples of the nodes that aren't visited"""
examples = {}
def dispatch(self, node):
self.node = node
className = node.__class__.__name__
meth = getattr(self.visitor, 'visit' + className, None)
if self.VERBOSE > 0:
if self.VERBOSE == 1:
if meth is None:
print "dispatch", className
else:
print "dispatch", className, (meth and meth.__name__ or '')
if meth:
return meth(node)
else:
klass = node.__class__
if self.VERBOSE < 2:
if self.examples.has_key(klass):
return
self.examples[klass] = klass
print
print klass
for attr in dir(node):
if attr[0] != '_':
print "\t", "%-12.12s" % attr, getattr(node, attr)
print
class CodeGenerator:
def __init__(self, filename=None):
self.filename = filename
@ -123,6 +161,26 @@ class CodeGenerator:
self.curStack = 0
self.maxStack = 0
def generateFunctionCode(self, func, filename='<?>'):
"""Generate code for a function body"""
self.name = func.name
self.filename = filename
args = func.argnames
self.code = PythonVMCode(len(args), name=func.name,
filename=filename, args=args)
if func.varargs:
self.code.setVarArgs()
if func.kwargs:
self.code.setKWArgs()
lnf = walk(func.code, LocalNameFinder(args), 0)
self.locals.push(lnf.getLocals())
## print func.name, "(", func.argnames, ")"
## print lnf.getLocals().items()
self.code.setLineNo(func.lineno)
walk(func.code, self)
self.code.emit('LOAD_CONST', None)
self.code.emit('RETURN_VALUE')
def emit(self):
"""Create a Python code object
@ -149,9 +207,22 @@ class CodeGenerator:
if self.curStack != 0:
print "warning: stack should be empty"
def visitNULL(self, node):
"""Method exists only to stop warning in -v mode"""
pass
visitStmt = visitNULL
visitGlobal = visitNULL
def visitDiscard(self, node):
self.visit(node.expr)
self.code.emit('POP_TOP')
self.pop(1)
return 1
def visitPass(self, node):
self.code.setLineNo(node.lineno)
def visitModule(self, node):
lnf = walk(node.node, LocalNameFinder(), 0)
self.locals.push(lnf.getLocals())
@ -160,12 +231,31 @@ class CodeGenerator:
self.code.emit('RETURN_VALUE')
return 1
def visitFunction(self, node):
codeBody = NestedCodeGenerator(node, filename=self.filename)
walk(node, codeBody)
def visitImport(self, node):
self.code.setLineNo(node.lineno)
for name in node.names:
self.code.emit('IMPORT_NAME', name)
if self.isLocalName(name):
self.code.emit('STORE_FAST', name)
else:
self.code.emit('STORE_GLOBAL', name)
def visitFrom(self, node):
self.code.setLineNo(node.lineno)
self.code.emit('IMPORT_NAME', node.modname)
for name in node.names:
self.code.emit('IMPORT_FROM', name)
self.code.emit('POP_TOP')
def visitFunction(self, node):
codeBody = CodeGenerator()
codeBody.generateFunctionCode(node, filename=self.filename)
self.code.setLineNo(node.lineno)
for default in node.defaults:
self.visit(default)
self.code.emit('LOAD_CONST', codeBody)
self.code.emit('MAKE_FUNCTION', 0)
self.code.emit('MAKE_FUNCTION', len(node.defaults))
# XXX nested functions break here!
self.code.emit('STORE_NAME', node.name)
return 1
@ -283,12 +373,48 @@ class CodeGenerator:
l2.bind(self.code.getCurInst())
return 1
def visitGetattr(self, node):
self.visit(node.expr)
self.code.emit('LOAD_ATTR', node.attrname)
return 1
def visitSubscript(self, node):
self.visit(node.expr)
for sub in node.subs[:-1]:
self.visit(sub)
self.code.emit('BINARY_SUBSCR')
self.visit(node.subs[-1])
if node.flags == 'OP_APPLY':
self.code.emit('BINARY_SUBSCR')
else:
self.code.emit('STORE_SUBSCR')
return 1
def visitSlice(self, node):
self.visit(node.expr)
slice = 0
if node.lower:
self.visit(node.lower)
slice = slice | 1
self.pop(1)
if node.upper:
self.visit(node.upper)
slice = slice | 2
self.pop(1)
if node.flags == 'OP_APPLY':
self.code.emit('SLICE+%d' % slice)
elif node.flags == 'OP_ASSIGN':
self.code.emit('STORE_SLICE+%d' % slice)
elif node.flags == 'OP_DELETE':
self.code.emit('DELETE_SLICE+%d' % slice)
else:
print node.flags
raise
return 1
def visitAssign(self, node):
self.code.setLineNo(node.lineno)
print "Assign"
print node.nodes
print node.expr
print
self.visit(node.expr)
for elt in node.nodes:
if isinstance(elt, ast.Node):
@ -304,6 +430,22 @@ class CodeGenerator:
self.code.emit('STORE_GLOBAL', node.name)
self.pop(1)
def visitAssAttr(self, node):
if node.flags != 'OP_ASSIGN':
print "warning: unexpected flags:", node.flags
print node
self.visit(node.expr)
self.code.emit('STORE_ATTR', node.attrname)
return 1
def visitAssTuple(self, node):
self.code.emit('UNPACK_TUPLE', len(node.nodes))
for child in node.nodes:
self.visit(child)
return 1
visitAssList = visitAssTuple
def binaryOp(self, node, op):
self.visit(node.left)
self.visit(node.right)
@ -328,6 +470,9 @@ class CodeGenerator:
def visitDiv(self, node):
return self.binaryOp(node, 'BINARY_DIVIDE')
def visitMod(self, node):
return self.binaryOp(node, 'BINARY_MODULO')
def visitUnarySub(self, node):
return self.unaryOp(node, 'UNARY_NEGATIVE')
@ -337,9 +482,28 @@ class CodeGenerator:
def visitUnaryInvert(self, node):
return self.unaryOp(node, 'UNARY_INVERT')
def visitNot(self, node):
return self.unaryOp(node, 'UNARY_NOT')
def visitBackquote(self, node):
return self.unaryOp(node, 'UNARY_CONVERT')
def visitTest(self, node, jump):
end = StackRef()
for child in node.nodes[:-1]:
self.visit(child)
self.code.emit(jump, end)
self.code.emit('POP_TOP')
self.visit(node.nodes[-1])
end.bind(self.code.getCurInst())
return 1
def visitAnd(self, node):
return self.visitTest(node, 'JUMP_IF_FALSE')
def visitOr(self, node):
return self.visitTest(node, 'JUMP_IF_TRUE')
def visitName(self, node):
if self.isLocalName(node.name):
self.code.loadFast(node.name)
@ -359,6 +523,13 @@ class CodeGenerator:
self.pop(len(node.nodes))
return 1
def visitList(self, node):
for elt in node.nodes:
self.visit(elt)
self.code.emit('BUILD_LIST', len(node.nodes))
self.pop(len(node.nodes))
return 1
def visitReturn(self, node):
self.code.setLineNo(node.lineno)
self.visit(node.value)
@ -395,55 +566,24 @@ class CodeGenerator:
self.code.emit('PRINT_NEWLINE')
return 1
class NestedCodeGenerator(CodeGenerator):
"""Generate code for a function object within another scope
XXX not clear that this subclass is needed
"""
super_init = CodeGenerator.__init__
def __init__(self, func, filename='<?>'):
"""code and args of function or class being walked
XXX need to separately pass to ASTVisitor. the constructor
only uses the code object to find the local names
Copies code form parent __init__ rather than calling it.
"""
self.name = func.name
self.super_init(filename)
args = func.argnames
self.code = PythonVMCode(len(args), name=func.name,
filename=filename)
if func.varargs:
self.code.setVarArgs()
if func.kwargs:
self.code.setKWArgs()
lnf = walk(func.code, LocalNameFinder(args), 0)
self.locals.push(lnf.getLocals())
def __repr__(self):
return "<NestedCodeGenerator: %s>" % self.name
def visitFunction(self, node):
lnf = walk(node.code, LocalNameFinder(node.argnames), 0)
self.locals.push(lnf.getLocals())
# XXX need to handle def foo((a, b)):
self.code.setLineNo(node.lineno)
self.visit(node.code)
self.code.emit('LOAD_CONST', None)
self.code.emit('RETURN_VALUE')
return 1
class LocalNameFinder:
def __init__(self, names=()):
self.names = misc.Set()
self.globals = misc.Set()
for name in names:
self.names.add(name)
def getLocals(self):
for elt in self.globals.items():
if self.names.has_elt(elt):
self.names.remove(elt)
return self.names
def visitGlobal(self, node):
for name in node.names:
self.globals.add(name)
return 1
def visitFunction(self, node):
self.names.add(node.name)
return 1
@ -542,7 +682,7 @@ class PythonVMCode:
KWARGS = 0x08
def __init__(self, argcount=0, name='?', filename='<?>',
docstring=None):
docstring=None, args=()):
# XXX why is the default value for flags 3?
self.insts = []
# used by makeCodeObject
@ -553,7 +693,7 @@ class PythonVMCode:
self.flags = 3
self.name = name
self.names = []
self.varnames = []
self.varnames = list(args) or []
# lnotab support
self.firstlineno = 0
self.lastlineno = 0
@ -603,7 +743,6 @@ class PythonVMCode:
6 LOAD_CONST 0 (<code object fact at 8115878 [...]
9 MAKE_FUNCTION 0
12 STORE_NAME 0 (fact)
"""
self._findOffsets()
@ -682,7 +821,7 @@ class PythonVMCode:
return self._lookupName(arg, self.varnames, self.names)
if op in self.globalOps:
return self._lookupName(arg, self.names)
if op == 'STORE_NAME':
if op in self.nameOps:
return self._lookupName(arg, self.names)
if op == 'COMPARE_OP':
return self.cmp_op.index(arg)
@ -692,6 +831,8 @@ class PythonVMCode:
return self.offsets[arg.resolve()]
return arg
nameOps = ('STORE_NAME', 'IMPORT_NAME', 'IMPORT_FROM',
'STORE_ATTR', 'LOAD_ATTR')
localOps = ('LOAD_FAST', 'STORE_FAST')
globalOps = ('LOAD_GLOBAL', 'STORE_GLOBAL')
@ -867,7 +1008,7 @@ class CompiledModule:
t = transformer.Transformer()
self.ast = t.parsesuite(self.source)
cg = CodeGenerator(self.filename)
walk(self.ast, cg)
walk(self.ast, cg, walker=ExampleASTVisitor)
self.code = cg.emit()
def dump(self, path):
@ -890,11 +1031,14 @@ class CompiledModule:
if __name__ == "__main__":
import getopt
opts, args = getopt.getopt(sys.argv[1:], 'v')
opts, args = getopt.getopt(sys.argv[1:], 'vq')
for k, v in opts:
if k == '-v':
ASTVisitor.VERBOSE = ASTVisitor.VERBOSE + 1
print k
if k == '-q':
f = open('/dev/null', 'wb')
sys.stdout = f
if args:
filename = args[0]
else:

View File

@ -23,15 +23,24 @@ def parse(path):
t = transformer.Transformer()
return t.parsesuite(src)
def walk(tree, visitor, verbose=None):
def walk(tree, visitor, verbose=None, walker=None):
print visitor, "start"
w = ASTVisitor()
if walker:
w = walker()
else:
w = ASTVisitor()
if verbose is not None:
w.VERBOSE = verbose
w.preorder(tree, visitor)
print visitor, "finish"
return w.visitor
def dumpNode(node):
print node.__class__
for attr in dir(node):
if attr[0] != '_':
print "\t", "%-10.10s" % attr, getattr(node, attr)
class ASTVisitor:
"""Performs a depth-first walk of the AST
@ -112,6 +121,35 @@ class ASTVisitor:
if meth:
return meth(node)
class ExampleASTVisitor(ASTVisitor):
"""Prints examples of the nodes that aren't visited"""
examples = {}
def dispatch(self, node):
self.node = node
className = node.__class__.__name__
meth = getattr(self.visitor, 'visit' + className, None)
if self.VERBOSE > 0:
if self.VERBOSE == 1:
if meth is None:
print "dispatch", className
else:
print "dispatch", className, (meth and meth.__name__ or '')
if meth:
return meth(node)
else:
klass = node.__class__
if self.VERBOSE < 2:
if self.examples.has_key(klass):
return
self.examples[klass] = klass
print
print klass
for attr in dir(node):
if attr[0] != '_':
print "\t", "%-12.12s" % attr, getattr(node, attr)
print
class CodeGenerator:
def __init__(self, filename=None):
self.filename = filename
@ -123,6 +161,26 @@ class CodeGenerator:
self.curStack = 0
self.maxStack = 0
def generateFunctionCode(self, func, filename='<?>'):
"""Generate code for a function body"""
self.name = func.name
self.filename = filename
args = func.argnames
self.code = PythonVMCode(len(args), name=func.name,
filename=filename, args=args)
if func.varargs:
self.code.setVarArgs()
if func.kwargs:
self.code.setKWArgs()
lnf = walk(func.code, LocalNameFinder(args), 0)
self.locals.push(lnf.getLocals())
## print func.name, "(", func.argnames, ")"
## print lnf.getLocals().items()
self.code.setLineNo(func.lineno)
walk(func.code, self)
self.code.emit('LOAD_CONST', None)
self.code.emit('RETURN_VALUE')
def emit(self):
"""Create a Python code object
@ -149,9 +207,22 @@ class CodeGenerator:
if self.curStack != 0:
print "warning: stack should be empty"
def visitNULL(self, node):
"""Method exists only to stop warning in -v mode"""
pass
visitStmt = visitNULL
visitGlobal = visitNULL
def visitDiscard(self, node):
self.visit(node.expr)
self.code.emit('POP_TOP')
self.pop(1)
return 1
def visitPass(self, node):
self.code.setLineNo(node.lineno)
def visitModule(self, node):
lnf = walk(node.node, LocalNameFinder(), 0)
self.locals.push(lnf.getLocals())
@ -160,12 +231,31 @@ class CodeGenerator:
self.code.emit('RETURN_VALUE')
return 1
def visitFunction(self, node):
codeBody = NestedCodeGenerator(node, filename=self.filename)
walk(node, codeBody)
def visitImport(self, node):
self.code.setLineNo(node.lineno)
for name in node.names:
self.code.emit('IMPORT_NAME', name)
if self.isLocalName(name):
self.code.emit('STORE_FAST', name)
else:
self.code.emit('STORE_GLOBAL', name)
def visitFrom(self, node):
self.code.setLineNo(node.lineno)
self.code.emit('IMPORT_NAME', node.modname)
for name in node.names:
self.code.emit('IMPORT_FROM', name)
self.code.emit('POP_TOP')
def visitFunction(self, node):
codeBody = CodeGenerator()
codeBody.generateFunctionCode(node, filename=self.filename)
self.code.setLineNo(node.lineno)
for default in node.defaults:
self.visit(default)
self.code.emit('LOAD_CONST', codeBody)
self.code.emit('MAKE_FUNCTION', 0)
self.code.emit('MAKE_FUNCTION', len(node.defaults))
# XXX nested functions break here!
self.code.emit('STORE_NAME', node.name)
return 1
@ -283,12 +373,48 @@ class CodeGenerator:
l2.bind(self.code.getCurInst())
return 1
def visitGetattr(self, node):
self.visit(node.expr)
self.code.emit('LOAD_ATTR', node.attrname)
return 1
def visitSubscript(self, node):
self.visit(node.expr)
for sub in node.subs[:-1]:
self.visit(sub)
self.code.emit('BINARY_SUBSCR')
self.visit(node.subs[-1])
if node.flags == 'OP_APPLY':
self.code.emit('BINARY_SUBSCR')
else:
self.code.emit('STORE_SUBSCR')
return 1
def visitSlice(self, node):
self.visit(node.expr)
slice = 0
if node.lower:
self.visit(node.lower)
slice = slice | 1
self.pop(1)
if node.upper:
self.visit(node.upper)
slice = slice | 2
self.pop(1)
if node.flags == 'OP_APPLY':
self.code.emit('SLICE+%d' % slice)
elif node.flags == 'OP_ASSIGN':
self.code.emit('STORE_SLICE+%d' % slice)
elif node.flags == 'OP_DELETE':
self.code.emit('DELETE_SLICE+%d' % slice)
else:
print node.flags
raise
return 1
def visitAssign(self, node):
self.code.setLineNo(node.lineno)
print "Assign"
print node.nodes
print node.expr
print
self.visit(node.expr)
for elt in node.nodes:
if isinstance(elt, ast.Node):
@ -304,6 +430,22 @@ class CodeGenerator:
self.code.emit('STORE_GLOBAL', node.name)
self.pop(1)
def visitAssAttr(self, node):
if node.flags != 'OP_ASSIGN':
print "warning: unexpected flags:", node.flags
print node
self.visit(node.expr)
self.code.emit('STORE_ATTR', node.attrname)
return 1
def visitAssTuple(self, node):
self.code.emit('UNPACK_TUPLE', len(node.nodes))
for child in node.nodes:
self.visit(child)
return 1
visitAssList = visitAssTuple
def binaryOp(self, node, op):
self.visit(node.left)
self.visit(node.right)
@ -328,6 +470,9 @@ class CodeGenerator:
def visitDiv(self, node):
return self.binaryOp(node, 'BINARY_DIVIDE')
def visitMod(self, node):
return self.binaryOp(node, 'BINARY_MODULO')
def visitUnarySub(self, node):
return self.unaryOp(node, 'UNARY_NEGATIVE')
@ -337,9 +482,28 @@ class CodeGenerator:
def visitUnaryInvert(self, node):
return self.unaryOp(node, 'UNARY_INVERT')
def visitNot(self, node):
return self.unaryOp(node, 'UNARY_NOT')
def visitBackquote(self, node):
return self.unaryOp(node, 'UNARY_CONVERT')
def visitTest(self, node, jump):
end = StackRef()
for child in node.nodes[:-1]:
self.visit(child)
self.code.emit(jump, end)
self.code.emit('POP_TOP')
self.visit(node.nodes[-1])
end.bind(self.code.getCurInst())
return 1
def visitAnd(self, node):
return self.visitTest(node, 'JUMP_IF_FALSE')
def visitOr(self, node):
return self.visitTest(node, 'JUMP_IF_TRUE')
def visitName(self, node):
if self.isLocalName(node.name):
self.code.loadFast(node.name)
@ -359,6 +523,13 @@ class CodeGenerator:
self.pop(len(node.nodes))
return 1
def visitList(self, node):
for elt in node.nodes:
self.visit(elt)
self.code.emit('BUILD_LIST', len(node.nodes))
self.pop(len(node.nodes))
return 1
def visitReturn(self, node):
self.code.setLineNo(node.lineno)
self.visit(node.value)
@ -395,55 +566,24 @@ class CodeGenerator:
self.code.emit('PRINT_NEWLINE')
return 1
class NestedCodeGenerator(CodeGenerator):
"""Generate code for a function object within another scope
XXX not clear that this subclass is needed
"""
super_init = CodeGenerator.__init__
def __init__(self, func, filename='<?>'):
"""code and args of function or class being walked
XXX need to separately pass to ASTVisitor. the constructor
only uses the code object to find the local names
Copies code form parent __init__ rather than calling it.
"""
self.name = func.name
self.super_init(filename)
args = func.argnames
self.code = PythonVMCode(len(args), name=func.name,
filename=filename)
if func.varargs:
self.code.setVarArgs()
if func.kwargs:
self.code.setKWArgs()
lnf = walk(func.code, LocalNameFinder(args), 0)
self.locals.push(lnf.getLocals())
def __repr__(self):
return "<NestedCodeGenerator: %s>" % self.name
def visitFunction(self, node):
lnf = walk(node.code, LocalNameFinder(node.argnames), 0)
self.locals.push(lnf.getLocals())
# XXX need to handle def foo((a, b)):
self.code.setLineNo(node.lineno)
self.visit(node.code)
self.code.emit('LOAD_CONST', None)
self.code.emit('RETURN_VALUE')
return 1
class LocalNameFinder:
def __init__(self, names=()):
self.names = misc.Set()
self.globals = misc.Set()
for name in names:
self.names.add(name)
def getLocals(self):
for elt in self.globals.items():
if self.names.has_elt(elt):
self.names.remove(elt)
return self.names
def visitGlobal(self, node):
for name in node.names:
self.globals.add(name)
return 1
def visitFunction(self, node):
self.names.add(node.name)
return 1
@ -542,7 +682,7 @@ class PythonVMCode:
KWARGS = 0x08
def __init__(self, argcount=0, name='?', filename='<?>',
docstring=None):
docstring=None, args=()):
# XXX why is the default value for flags 3?
self.insts = []
# used by makeCodeObject
@ -553,7 +693,7 @@ class PythonVMCode:
self.flags = 3
self.name = name
self.names = []
self.varnames = []
self.varnames = list(args) or []
# lnotab support
self.firstlineno = 0
self.lastlineno = 0
@ -603,7 +743,6 @@ class PythonVMCode:
6 LOAD_CONST 0 (<code object fact at 8115878 [...]
9 MAKE_FUNCTION 0
12 STORE_NAME 0 (fact)
"""
self._findOffsets()
@ -682,7 +821,7 @@ class PythonVMCode:
return self._lookupName(arg, self.varnames, self.names)
if op in self.globalOps:
return self._lookupName(arg, self.names)
if op == 'STORE_NAME':
if op in self.nameOps:
return self._lookupName(arg, self.names)
if op == 'COMPARE_OP':
return self.cmp_op.index(arg)
@ -692,6 +831,8 @@ class PythonVMCode:
return self.offsets[arg.resolve()]
return arg
nameOps = ('STORE_NAME', 'IMPORT_NAME', 'IMPORT_FROM',
'STORE_ATTR', 'LOAD_ATTR')
localOps = ('LOAD_FAST', 'STORE_FAST')
globalOps = ('LOAD_GLOBAL', 'STORE_GLOBAL')
@ -867,7 +1008,7 @@ class CompiledModule:
t = transformer.Transformer()
self.ast = t.parsesuite(self.source)
cg = CodeGenerator(self.filename)
walk(self.ast, cg)
walk(self.ast, cg, walker=ExampleASTVisitor)
self.code = cg.emit()
def dump(self, path):
@ -890,11 +1031,14 @@ class CompiledModule:
if __name__ == "__main__":
import getopt
opts, args = getopt.getopt(sys.argv[1:], 'v')
opts, args = getopt.getopt(sys.argv[1:], 'vq')
for k, v in opts:
if k == '-v':
ASTVisitor.VERBOSE = ASTVisitor.VERBOSE + 1
print k
if k == '-q':
f = open('/dev/null', 'wb')
sys.stdout = f
if args:
filename = args[0]
else: