mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 21:51:50 +00:00 
			
		
		
		
	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:
		
							parent
							
								
									69926eaee0
								
							
						
					
					
						commit
						5e0ce53e0e
					
				
					 2 changed files with 400 additions and 112 deletions
				
			
		|  | @ -23,15 +23,24 @@ def parse(path): | ||||||
|     t = transformer.Transformer() |     t = transformer.Transformer() | ||||||
|     return t.parsesuite(src) |     return t.parsesuite(src) | ||||||
| 
 | 
 | ||||||
| def walk(tree, visitor, verbose=None): | def walk(tree, visitor, verbose=None, walker=None): | ||||||
|     print visitor, "start" |     print visitor, "start" | ||||||
|     w = ASTVisitor() |     if walker: | ||||||
|  |         w = walker() | ||||||
|  |     else: | ||||||
|  |         w = ASTVisitor() | ||||||
|     if verbose is not None: |     if verbose is not None: | ||||||
|         w.VERBOSE = verbose |         w.VERBOSE = verbose | ||||||
|     w.preorder(tree, visitor) |     w.preorder(tree, visitor) | ||||||
|     print visitor, "finish" |     print visitor, "finish" | ||||||
|     return w.visitor |     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: | class ASTVisitor: | ||||||
|     """Performs a depth-first walk of the AST |     """Performs a depth-first walk of the AST | ||||||
| 
 | 
 | ||||||
|  | @ -112,6 +121,35 @@ def dispatch(self, node): | ||||||
|         if meth: |         if meth: | ||||||
|             return meth(node) |             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: | class CodeGenerator: | ||||||
|     def __init__(self, filename=None): |     def __init__(self, filename=None): | ||||||
|         self.filename = filename |         self.filename = filename | ||||||
|  | @ -123,6 +161,26 @@ def __init__(self, filename=None): | ||||||
|         self.curStack = 0 |         self.curStack = 0 | ||||||
|         self.maxStack = 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): |     def emit(self): | ||||||
|         """Create a Python code object |         """Create a Python code object | ||||||
| 
 | 
 | ||||||
|  | @ -149,9 +207,22 @@ def assertStackEmpty(self): | ||||||
|         if self.curStack != 0: |         if self.curStack != 0: | ||||||
|             print "warning: stack should be empty" |             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): |     def visitDiscard(self, node): | ||||||
|  |         self.visit(node.expr) | ||||||
|  |         self.code.emit('POP_TOP') | ||||||
|  |         self.pop(1) | ||||||
|         return 1 |         return 1 | ||||||
| 
 | 
 | ||||||
|  |     def visitPass(self, node): | ||||||
|  |         self.code.setLineNo(node.lineno) | ||||||
|  | 
 | ||||||
|     def visitModule(self, node): |     def visitModule(self, node): | ||||||
| 	lnf = walk(node.node, LocalNameFinder(), 0) | 	lnf = walk(node.node, LocalNameFinder(), 0) | ||||||
| 	self.locals.push(lnf.getLocals()) | 	self.locals.push(lnf.getLocals()) | ||||||
|  | @ -160,12 +231,31 @@ def visitModule(self, node): | ||||||
|         self.code.emit('RETURN_VALUE') |         self.code.emit('RETURN_VALUE') | ||||||
|         return 1 |         return 1 | ||||||
| 
 | 
 | ||||||
|     def visitFunction(self, node): |     def visitImport(self, node): | ||||||
|         codeBody = NestedCodeGenerator(node, filename=self.filename) |  | ||||||
|         walk(node, codeBody) |  | ||||||
|         self.code.setLineNo(node.lineno) |         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('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) |         self.code.emit('STORE_NAME', node.name) | ||||||
|         return 1 |         return 1 | ||||||
| 
 | 
 | ||||||
|  | @ -283,12 +373,48 @@ def visitCompare(self, node): | ||||||
| 	    l2.bind(self.code.getCurInst()) | 	    l2.bind(self.code.getCurInst()) | ||||||
| 	return 1 | 	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): |     def visitAssign(self, node): | ||||||
|         self.code.setLineNo(node.lineno) |         self.code.setLineNo(node.lineno) | ||||||
|         print "Assign" |  | ||||||
|         print node.nodes |  | ||||||
|         print node.expr |  | ||||||
|         print |  | ||||||
|         self.visit(node.expr) |         self.visit(node.expr) | ||||||
|         for elt in node.nodes: |         for elt in node.nodes: | ||||||
|             if isinstance(elt, ast.Node): |             if isinstance(elt, ast.Node): | ||||||
|  | @ -304,6 +430,22 @@ def visitAssName(self, node): | ||||||
|             self.code.emit('STORE_GLOBAL', node.name) |             self.code.emit('STORE_GLOBAL', node.name) | ||||||
|         self.pop(1) |         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): |     def binaryOp(self, node, op): | ||||||
| 	self.visit(node.left) | 	self.visit(node.left) | ||||||
| 	self.visit(node.right) | 	self.visit(node.right) | ||||||
|  | @ -328,6 +470,9 @@ def visitMul(self, node): | ||||||
|     def visitDiv(self, node): |     def visitDiv(self, node): | ||||||
| 	return self.binaryOp(node, 'BINARY_DIVIDE') | 	return self.binaryOp(node, 'BINARY_DIVIDE') | ||||||
| 
 | 
 | ||||||
|  |     def visitMod(self, node): | ||||||
|  | 	return self.binaryOp(node, 'BINARY_MODULO') | ||||||
|  | 
 | ||||||
|     def visitUnarySub(self, node): |     def visitUnarySub(self, node): | ||||||
|         return self.unaryOp(node, 'UNARY_NEGATIVE') |         return self.unaryOp(node, 'UNARY_NEGATIVE') | ||||||
| 
 | 
 | ||||||
|  | @ -337,9 +482,28 @@ def visitUnaryAdd(self, node): | ||||||
|     def visitUnaryInvert(self, node): |     def visitUnaryInvert(self, node): | ||||||
|         return self.unaryOp(node, 'UNARY_INVERT') |         return self.unaryOp(node, 'UNARY_INVERT') | ||||||
| 
 | 
 | ||||||
|  |     def visitNot(self, node): | ||||||
|  |         return self.unaryOp(node, 'UNARY_NOT') | ||||||
|  | 
 | ||||||
|     def visitBackquote(self, node): |     def visitBackquote(self, node): | ||||||
|         return self.unaryOp(node, 'UNARY_CONVERT') |         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): |     def visitName(self, node): | ||||||
|         if self.isLocalName(node.name): |         if self.isLocalName(node.name): | ||||||
| 	    self.code.loadFast(node.name) | 	    self.code.loadFast(node.name) | ||||||
|  | @ -359,6 +523,13 @@ def visitTuple(self, node): | ||||||
|         self.pop(len(node.nodes)) |         self.pop(len(node.nodes)) | ||||||
|         return 1 |         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): |     def visitReturn(self, node): | ||||||
| 	self.code.setLineNo(node.lineno) | 	self.code.setLineNo(node.lineno) | ||||||
| 	self.visit(node.value) | 	self.visit(node.value) | ||||||
|  | @ -395,55 +566,24 @@ def visitPrintnl(self, node): | ||||||
| 	self.code.emit('PRINT_NEWLINE') | 	self.code.emit('PRINT_NEWLINE') | ||||||
| 	return 1 | 	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: | class LocalNameFinder: | ||||||
|     def __init__(self, names=()): |     def __init__(self, names=()): | ||||||
| 	self.names = misc.Set() | 	self.names = misc.Set() | ||||||
|  |         self.globals = misc.Set() | ||||||
| 	for name in names: | 	for name in names: | ||||||
| 	    self.names.add(name) | 	    self.names.add(name) | ||||||
| 
 | 
 | ||||||
|     def getLocals(self): |     def getLocals(self): | ||||||
|  |         for elt in self.globals.items(): | ||||||
|  |             if self.names.has_elt(elt): | ||||||
|  |                 self.names.remove(elt) | ||||||
| 	return self.names | 	return self.names | ||||||
| 
 | 
 | ||||||
|  |     def visitGlobal(self, node): | ||||||
|  |         for name in node.names: | ||||||
|  |             self.globals.add(name) | ||||||
|  |         return 1 | ||||||
|  | 
 | ||||||
|     def visitFunction(self, node): |     def visitFunction(self, node): | ||||||
|         self.names.add(node.name) |         self.names.add(node.name) | ||||||
| 	return 1 | 	return 1 | ||||||
|  | @ -542,7 +682,7 @@ class PythonVMCode: | ||||||
|     KWARGS = 0x08 |     KWARGS = 0x08 | ||||||
| 
 | 
 | ||||||
|     def __init__(self, argcount=0, name='?', filename='<?>', |     def __init__(self, argcount=0, name='?', filename='<?>', | ||||||
|                  docstring=None): |                  docstring=None, args=()): | ||||||
|         # XXX why is the default value for flags 3? |         # XXX why is the default value for flags 3? | ||||||
| 	self.insts = [] | 	self.insts = [] | ||||||
|         # used by makeCodeObject |         # used by makeCodeObject | ||||||
|  | @ -553,7 +693,7 @@ def __init__(self, argcount=0, name='?', filename='<?>', | ||||||
|         self.flags = 3 |         self.flags = 3 | ||||||
|         self.name = name |         self.name = name | ||||||
|         self.names = [] |         self.names = [] | ||||||
|         self.varnames = [] |         self.varnames = list(args) or [] | ||||||
|         # lnotab support |         # lnotab support | ||||||
|         self.firstlineno = 0 |         self.firstlineno = 0 | ||||||
|         self.lastlineno = 0 |         self.lastlineno = 0 | ||||||
|  | @ -603,7 +743,6 @@ def makeCodeObject(self, stacksize): | ||||||
|           6 LOAD_CONST          0 (<code object fact at 8115878 [...] |           6 LOAD_CONST          0 (<code object fact at 8115878 [...] | ||||||
|           9 MAKE_FUNCTION       0 |           9 MAKE_FUNCTION       0 | ||||||
|          12 STORE_NAME          0 (fact) |          12 STORE_NAME          0 (fact) | ||||||
| 
 |  | ||||||
|         """ |         """ | ||||||
|          |          | ||||||
|         self._findOffsets() |         self._findOffsets() | ||||||
|  | @ -682,7 +821,7 @@ def _convertArg(self, op, arg): | ||||||
|                 return self._lookupName(arg, self.varnames, self.names) |                 return self._lookupName(arg, self.varnames, self.names) | ||||||
|         if op in self.globalOps: |         if op in self.globalOps: | ||||||
|             return self._lookupName(arg, self.names) |             return self._lookupName(arg, self.names) | ||||||
|         if op == 'STORE_NAME': |         if op in self.nameOps: | ||||||
|             return self._lookupName(arg, self.names) |             return self._lookupName(arg, self.names) | ||||||
|         if op == 'COMPARE_OP': |         if op == 'COMPARE_OP': | ||||||
|             return self.cmp_op.index(arg) |             return self.cmp_op.index(arg) | ||||||
|  | @ -692,6 +831,8 @@ def _convertArg(self, op, arg): | ||||||
|             return self.offsets[arg.resolve()] |             return self.offsets[arg.resolve()] | ||||||
|         return arg |         return arg | ||||||
| 
 | 
 | ||||||
|  |     nameOps = ('STORE_NAME', 'IMPORT_NAME', 'IMPORT_FROM', | ||||||
|  |                'STORE_ATTR', 'LOAD_ATTR') | ||||||
|     localOps = ('LOAD_FAST', 'STORE_FAST') |     localOps = ('LOAD_FAST', 'STORE_FAST') | ||||||
|     globalOps = ('LOAD_GLOBAL', 'STORE_GLOBAL') |     globalOps = ('LOAD_GLOBAL', 'STORE_GLOBAL') | ||||||
| 
 | 
 | ||||||
|  | @ -867,7 +1008,7 @@ def compile(self): | ||||||
|         t = transformer.Transformer() |         t = transformer.Transformer() | ||||||
|         self.ast = t.parsesuite(self.source) |         self.ast = t.parsesuite(self.source) | ||||||
|         cg = CodeGenerator(self.filename) |         cg = CodeGenerator(self.filename) | ||||||
|         walk(self.ast, cg) |         walk(self.ast, cg, walker=ExampleASTVisitor) | ||||||
|         self.code = cg.emit() |         self.code = cg.emit() | ||||||
| 
 | 
 | ||||||
|     def dump(self, path): |     def dump(self, path): | ||||||
|  | @ -890,11 +1031,14 @@ def _pyc_header(self): | ||||||
| if __name__ == "__main__": | if __name__ == "__main__": | ||||||
|     import getopt |     import getopt | ||||||
| 
 | 
 | ||||||
|     opts, args = getopt.getopt(sys.argv[1:], 'v') |     opts, args = getopt.getopt(sys.argv[1:], 'vq') | ||||||
|     for k, v in opts: |     for k, v in opts: | ||||||
|         if k == '-v': |         if k == '-v': | ||||||
|             ASTVisitor.VERBOSE = ASTVisitor.VERBOSE + 1 |             ASTVisitor.VERBOSE = ASTVisitor.VERBOSE + 1 | ||||||
|             print k |             print k | ||||||
|  |         if k == '-q': | ||||||
|  |             f = open('/dev/null', 'wb') | ||||||
|  |             sys.stdout = f | ||||||
|     if args: |     if args: | ||||||
|         filename = args[0] |         filename = args[0] | ||||||
|     else: |     else: | ||||||
|  |  | ||||||
|  | @ -23,15 +23,24 @@ def parse(path): | ||||||
|     t = transformer.Transformer() |     t = transformer.Transformer() | ||||||
|     return t.parsesuite(src) |     return t.parsesuite(src) | ||||||
| 
 | 
 | ||||||
| def walk(tree, visitor, verbose=None): | def walk(tree, visitor, verbose=None, walker=None): | ||||||
|     print visitor, "start" |     print visitor, "start" | ||||||
|     w = ASTVisitor() |     if walker: | ||||||
|  |         w = walker() | ||||||
|  |     else: | ||||||
|  |         w = ASTVisitor() | ||||||
|     if verbose is not None: |     if verbose is not None: | ||||||
|         w.VERBOSE = verbose |         w.VERBOSE = verbose | ||||||
|     w.preorder(tree, visitor) |     w.preorder(tree, visitor) | ||||||
|     print visitor, "finish" |     print visitor, "finish" | ||||||
|     return w.visitor |     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: | class ASTVisitor: | ||||||
|     """Performs a depth-first walk of the AST |     """Performs a depth-first walk of the AST | ||||||
| 
 | 
 | ||||||
|  | @ -112,6 +121,35 @@ def dispatch(self, node): | ||||||
|         if meth: |         if meth: | ||||||
|             return meth(node) |             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: | class CodeGenerator: | ||||||
|     def __init__(self, filename=None): |     def __init__(self, filename=None): | ||||||
|         self.filename = filename |         self.filename = filename | ||||||
|  | @ -123,6 +161,26 @@ def __init__(self, filename=None): | ||||||
|         self.curStack = 0 |         self.curStack = 0 | ||||||
|         self.maxStack = 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): |     def emit(self): | ||||||
|         """Create a Python code object |         """Create a Python code object | ||||||
| 
 | 
 | ||||||
|  | @ -149,9 +207,22 @@ def assertStackEmpty(self): | ||||||
|         if self.curStack != 0: |         if self.curStack != 0: | ||||||
|             print "warning: stack should be empty" |             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): |     def visitDiscard(self, node): | ||||||
|  |         self.visit(node.expr) | ||||||
|  |         self.code.emit('POP_TOP') | ||||||
|  |         self.pop(1) | ||||||
|         return 1 |         return 1 | ||||||
| 
 | 
 | ||||||
|  |     def visitPass(self, node): | ||||||
|  |         self.code.setLineNo(node.lineno) | ||||||
|  | 
 | ||||||
|     def visitModule(self, node): |     def visitModule(self, node): | ||||||
| 	lnf = walk(node.node, LocalNameFinder(), 0) | 	lnf = walk(node.node, LocalNameFinder(), 0) | ||||||
| 	self.locals.push(lnf.getLocals()) | 	self.locals.push(lnf.getLocals()) | ||||||
|  | @ -160,12 +231,31 @@ def visitModule(self, node): | ||||||
|         self.code.emit('RETURN_VALUE') |         self.code.emit('RETURN_VALUE') | ||||||
|         return 1 |         return 1 | ||||||
| 
 | 
 | ||||||
|     def visitFunction(self, node): |     def visitImport(self, node): | ||||||
|         codeBody = NestedCodeGenerator(node, filename=self.filename) |  | ||||||
|         walk(node, codeBody) |  | ||||||
|         self.code.setLineNo(node.lineno) |         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('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) |         self.code.emit('STORE_NAME', node.name) | ||||||
|         return 1 |         return 1 | ||||||
| 
 | 
 | ||||||
|  | @ -283,12 +373,48 @@ def visitCompare(self, node): | ||||||
| 	    l2.bind(self.code.getCurInst()) | 	    l2.bind(self.code.getCurInst()) | ||||||
| 	return 1 | 	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): |     def visitAssign(self, node): | ||||||
|         self.code.setLineNo(node.lineno) |         self.code.setLineNo(node.lineno) | ||||||
|         print "Assign" |  | ||||||
|         print node.nodes |  | ||||||
|         print node.expr |  | ||||||
|         print |  | ||||||
|         self.visit(node.expr) |         self.visit(node.expr) | ||||||
|         for elt in node.nodes: |         for elt in node.nodes: | ||||||
|             if isinstance(elt, ast.Node): |             if isinstance(elt, ast.Node): | ||||||
|  | @ -304,6 +430,22 @@ def visitAssName(self, node): | ||||||
|             self.code.emit('STORE_GLOBAL', node.name) |             self.code.emit('STORE_GLOBAL', node.name) | ||||||
|         self.pop(1) |         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): |     def binaryOp(self, node, op): | ||||||
| 	self.visit(node.left) | 	self.visit(node.left) | ||||||
| 	self.visit(node.right) | 	self.visit(node.right) | ||||||
|  | @ -328,6 +470,9 @@ def visitMul(self, node): | ||||||
|     def visitDiv(self, node): |     def visitDiv(self, node): | ||||||
| 	return self.binaryOp(node, 'BINARY_DIVIDE') | 	return self.binaryOp(node, 'BINARY_DIVIDE') | ||||||
| 
 | 
 | ||||||
|  |     def visitMod(self, node): | ||||||
|  | 	return self.binaryOp(node, 'BINARY_MODULO') | ||||||
|  | 
 | ||||||
|     def visitUnarySub(self, node): |     def visitUnarySub(self, node): | ||||||
|         return self.unaryOp(node, 'UNARY_NEGATIVE') |         return self.unaryOp(node, 'UNARY_NEGATIVE') | ||||||
| 
 | 
 | ||||||
|  | @ -337,9 +482,28 @@ def visitUnaryAdd(self, node): | ||||||
|     def visitUnaryInvert(self, node): |     def visitUnaryInvert(self, node): | ||||||
|         return self.unaryOp(node, 'UNARY_INVERT') |         return self.unaryOp(node, 'UNARY_INVERT') | ||||||
| 
 | 
 | ||||||
|  |     def visitNot(self, node): | ||||||
|  |         return self.unaryOp(node, 'UNARY_NOT') | ||||||
|  | 
 | ||||||
|     def visitBackquote(self, node): |     def visitBackquote(self, node): | ||||||
|         return self.unaryOp(node, 'UNARY_CONVERT') |         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): |     def visitName(self, node): | ||||||
|         if self.isLocalName(node.name): |         if self.isLocalName(node.name): | ||||||
| 	    self.code.loadFast(node.name) | 	    self.code.loadFast(node.name) | ||||||
|  | @ -359,6 +523,13 @@ def visitTuple(self, node): | ||||||
|         self.pop(len(node.nodes)) |         self.pop(len(node.nodes)) | ||||||
|         return 1 |         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): |     def visitReturn(self, node): | ||||||
| 	self.code.setLineNo(node.lineno) | 	self.code.setLineNo(node.lineno) | ||||||
| 	self.visit(node.value) | 	self.visit(node.value) | ||||||
|  | @ -395,55 +566,24 @@ def visitPrintnl(self, node): | ||||||
| 	self.code.emit('PRINT_NEWLINE') | 	self.code.emit('PRINT_NEWLINE') | ||||||
| 	return 1 | 	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: | class LocalNameFinder: | ||||||
|     def __init__(self, names=()): |     def __init__(self, names=()): | ||||||
| 	self.names = misc.Set() | 	self.names = misc.Set() | ||||||
|  |         self.globals = misc.Set() | ||||||
| 	for name in names: | 	for name in names: | ||||||
| 	    self.names.add(name) | 	    self.names.add(name) | ||||||
| 
 | 
 | ||||||
|     def getLocals(self): |     def getLocals(self): | ||||||
|  |         for elt in self.globals.items(): | ||||||
|  |             if self.names.has_elt(elt): | ||||||
|  |                 self.names.remove(elt) | ||||||
| 	return self.names | 	return self.names | ||||||
| 
 | 
 | ||||||
|  |     def visitGlobal(self, node): | ||||||
|  |         for name in node.names: | ||||||
|  |             self.globals.add(name) | ||||||
|  |         return 1 | ||||||
|  | 
 | ||||||
|     def visitFunction(self, node): |     def visitFunction(self, node): | ||||||
|         self.names.add(node.name) |         self.names.add(node.name) | ||||||
| 	return 1 | 	return 1 | ||||||
|  | @ -542,7 +682,7 @@ class PythonVMCode: | ||||||
|     KWARGS = 0x08 |     KWARGS = 0x08 | ||||||
| 
 | 
 | ||||||
|     def __init__(self, argcount=0, name='?', filename='<?>', |     def __init__(self, argcount=0, name='?', filename='<?>', | ||||||
|                  docstring=None): |                  docstring=None, args=()): | ||||||
|         # XXX why is the default value for flags 3? |         # XXX why is the default value for flags 3? | ||||||
| 	self.insts = [] | 	self.insts = [] | ||||||
|         # used by makeCodeObject |         # used by makeCodeObject | ||||||
|  | @ -553,7 +693,7 @@ def __init__(self, argcount=0, name='?', filename='<?>', | ||||||
|         self.flags = 3 |         self.flags = 3 | ||||||
|         self.name = name |         self.name = name | ||||||
|         self.names = [] |         self.names = [] | ||||||
|         self.varnames = [] |         self.varnames = list(args) or [] | ||||||
|         # lnotab support |         # lnotab support | ||||||
|         self.firstlineno = 0 |         self.firstlineno = 0 | ||||||
|         self.lastlineno = 0 |         self.lastlineno = 0 | ||||||
|  | @ -603,7 +743,6 @@ def makeCodeObject(self, stacksize): | ||||||
|           6 LOAD_CONST          0 (<code object fact at 8115878 [...] |           6 LOAD_CONST          0 (<code object fact at 8115878 [...] | ||||||
|           9 MAKE_FUNCTION       0 |           9 MAKE_FUNCTION       0 | ||||||
|          12 STORE_NAME          0 (fact) |          12 STORE_NAME          0 (fact) | ||||||
| 
 |  | ||||||
|         """ |         """ | ||||||
|          |          | ||||||
|         self._findOffsets() |         self._findOffsets() | ||||||
|  | @ -682,7 +821,7 @@ def _convertArg(self, op, arg): | ||||||
|                 return self._lookupName(arg, self.varnames, self.names) |                 return self._lookupName(arg, self.varnames, self.names) | ||||||
|         if op in self.globalOps: |         if op in self.globalOps: | ||||||
|             return self._lookupName(arg, self.names) |             return self._lookupName(arg, self.names) | ||||||
|         if op == 'STORE_NAME': |         if op in self.nameOps: | ||||||
|             return self._lookupName(arg, self.names) |             return self._lookupName(arg, self.names) | ||||||
|         if op == 'COMPARE_OP': |         if op == 'COMPARE_OP': | ||||||
|             return self.cmp_op.index(arg) |             return self.cmp_op.index(arg) | ||||||
|  | @ -692,6 +831,8 @@ def _convertArg(self, op, arg): | ||||||
|             return self.offsets[arg.resolve()] |             return self.offsets[arg.resolve()] | ||||||
|         return arg |         return arg | ||||||
| 
 | 
 | ||||||
|  |     nameOps = ('STORE_NAME', 'IMPORT_NAME', 'IMPORT_FROM', | ||||||
|  |                'STORE_ATTR', 'LOAD_ATTR') | ||||||
|     localOps = ('LOAD_FAST', 'STORE_FAST') |     localOps = ('LOAD_FAST', 'STORE_FAST') | ||||||
|     globalOps = ('LOAD_GLOBAL', 'STORE_GLOBAL') |     globalOps = ('LOAD_GLOBAL', 'STORE_GLOBAL') | ||||||
| 
 | 
 | ||||||
|  | @ -867,7 +1008,7 @@ def compile(self): | ||||||
|         t = transformer.Transformer() |         t = transformer.Transformer() | ||||||
|         self.ast = t.parsesuite(self.source) |         self.ast = t.parsesuite(self.source) | ||||||
|         cg = CodeGenerator(self.filename) |         cg = CodeGenerator(self.filename) | ||||||
|         walk(self.ast, cg) |         walk(self.ast, cg, walker=ExampleASTVisitor) | ||||||
|         self.code = cg.emit() |         self.code = cg.emit() | ||||||
| 
 | 
 | ||||||
|     def dump(self, path): |     def dump(self, path): | ||||||
|  | @ -890,11 +1031,14 @@ def _pyc_header(self): | ||||||
| if __name__ == "__main__": | if __name__ == "__main__": | ||||||
|     import getopt |     import getopt | ||||||
| 
 | 
 | ||||||
|     opts, args = getopt.getopt(sys.argv[1:], 'v') |     opts, args = getopt.getopt(sys.argv[1:], 'vq') | ||||||
|     for k, v in opts: |     for k, v in opts: | ||||||
|         if k == '-v': |         if k == '-v': | ||||||
|             ASTVisitor.VERBOSE = ASTVisitor.VERBOSE + 1 |             ASTVisitor.VERBOSE = ASTVisitor.VERBOSE + 1 | ||||||
|             print k |             print k | ||||||
|  |         if k == '-q': | ||||||
|  |             f = open('/dev/null', 'wb') | ||||||
|  |             sys.stdout = f | ||||||
|     if args: |     if args: | ||||||
|         filename = args[0] |         filename = args[0] | ||||||
|     else: |     else: | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Jeremy Hylton
						Jeremy Hylton