pycodegen.py revision 2051608616178ca6b73d5d59d887ddd67c5835e3
1import os
2import marshal
3import stat
4import string
5import struct
6import types
7from cStringIO import StringIO
8
9from compiler import ast, parse, walk
10from compiler import pyassem, misc
11from compiler.pyassem import CO_VARARGS, CO_VARKEYWORDS, CO_NEWLOCALS, TupleArg
12
13callfunc_opcode_info = {
14    # (Have *args, Have **args) : opcode
15    (0,0) : "CALL_FUNCTION",
16    (1,0) : "CALL_FUNCTION_VAR",
17    (0,1) : "CALL_FUNCTION_KW",
18    (1,1) : "CALL_FUNCTION_VAR_KW",
19}
20
21def compile(filename):
22    f = open(filename)
23    buf = f.read()
24    f.close()
25    mod = Module(buf, filename)
26    mod.compile()
27    f = open(filename + "c", "wb")
28    mod.dump(f)
29    f.close()
30
31class Module:
32    def __init__(self, source, filename):
33        self.filename = filename
34        self.source = source
35        self.code = None
36
37    def compile(self):
38        ast = parse(self.source)
39        root, filename = os.path.split(self.filename)
40        gen = ModuleCodeGenerator(filename)
41        walk(ast, gen, 1)
42        self.code = gen.getCode()
43
44    def dump(self, f):
45        f.write(self.getPycHeader())
46        marshal.dump(self.code, f)
47
48    MAGIC = (50823 | (ord('\r')<<16) | (ord('\n')<<24))
49
50    def getPycHeader(self):
51        # compile.c uses marshal to write a long directly, with
52        # calling the interface that would also generate a 1-byte code
53        # to indicate the type of the value.  simplest way to get the
54        # same effect is to call marshal and then skip the code.
55        magic = marshal.dumps(self.MAGIC)[1:]
56        mtime = os.stat(self.filename)[stat.ST_MTIME]
57        mtime = struct.pack('i', mtime)
58        return magic + mtime
59
60class CodeGenerator:
61
62    optimized = 0 # is namespace access optimized?
63
64    def __init__(self, filename):
65## Subclasses must define a constructor that intializes self.graph
66## before calling this init function
67##         self.graph = pyassem.PyFlowGraph()
68        self.filename = filename
69        self.locals = misc.Stack()
70        self.loops = misc.Stack()
71        self.curStack = 0
72        self.maxStack = 0
73        self._setupGraphDelegation()
74
75    def _setupGraphDelegation(self):
76        self.emit = self.graph.emit
77        self.newBlock = self.graph.newBlock
78        self.startBlock = self.graph.startBlock
79        self.nextBlock = self.graph.nextBlock
80        self.setDocstring = self.graph.setDocstring
81
82    def getCode(self):
83        """Return a code object"""
84        return self.graph.getCode()
85
86    # Next five methods handle name access
87
88    def isLocalName(self, name):
89        return self.locals.top().has_elt(name)
90
91    def storeName(self, name):
92        self._nameOp('STORE', name)
93
94    def loadName(self, name):
95        self._nameOp('LOAD', name)
96
97    def delName(self, name):
98        self._nameOp('DELETE', name)
99
100    def _nameOp(self, prefix, name):
101        if not self.optimized:
102            self.emit(prefix + '_NAME', name)
103            return
104        if self.isLocalName(name):
105            self.emit(prefix + '_FAST', name)
106        else:
107            self.emit(prefix + '_GLOBAL', name)
108
109    def set_lineno(self, node):
110        """Emit SET_LINENO if node has lineno attribute
111
112        Returns true if SET_LINENO was emitted.
113
114        There are no rules for when an AST node should have a lineno
115        attribute.  The transformer and AST code need to be reviewed
116        and a consistent policy implemented and documented.  Until
117        then, this method works around missing line numbers.
118        """
119        lineno = getattr(node, 'lineno', None)
120        if lineno is not None:
121            self.emit('SET_LINENO', lineno)
122            return 1
123        return 0
124
125    # The first few visitor methods handle nodes that generator new
126    # code objects
127
128    def visitModule(self, node):
129        lnf = walk(node.node, LocalNameFinder(), 0)
130        self.locals.push(lnf.getLocals())
131        self.setDocstring(node.doc)
132        self.visit(node.node)
133        self.emit('LOAD_CONST', None)
134        self.emit('RETURN_VALUE')
135
136    def visitFunction(self, node):
137        self._visitFuncOrLambda(node, isLambda=0)
138        self.storeName(node.name)
139
140    def visitLambda(self, node):
141        self._visitFuncOrLambda(node, isLambda=1)
142##        self.storeName("<lambda>")
143
144    def _visitFuncOrLambda(self, node, isLambda):
145        gen = FunctionCodeGenerator(node, self.filename, isLambda)
146        walk(node.code, gen)
147        gen.finish()
148        self.set_lineno(node)
149        for default in node.defaults:
150            self.visit(default)
151        self.emit('LOAD_CONST', gen.getCode())
152        self.emit('MAKE_FUNCTION', len(node.defaults))
153
154    def visitClass(self, node):
155        gen = ClassCodeGenerator(node, self.filename)
156        walk(node.code, gen)
157        gen.finish()
158        self.set_lineno(node)
159        self.emit('LOAD_CONST', node.name)
160        for base in node.bases:
161            self.visit(base)
162        self.emit('BUILD_TUPLE', len(node.bases))
163        self.emit('LOAD_CONST', gen.getCode())
164        self.emit('MAKE_FUNCTION', 0)
165        self.emit('CALL_FUNCTION', 0)
166        self.emit('BUILD_CLASS')
167        self.storeName(node.name)
168
169    # The rest are standard visitor methods
170
171    # The next few implement control-flow statements
172
173    def visitIf(self, node):
174        end = self.newBlock()
175        numtests = len(node.tests)
176        for i in range(numtests):
177            test, suite = node.tests[i]
178            self.set_lineno(test)
179            self.visit(test)
180##            if i == numtests - 1 and not node.else_:
181##                nextTest = end
182##            else:
183##                nextTest = self.newBlock()
184            nextTest = self.newBlock()
185            self.emit('JUMP_IF_FALSE', nextTest)
186            self.nextBlock()
187            self.emit('POP_TOP')
188            self.visit(suite)
189            self.emit('JUMP_FORWARD', end)
190            self.nextBlock(nextTest)
191            self.emit('POP_TOP')
192        if node.else_:
193            self.visit(node.else_)
194        self.nextBlock(end)
195
196    def visitWhile(self, node):
197        self.set_lineno(node)
198
199        loop = self.newBlock()
200        else_ = self.newBlock()
201
202        after = self.newBlock()
203        self.emit('SETUP_LOOP', after)
204
205        self.nextBlock(loop)
206        self.loops.push(loop)
207
208        self.set_lineno(node)
209        self.visit(node.test)
210        self.emit('JUMP_IF_FALSE', else_ or after)
211
212        self.nextBlock()
213        self.emit('POP_TOP')
214        self.visit(node.body)
215        self.emit('JUMP_ABSOLUTE', loop)
216
217        self.startBlock(else_) # or just the POPs if not else clause
218        self.emit('POP_TOP')
219        self.emit('POP_BLOCK')
220        if node.else_:
221            self.visit(node.else_)
222        self.loops.pop()
223        self.nextBlock(after)
224
225    def visitFor(self, node):
226        start = self.newBlock()
227        anchor = self.newBlock()
228        after = self.newBlock()
229        self.loops.push(start)
230
231        self.set_lineno(node)
232        self.emit('SETUP_LOOP', after)
233        self.visit(node.list)
234        self.visit(ast.Const(0))
235        self.nextBlock(start)
236        self.set_lineno(node)
237        self.emit('FOR_LOOP', anchor)
238        self.visit(node.assign)
239        self.visit(node.body)
240        self.emit('JUMP_ABSOLUTE', start)
241        self.nextBlock(anchor)
242        self.emit('POP_BLOCK')
243        if node.else_:
244            self.visit(node.else_)
245        self.loops.pop()
246        self.nextBlock(after)
247
248    def visitBreak(self, node):
249        if not self.loops:
250            raise SyntaxError, "'break' outside loop (%s, %d)" % \
251                  (self.filename, node.lineno)
252        self.set_lineno(node)
253        self.emit('BREAK_LOOP')
254
255    def visitContinue(self, node):
256        if not self.loops:
257            raise SyntaxError, "'continue' outside loop (%s, %d)" % \
258                  (self.filename, node.lineno)
259        l = self.loops.top()
260        self.set_lineno(node)
261        self.emit('JUMP_ABSOLUTE', l)
262        self.nextBlock()
263
264    def visitTest(self, node, jump):
265        end = self.newBlock()
266        for child in node.nodes[:-1]:
267            self.visit(child)
268            self.emit(jump, end)
269            self.nextBlock()
270            self.emit('POP_TOP')
271        self.visit(node.nodes[-1])
272        self.nextBlock(end)
273
274    def visitAnd(self, node):
275        self.visitTest(node, 'JUMP_IF_FALSE')
276
277    def visitOr(self, node):
278        self.visitTest(node, 'JUMP_IF_TRUE')
279
280    def visitCompare(self, node):
281        self.visit(node.expr)
282        cleanup = self.newBlock()
283        for op, code in node.ops[:-1]:
284            self.visit(code)
285            self.emit('DUP_TOP')
286            self.emit('ROT_THREE')
287            self.emit('COMPARE_OP', op)
288            self.emit('JUMP_IF_FALSE', cleanup)
289            self.nextBlock()
290            self.emit('POP_TOP')
291        # now do the last comparison
292        if node.ops:
293            op, code = node.ops[-1]
294            self.visit(code)
295            self.emit('COMPARE_OP', op)
296        if len(node.ops) > 1:
297            end = self.newBlock()
298            self.emit('JUMP_FORWARD', end)
299            self.nextBlock(cleanup)
300            self.emit('ROT_TWO')
301            self.emit('POP_TOP')
302            self.nextBlock(end)
303
304    # exception related
305
306    def visitAssert(self, node):
307        # XXX would be interesting to implement this via a
308        # transformation of the AST before this stage
309        end = self.newBlock()
310        self.set_lineno(node)
311        # XXX __debug__ and AssertionError appear to be special cases
312        # -- they are always loaded as globals even if there are local
313        # names.  I guess this is a sort of renaming op.
314        self.emit('LOAD_GLOBAL', '__debug__')
315        self.emit('JUMP_IF_FALSE', end)
316        self.nextBlock()
317        self.emit('POP_TOP')
318        self.visit(node.test)
319        self.emit('JUMP_IF_TRUE', end)
320        self.nextBlock()
321        self.emit('LOAD_GLOBAL', 'AssertionError')
322        self.visit(node.fail)
323        self.emit('RAISE_VARARGS', 2)
324        self.nextBlock(end)
325        self.emit('POP_TOP')
326
327    def visitRaise(self, node):
328        self.set_lineno(node)
329        n = 0
330        if node.expr1:
331            self.visit(node.expr1)
332            n = n + 1
333        if node.expr2:
334            self.visit(node.expr2)
335            n = n + 1
336        if node.expr3:
337            self.visit(node.expr3)
338            n = n + 1
339        self.emit('RAISE_VARARGS', n)
340
341    def visitTryExcept(self, node):
342        handlers = self.newBlock()
343        end = self.newBlock()
344        if node.else_:
345            lElse = self.newBlock()
346        else:
347            lElse = end
348        self.set_lineno(node)
349        self.emit('SETUP_EXCEPT', handlers)
350        self.visit(node.body)
351        self.emit('POP_BLOCK')
352        self.emit('JUMP_FORWARD', lElse)
353        self.nextBlock(handlers)
354
355        last = len(node.handlers) - 1
356        for i in range(len(node.handlers)):
357            expr, target, body = node.handlers[i]
358            self.set_lineno(expr)
359            if expr:
360                self.emit('DUP_TOP')
361                self.visit(expr)
362                self.emit('COMPARE_OP', 'exception match')
363                next = self.newBlock()
364                self.emit('JUMP_IF_FALSE', next)
365                self.nextBlock()
366                self.emit('POP_TOP')
367            self.emit('POP_TOP')
368            if target:
369                self.visit(target)
370            else:
371                self.emit('POP_TOP')
372            self.emit('POP_TOP')
373            self.visit(body)
374            self.emit('JUMP_FORWARD', end)
375            if expr:
376                self.nextBlock(next)
377            self.emit('POP_TOP')
378        self.emit('END_FINALLY')
379        if node.else_:
380            self.nextBlock(lElse)
381            self.visit(node.else_)
382        self.nextBlock(end)
383
384    def visitTryFinally(self, node):
385        final = self.newBlock()
386        self.set_lineno(node)
387        self.emit('SETUP_FINALLY', final)
388        self.visit(node.body)
389        self.emit('POP_BLOCK')
390        self.emit('LOAD_CONST', None)
391        self.nextBlock(final)
392        self.visit(node.final)
393        self.emit('END_FINALLY')
394
395    # misc
396
397##     def visitStmt(self, node):
398##         # nothing to do except walk the children
399##         pass
400
401    def visitDiscard(self, node):
402        self.visit(node.expr)
403        self.emit('POP_TOP')
404
405    def visitConst(self, node):
406        self.emit('LOAD_CONST', node.value)
407
408    def visitKeyword(self, node):
409        self.emit('LOAD_CONST', node.name)
410        self.visit(node.expr)
411
412    def visitGlobal(self, node):
413        # no code to generate
414        pass
415
416    def visitName(self, node):
417        self.loadName(node.name)
418
419    def visitPass(self, node):
420        self.set_lineno(node)
421
422    def visitImport(self, node):
423        self.set_lineno(node)
424        for name, alias in node.names:
425            self.emit('LOAD_CONST', None)
426            self.emit('IMPORT_NAME', name)
427            self._resolveDots(name)
428            self.storeName(alias or name)
429
430    def visitFrom(self, node):
431        self.set_lineno(node)
432        fromlist = map(lambda (name, alias): name, node.names)
433        self.emit('LOAD_CONST', tuple(fromlist))
434        self.emit('IMPORT_NAME', node.modname)
435        for name, alias in node.names:
436            if name == '*':
437                self.namespace = 0
438            self.emit('IMPORT_FROM', name)
439            self._resolveDots(name)
440            self.storeName(alias or name)
441        self.emit('POP_TOP')
442
443    def _resolveDots(self, name):
444        elts = string.split(name, ".")
445        if len(elts) == 1:
446            return
447        for elt in elts[1:]:
448            self.emit('LOAD_ATTR', elt)
449
450    def visitGetattr(self, node):
451        self.visit(node.expr)
452        self.emit('LOAD_ATTR', node.attrname)
453
454    # next five implement assignments
455
456    def visitAssign(self, node):
457        self.set_lineno(node)
458        self.visit(node.expr)
459        dups = len(node.nodes) - 1
460        for i in range(len(node.nodes)):
461            elt = node.nodes[i]
462            if i < dups:
463                self.emit('DUP_TOP')
464            if isinstance(elt, ast.Node):
465                self.visit(elt)
466
467    def visitAssName(self, node):
468        if node.flags == 'OP_ASSIGN':
469            self.storeName(node.name)
470        elif node.flags == 'OP_DELETE':
471            self.delName(node.name)
472        else:
473            print "oops", node.flags
474
475    def visitAssAttr(self, node):
476        self.visit(node.expr)
477        if node.flags == 'OP_ASSIGN':
478            self.emit('STORE_ATTR', node.attrname)
479        elif node.flags == 'OP_DELETE':
480            self.emit('DELETE_ATTR', node.attrname)
481        else:
482            print "warning: unexpected flags:", node.flags
483            print node
484
485    def visitAssTuple(self, node):
486        if findOp(node) != 'OP_DELETE':
487            self.emit('UNPACK_SEQUENCE', len(node.nodes))
488        for child in node.nodes:
489            self.visit(child)
490
491    visitAssList = visitAssTuple
492
493    def visitExec(self, node):
494        self.visit(node.expr)
495        if node.locals is None:
496            self.emit('LOAD_CONST', None)
497        else:
498            self.visit(node.locals)
499        if node.globals is None:
500            self.emit('DUP_TOP')
501        else:
502            self.visit(node.globals)
503        self.emit('EXEC_STMT')
504
505    def visitCallFunc(self, node):
506        pos = 0
507        kw = 0
508        self.set_lineno(node)
509        self.visit(node.node)
510        for arg in node.args:
511            self.visit(arg)
512            if isinstance(arg, ast.Keyword):
513                kw = kw + 1
514            else:
515                pos = pos + 1
516        if node.star_args is not None:
517            self.visit(node.star_args)
518        if node.dstar_args is not None:
519            self.visit(node.dstar_args)
520        have_star = node.star_args is not None
521        have_dstar = node.dstar_args is not None
522        opcode = callfunc_opcode_info[have_star, have_dstar]
523        self.emit(opcode, kw << 8 | pos)
524
525    def visitPrint(self, node):
526        self.set_lineno(node)
527        for child in node.nodes:
528            self.visit(child)
529            self.emit('PRINT_ITEM')
530
531    def visitPrintnl(self, node):
532        self.visitPrint(node)
533        self.emit('PRINT_NEWLINE')
534
535    def visitReturn(self, node):
536        self.set_lineno(node)
537        self.visit(node.value)
538        self.emit('RETURN_VALUE')
539
540    # slice and subscript stuff
541
542    def visitSlice(self, node):
543        self.visit(node.expr)
544        slice = 0
545        if node.lower:
546            self.visit(node.lower)
547            slice = slice | 1
548        if node.upper:
549            self.visit(node.upper)
550            slice = slice | 2
551        if node.flags == 'OP_APPLY':
552            self.emit('SLICE+%d' % slice)
553        elif node.flags == 'OP_ASSIGN':
554            self.emit('STORE_SLICE+%d' % slice)
555        elif node.flags == 'OP_DELETE':
556            self.emit('DELETE_SLICE+%d' % slice)
557        else:
558            print "weird slice", node.flags
559            raise
560
561    def visitSubscript(self, node):
562        self.visit(node.expr)
563        for sub in node.subs:
564            self.visit(sub)
565        if len(node.subs) > 1:
566            self.emit('BUILD_TUPLE', len(node.subs))
567        if node.flags == 'OP_APPLY':
568            self.emit('BINARY_SUBSCR')
569        elif node.flags == 'OP_ASSIGN':
570            self.emit('STORE_SUBSCR')
571        elif node.flags == 'OP_DELETE':
572            self.emit('DELETE_SUBSCR')
573
574    # binary ops
575
576    def binaryOp(self, node, op):
577        self.visit(node.left)
578        self.visit(node.right)
579        self.emit(op)
580
581    def visitAdd(self, node):
582        return self.binaryOp(node, 'BINARY_ADD')
583
584    def visitSub(self, node):
585        return self.binaryOp(node, 'BINARY_SUBTRACT')
586
587    def visitMul(self, node):
588        return self.binaryOp(node, 'BINARY_MULTIPLY')
589
590    def visitDiv(self, node):
591        return self.binaryOp(node, 'BINARY_DIVIDE')
592
593    def visitMod(self, node):
594        return self.binaryOp(node, 'BINARY_MODULO')
595
596    def visitPower(self, node):
597        return self.binaryOp(node, 'BINARY_POWER')
598
599    def visitLeftShift(self, node):
600        return self.binaryOp(node, 'BINARY_LSHIFT')
601
602    def visitRightShift(self, node):
603        return self.binaryOp(node, 'BINARY_RSHIFT')
604
605    # unary ops
606
607    def unaryOp(self, node, op):
608        self.visit(node.expr)
609        self.emit(op)
610
611    def visitInvert(self, node):
612        return self.unaryOp(node, 'UNARY_INVERT')
613
614    def visitUnarySub(self, node):
615        return self.unaryOp(node, 'UNARY_NEGATIVE')
616
617    def visitUnaryAdd(self, node):
618        return self.unaryOp(node, 'UNARY_POSITIVE')
619
620    def visitUnaryInvert(self, node):
621        return self.unaryOp(node, 'UNARY_INVERT')
622
623    def visitNot(self, node):
624        return self.unaryOp(node, 'UNARY_NOT')
625
626    def visitBackquote(self, node):
627        return self.unaryOp(node, 'UNARY_CONVERT')
628
629    # bit ops
630
631    def bitOp(self, nodes, op):
632        self.visit(nodes[0])
633        for node in nodes[1:]:
634            self.visit(node)
635            self.emit(op)
636
637    def visitBitand(self, node):
638        return self.bitOp(node.nodes, 'BINARY_AND')
639
640    def visitBitor(self, node):
641        return self.bitOp(node.nodes, 'BINARY_OR')
642
643    def visitBitxor(self, node):
644        return self.bitOp(node.nodes, 'BINARY_XOR')
645
646    # object constructors
647
648    def visitEllipsis(self, node):
649        self.emit('LOAD_CONST', Ellipsis)
650
651    def visitTuple(self, node):
652        for elt in node.nodes:
653            self.visit(elt)
654        self.emit('BUILD_TUPLE', len(node.nodes))
655
656    def visitList(self, node):
657        for elt in node.nodes:
658            self.visit(elt)
659        self.emit('BUILD_LIST', len(node.nodes))
660
661    def visitSliceobj(self, node):
662        for child in node.nodes:
663            self.visit(child)
664        self.emit('BUILD_SLICE', len(node.nodes))
665
666    def visitDict(self, node):
667        lineno = getattr(node, 'lineno', None)
668        if lineno:
669            set.emit('SET_LINENO', lineno)
670        self.emit('BUILD_MAP', 0)
671        for k, v in node.items:
672            lineno2 = getattr(node, 'lineno', None)
673            if lineno2 is not None and lineno != lineno2:
674                self.emit('SET_LINENO', lineno2)
675                lineno = lineno2
676            self.emit('DUP_TOP')
677            self.visit(v)
678            self.emit('ROT_TWO')
679            self.visit(k)
680            self.emit('STORE_SUBSCR')
681
682class ModuleCodeGenerator(CodeGenerator):
683    super_init = CodeGenerator.__init__
684
685    def __init__(self, filename):
686        # XXX <module> is ? in compile.c
687        self.graph = pyassem.PyFlowGraph("<module>", filename)
688        self.super_init(filename)
689
690class FunctionCodeGenerator(CodeGenerator):
691    super_init = CodeGenerator.__init__
692
693    optimized = 1
694    lambdaCount = 0
695
696    def __init__(self, func, filename, isLambda=0):
697        if isLambda:
698            klass = FunctionCodeGenerator
699            name = "<lambda.%d>" % klass.lambdaCount
700            klass.lambdaCount = klass.lambdaCount + 1
701        else:
702            name = func.name
703        args, hasTupleArg = generateArgList(func.argnames)
704        self.graph = pyassem.PyFlowGraph(name, filename, args,
705                                           optimized=1)
706        self.isLambda = isLambda
707        self.super_init(filename)
708
709        lnf = walk(func.code, LocalNameFinder(args), 0)
710        self.locals.push(lnf.getLocals())
711        if func.varargs:
712            self.graph.setFlag(CO_VARARGS)
713        if func.kwargs:
714            self.graph.setFlag(CO_VARKEYWORDS)
715        self.set_lineno(func)
716        if hasTupleArg:
717            self.generateArgUnpack(func.argnames)
718
719    def finish(self):
720        self.graph.startExitBlock()
721        if not self.isLambda:
722            self.emit('LOAD_CONST', None)
723        self.emit('RETURN_VALUE')
724
725    def generateArgUnpack(self, args):
726        count = 0
727        for arg in args:
728            if type(arg) == types.TupleType:
729                self.emit('LOAD_FAST', '.nested%d' % count)
730                count = count + 1
731                self.unpackSequence(arg)
732
733    def unpackSequence(self, tup):
734        self.emit('UNPACK_SEQUENCE', len(tup))
735        for elt in tup:
736            if type(elt) == types.TupleType:
737                self.unpackSequence(elt)
738            else:
739                self.emit('STORE_FAST', elt)
740
741    unpackTuple = unpackSequence
742
743class ClassCodeGenerator(CodeGenerator):
744    super_init = CodeGenerator.__init__
745
746    def __init__(self, klass, filename):
747        self.graph = pyassem.PyFlowGraph(klass.name, filename,
748                                           optimized=0)
749        self.super_init(filename)
750        lnf = walk(klass.code, LocalNameFinder(), 0)
751        self.locals.push(lnf.getLocals())
752        self.graph.setFlag(CO_NEWLOCALS)
753
754    def finish(self):
755        self.graph.startExitBlock()
756        self.emit('LOAD_LOCALS')
757        self.emit('RETURN_VALUE')
758
759
760def generateArgList(arglist):
761    """Generate an arg list marking TupleArgs"""
762    args = []
763    extra = []
764    count = 0
765    for elt in arglist:
766        if type(elt) == types.StringType:
767            args.append(elt)
768        elif type(elt) == types.TupleType:
769            args.append(TupleArg(count, elt))
770            count = count + 1
771            extra.extend(misc.flatten(elt))
772        else:
773            raise ValueError, "unexpect argument type:", elt
774    return args + extra, count
775
776class LocalNameFinder:
777    """Find local names in scope"""
778    def __init__(self, names=()):
779        self.names = misc.Set()
780        self.globals = misc.Set()
781        for name in names:
782            self.names.add(name)
783
784    def getLocals(self):
785        for elt in self.globals.elements():
786            if self.names.has_elt(elt):
787                self.names.remove(elt)
788        return self.names
789
790    def visitDict(self, node):
791        pass
792
793    def visitGlobal(self, node):
794        for name in node.names:
795            self.globals.add(name)
796
797    def visitFunction(self, node):
798        self.names.add(node.name)
799
800    def visitLambda(self, node):
801        pass
802
803    def visitImport(self, node):
804        for name, alias in node.names:
805            self.names.add(alias or name)
806
807    def visitFrom(self, node):
808        for name, alias in node.names:
809            self.names.add(alias or name)
810
811    def visitClass(self, node):
812        self.names.add(node.name)
813
814    def visitAssName(self, node):
815        self.names.add(node.name)
816
817def findOp(node):
818    """Find the op (DELETE, LOAD, STORE) in an AssTuple tree"""
819    v = OpFinder()
820    walk(node, v, 0)
821    return v.op
822
823class OpFinder:
824    def __init__(self):
825        self.op = None
826    def visitAssName(self, node):
827        if self.op is None:
828            self.op = node.flags
829        elif self.op != node.flags:
830            raise ValueError, "mixed ops in stmt"
831
832if __name__ == "__main__":
833    import sys
834
835    for file in sys.argv[1:]:
836        compile(file)
837