# Copyright (c) 1999 John Aycock # Copyright (c) 2000 by hartmut Goebel # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # # See 'CHANGES' for a list of changes # # NB. This is not a masterpiece of software, but became more like a hack. # Probably a complete write would be sensefull. hG/2000-12-27 # import re, sys, os, types import dis, imp, marshal import string import cStringIO def _load_file(filename): """ load a Python source file and compile it to byte-code _load_module(filename: string): code_object filename: name of file containing Python source code (normally a .py) code_object: code_object compiled from this source code This function does NOT write any file! """ fp = open(filename, 'rb') source = fp.read()+'\n' try: co = compile(source, filename, 'exec') except SyntaxError: sys.stderr.writelines( ['>>Syntax error in ', filename, '\n'] ) raise fp.close() return co def _load_module(filename): """ load a module without importing it _load_module(filename: string): code_object filename: name of file containing Python byte-code object (normally a .pyc) code_object: code_object from this file """ fp = open(filename, 'rb') if fp.read(4) != imp.get_magic(): raise ImportError, "Bad magic number in %s" % filename fp.read(4) co = marshal.load(fp) fp.close() return co #-- start of (de-)compiler # # Scanning # class Code: """Class for representing code-objects. This is similar to the original code object, but additionally the diassembled code is stored in the attribute '_tokens'. """ def __init__(self, co): for i in dir(co): exec 'self.%s = co.%s' % (i, i) self._tokens, self._customize = disassemble(co) class Token: """Class representing a byte-code token. A byte-code token is equivalent to the contents of one line as output by dis.dis(). """ def __init__(self, type, attr=None, pattr=None, offset=-1): self.type = intern(type) self.attr = attr self.pattr = pattr self.offset = offset def __cmp__(self, o): if isinstance(o, Token): # both are tokens: compare type and pattr return cmp(self.type, o.type) \ or cmp(self.pattr, o.pattr) else: return cmp(self.type, o) def __repr__(self): return str(self.type) def __str__(self): if self.pattr: pattr = self.pattr else: pattr = '' return '%s\t%-17s %s' % (self.offset, self.type, pattr) def __hash__(self): return hash(self.type) def __getitem__(self, i): raise IndexError _JUMP_OPS_ = map(lambda op: dis.opname[op], dis.hasjrel + dis.hasjabs) def disassemble(co): """Disassemble a code object, returning a list of Token. The main part of this procedure is modelled after dis.diaassemble(). """ rv = [] customize = {} code = co.co_code cf = find_jump_targets(code) n = len(code) i = 0 while i < n: offset = i if cf.has_key(offset): for j in range(cf[offset]): rv.append(Token('COME_FROM', offset="%s_%d" % (offset, j) )) c = code[i] op = ord(c) opname = dis.opname[op] i = i+1 oparg = None; pattr = None if op >= dis.HAVE_ARGUMENT: oparg = ord(code[i]) + ord(code[i+1])*256 i = i+2 if op in dis.hasconst: const = co.co_consts[oparg] if type(const) == types.CodeType: oparg = const if const.co_name == '': assert opname == 'LOAD_CONST' opname = 'LOAD_LAMBDA' # verify uses 'pattr' for # comparism, since 'attr' now # hold Code(const) and thus # can not be used for # comparism (todo: thinkg # about changing this) #pattr = 'code_object @ 0x%x %s->%s' %\ # (id(const), const.co_filename, const.co_name) pattr = 'code_object ' + const.co_name else: pattr = `const` elif op in dis.hasname: pattr = co.co_names[oparg] elif op in dis.hasjrel: pattr = `i + oparg` elif op in dis.haslocal: pattr = co.co_varnames[oparg] elif op in dis.hascompare: pattr = dis.cmp_op[oparg] if opname == 'SET_LINENO': continue elif opname in ('BUILD_LIST', 'BUILD_TUPLE', 'BUILD_SLICE', 'UNPACK_LIST', 'UNPACK_TUPLE', 'UNPACK_SEQUENCE', 'MAKE_FUNCTION', 'CALL_FUNCTION', 'CALL_FUNCTION_VAR', 'CALL_FUNCTION_KW', 'CALL_FUNCTION_VAR_KW', 'DUP_TOPX', ): opname = '%s_%d' % (opname, oparg) customize[opname] = oparg rv.append(Token(opname, oparg, pattr, offset)) return rv, customize def find_jump_targets(code): """Detect all offsets in a byte code which are jump targets. Return the list of offsets. This procedure is modelled after dis.findlables(), but here for each target the number of jumps are counted. """ targets = {} n = len(code) i = 0 while i < n: c = code[i] op = ord(c) i = i+1 if op >= dis.HAVE_ARGUMENT: oparg = ord(code[i]) + ord(code[i+1])*256 i = i+2 label = -1 if op in dis.hasjrel: label = i+oparg # todo: absolut jumps #elif op in dis.hasjabs: # label = oparg if label >= 0: targets[label] = targets.get(label, 0) + 1 return targets # # Parsing # class AST: def __init__(self, type, kids=None): self.type = intern(type) if kids == None: kids = [] self._kids = kids def append(self, o): self._kids.append(o) def pop(self): return self._kids.pop() def __getitem__(self, i): return self._kids[i] def __setitem__(self, i, val): self._kids[i] = val def __delitem__(self, i): del self._kids[i] def __len__(self): return len(self._kids) def __getslice__(self, low, high): return self._kids[low:high] def __setslice__(self, low, high, seq): self._kids[low:high] = seq def __delslice__(self, low, high): del self._kids[low:high] def __cmp__(self, o): if isinstance(o, AST): return cmp(self.type, o.type) \ or cmp(self._kids, o._kids) else: return cmp(self.type, o) def __hash__(self): return hash(self.type) def __repr__(self): rv = str(self.type) for k in self._kids: rv = rv + '\n' + string.replace(str(k), '\n', '\n ') return rv # Some ASTs used for comparing code fragments (like 'return None' at # the end of functions). RETURN_LOCALS = AST('stmt', [ AST('return_stmt', [ AST('expr', [ Token('LOAD_LOCALS') ]), Token('RETURN_VALUE')]) ]) RETURN_NONE = AST('stmt', [ AST('return_stmt', [ AST('expr', [ Token('LOAD_CONST', pattr='None') ]), Token('RETURN_VALUE')]) ]) ASSIGN_DOC_STRING = lambda doc_string: \ AST('stmt', [ AST('assign', [ AST('expr', [ Token('LOAD_CONST', pattr=`doc_string`) ]), AST('designator', [ Token('STORE_NAME', pattr='__doc__')]) ])]) BUILD_TUPLE_0 = AST('expr', [ Token('BUILD_TUPLE_0') ] ) from spark import GenericASTBuilder, GenericASTMatcher class Parser(GenericASTBuilder): def __init__(self): GenericASTBuilder.__init__(self, AST, 'code') self.customized = {} def cleanup(self): """ Remove recursive references to allow garbage collector to collect this object. """ for dict in (self.rule2func, self.rules, self.rule2name, self.first): for i in dict.keys(): dict[i] = None for i in dir(self): setattr(self, i, None) def error(self, token): # output offset, too print "Syntax error at or near `%s' token at offset %s" % \ (`token`, token.offset) raise SystemExit def typestring(self, token): return token.type def p_funcdef(self, args): ''' stmt ::= funcdef funcdef ::= mkfunc STORE_FAST funcdef ::= mkfunc STORE_NAME ''' # new for Python2.0 # # UNPACK_SEQUENCE # number of tuple items # EXTENDED_ARG def p_list_comprehension(self, args): ''' expr ::= list_compr list_compr ::= lc_prep lc_for lc_cleanup lc_prep ::= BUILD_LIST_0 DUP_TOP LOAD_ATTR STORE_NAME lc_prep ::= BUILD_LIST_0 DUP_TOP LOAD_ATTR STORE_FAST lc_for ::= expr LOAD_CONST FOR_LOOP designator lc_for JUMP_ABSOLUTE COME_FROM lc_for ::= expr LOAD_CONST FOR_LOOP designator lc_if JUMP_ABSOLUTE COME_FROM lc_for ::= expr LOAD_CONST FOR_LOOP designator lc_body JUMP_ABSOLUTE COME_FROM lc_if ::= expr condjmp lc_body JUMP_FORWARD COME_FROM POP_TOP COME_FROM lc_body ::= LOAD_NAME expr CALL_FUNCTION_1 POP_TOP lc_body ::= LOAD_FAST expr CALL_FUNCTION_1 POP_TOP lc_cleanup ::= DELETE_NAME lc_cleanup ::= DELETE_FAST ''' def p_augmented_assign(self, args): ''' stmt ::= augassign1 stmt ::= augassign2 augassign1 ::= expr expr inplace_op designator augassign1 ::= expr expr inplace_op ROT_THREE STORE_SUBSCR augassign1 ::= expr expr inplace_op ROT_TWO STORE_SLICE+0 augassign1 ::= expr expr inplace_op ROT_THREE STORE_SLICE+1 augassign1 ::= expr expr inplace_op ROT_THREE STORE_SLICE+2 augassign1 ::= expr expr inplace_op ROT_FOUR STORE_SLICE+3 augassign2 ::= expr DUP_TOP LOAD_ATTR expr inplace_op ROT_TWO STORE_ATTR inplace_op ::= INPLACE_ADD inplace_op ::= INPLACE_SUBTRACT inplace_op ::= INPLACE_MULTIPLY inplace_op ::= INPLACE_DIVIDE inplace_op ::= INPLACE_MODULO inplace_op ::= INPLACE_POWER inplace_op ::= INPLACE_LSHIFT inplace_op ::= INPLACE_RSHIFT inplace_op ::= INPLACE_AND inplace_op ::= INPLACE_XOR inplace_op ::= INPLACE_OR ''' def p_assign(self, args): ''' stmt ::= assign assign ::= expr DUP_TOP designList assign ::= expr designator ''' def p_print(self, args): ''' stmt ::= print_stmt stmt ::= print_stmt_nl stmt ::= print_nl_stmt print_stmt ::= expr PRINT_ITEM print_nl_stmt ::= PRINT_NEWLINE print_stmt_nl ::= print_stmt print_nl_stmt ''' def p_print_to(self, args): ''' stmt ::= print_to stmt ::= print_to_nl stmt ::= print_nl_to print_to ::= expr print_to_items POP_TOP print_to_nl ::= expr print_to_items PRINT_NEWLINE_TO print_nl_to ::= expr PRINT_NEWLINE_TO print_to_items ::= print_to_items print_to_item print_to_items ::= print_to_item print_to_item ::= DUP_TOP expr ROT_TWO PRINT_ITEM_TO ''' # expr print_to* POP_TOP # expr { print_to* } PRINT_NEWLINE_TO def p_import15(self, args): ''' stmt ::= importstmt stmt ::= importfrom importstmt ::= IMPORT_NAME STORE_FAST importstmt ::= IMPORT_NAME STORE_NAME importfrom ::= IMPORT_NAME importlist POP_TOP importlist ::= importlist IMPORT_FROM importlist ::= IMPORT_FROM ''' def p_import20(self, args): ''' stmt ::= importstmt2 stmt ::= importfrom2 stmt ::= importstar2 importstmt2 ::= LOAD_CONST import_as importstar2 ::= LOAD_CONST IMPORT_NAME IMPORT_STAR importfrom2 ::= LOAD_CONST IMPORT_NAME importlist2 POP_TOP importlist2 ::= importlist2 import_as importlist2 ::= import_as import_as ::= IMPORT_NAME STORE_FAST import_as ::= IMPORT_NAME STORE_NAME import_as ::= IMPORT_NAME LOAD_ATTR STORE_FAST import_as ::= IMPORT_NAME LOAD_ATTR STORE_NAME import_as ::= IMPORT_FROM STORE_FAST import_as ::= IMPORT_FROM STORE_NAME ''' # 'import_as' can't use designator, since n_import_as() # needs to compare both kids' pattr def p_grammar(self, args): ''' code ::= stmts code ::= stmts ::= stmts stmt stmts ::= stmt stmts_opt ::= stmts stmts_opt ::= passstmt passstmt ::= designList ::= designator designator designList ::= designator DUP_TOP designList designator ::= STORE_FAST designator ::= STORE_NAME designator ::= STORE_GLOBAL designator ::= expr STORE_ATTR designator ::= expr STORE_SLICE+0 designator ::= expr expr STORE_SLICE+1 designator ::= expr expr STORE_SLICE+2 designator ::= expr expr expr STORE_SLICE+3 designator ::= store_subscr store_subscr ::= expr expr STORE_SUBSCR designator ::= unpack designator ::= unpack_list stmt ::= classdef stmt ::= call_stmt call_stmt ::= expr POP_TOP stmt ::= return_stmt return_stmt ::= expr RETURN_VALUE stmt ::= break_stmt break_stmt ::= BREAK_LOOP stmt ::= continue_stmt continue_stmt ::= JUMP_ABSOLUTE stmt ::= raise_stmt raise_stmt ::= exprlist RAISE_VARARGS raise_stmt ::= nullexprlist RAISE_VARARGS stmt ::= exec_stmt exec_stmt ::= expr exprlist DUP_TOP EXEC_STMT exec_stmt ::= expr exprlist EXEC_STMT stmt ::= assert stmt ::= assert2 stmt ::= ifstmt stmt ::= ifelsestmt stmt ::= whilestmt stmt ::= whileelsestmt stmt ::= forstmt stmt ::= forelsestmt stmt ::= trystmt stmt ::= tryfinallystmt stmt ::= DELETE_FAST stmt ::= DELETE_NAME stmt ::= DELETE_GLOBAL stmt ::= expr DELETE_SLICE+0 stmt ::= expr expr DELETE_SLICE+1 stmt ::= expr expr DELETE_SLICE+2 stmt ::= expr expr expr DELETE_SLICE+3 stmt ::= delete_subscr delete_subscr ::= expr expr DELETE_SUBSCR stmt ::= expr DELETE_ATTR kwarg ::= LOAD_CONST expr classdef ::= LOAD_CONST expr mkfunc CALL_FUNCTION_0 BUILD_CLASS STORE_NAME classdef ::= LOAD_CONST expr mkfunc CALL_FUNCTION_0 BUILD_CLASS STORE_FAST condjmp ::= JUMP_IF_FALSE POP_TOP condjmp ::= JUMP_IF_TRUE POP_TOP assert ::= expr JUMP_IF_FALSE POP_TOP expr JUMP_IF_TRUE POP_TOP LOAD_GLOBAL RAISE_VARARGS COME_FROM COME_FROM POP_TOP assert2 ::= expr JUMP_IF_FALSE POP_TOP expr JUMP_IF_TRUE POP_TOP LOAD_GLOBAL expr RAISE_VARARGS COME_FROM COME_FROM POP_TOP ifstmt ::= expr condjmp stmts_opt JUMP_FORWARD COME_FROM POP_TOP COME_FROM ifelsestmt ::= expr condjmp stmts_opt JUMP_FORWARD COME_FROM POP_TOP stmts COME_FROM trystmt ::= SETUP_EXCEPT stmts_opt POP_BLOCK JUMP_FORWARD COME_FROM except_stmt try_end ::= END_FINALLY COME_FROM try_end ::= except_else except_else ::= END_FINALLY COME_FROM stmts except_stmt ::= except_cond except_stmt COME_FROM except_stmt ::= except_conds try_end COME_FROM except_stmt ::= except try_end COME_FROM except_stmt ::= try_end except_conds ::= except_cond except_conds COME_FROM except_conds ::= except_cond ::= except_cond1 except_cond ::= except_cond2 except_cond1 ::= DUP_TOP expr COMPARE_OP JUMP_IF_FALSE POP_TOP POP_TOP POP_TOP POP_TOP stmts_opt JUMP_FORWARD COME_FROM POP_TOP except_cond2 ::= DUP_TOP expr COMPARE_OP JUMP_IF_FALSE POP_TOP POP_TOP designator POP_TOP stmts_opt JUMP_FORWARD COME_FROM POP_TOP except ::= POP_TOP POP_TOP POP_TOP stmts_opt JUMP_FORWARD tryfinallystmt ::= SETUP_FINALLY stmts_opt POP_BLOCK LOAD_CONST COME_FROM stmts_opt END_FINALLY whilestmt ::= SETUP_LOOP expr JUMP_IF_FALSE POP_TOP stmts_opt JUMP_ABSOLUTE COME_FROM POP_TOP POP_BLOCK COME_FROM whileelsestmt ::= SETUP_LOOP expr JUMP_IF_FALSE POP_TOP stmts_opt JUMP_ABSOLUTE COME_FROM POP_TOP POP_BLOCK stmts COME_FROM forstmt ::= SETUP_LOOP expr LOAD_CONST FOR_LOOP designator stmts_opt JUMP_ABSOLUTE COME_FROM POP_BLOCK COME_FROM forelsestmt ::= SETUP_LOOP expr LOAD_CONST FOR_LOOP designator stmts_opt JUMP_ABSOLUTE COME_FROM POP_BLOCK stmts COME_FROM ''' def p_expr(self, args): ''' expr ::= mklambda expr ::= mkfunc expr ::= SET_LINENO expr ::= LOAD_FAST expr ::= LOAD_NAME expr ::= LOAD_CONST expr ::= LOAD_GLOBAL expr ::= LOAD_LOCALS expr ::= expr LOAD_ATTR expr ::= binary_expr binary_expr ::= expr expr binary_op binary_op ::= BINARY_ADD binary_op ::= BINARY_SUBTRACT binary_op ::= BINARY_MULTIPLY binary_op ::= BINARY_DIVIDE binary_op ::= BINARY_MODULO binary_op ::= BINARY_LSHIFT binary_op ::= BINARY_RSHIFT binary_op ::= BINARY_AND binary_op ::= BINARY_OR binary_op ::= BINARY_XOR binary_op ::= BINARY_POWER expr ::= binary_subscr binary_subscr ::= expr expr BINARY_SUBSCR expr ::= expr expr DUP_TOPX_2 BINARY_SUBSCR expr ::= cmp expr ::= expr UNARY_POSITIVE expr ::= expr UNARY_NEGATIVE expr ::= expr UNARY_CONVERT expr ::= expr UNARY_INVERT expr ::= expr UNARY_NOT expr ::= mapexpr expr ::= expr SLICE+0 expr ::= expr expr SLICE+1 expr ::= expr expr SLICE+2 expr ::= expr expr expr SLICE+3 expr ::= expr DUP_TOP SLICE+0 expr ::= expr expr DUP_TOPX_2 SLICE+1 expr ::= expr expr DUP_TOPX_2 SLICE+2 expr ::= expr expr expr DUP_TOPX_3 SLICE+3 expr ::= and expr ::= or or ::= expr JUMP_IF_TRUE POP_TOP expr COME_FROM and ::= expr JUMP_IF_FALSE POP_TOP expr COME_FROM cmp ::= cmp_list cmp ::= compare compare ::= expr expr COMPARE_OP cmp_list ::= expr cmp_list1 ROT_TWO POP_TOP COME_FROM cmp_list1 ::= expr DUP_TOP ROT_THREE COMPARE_OP JUMP_IF_FALSE POP_TOP cmp_list1 COME_FROM cmp_list1 ::= expr DUP_TOP ROT_THREE COMPARE_OP JUMP_IF_FALSE POP_TOP cmp_list2 COME_FROM cmp_list2 ::= expr COMPARE_OP JUMP_FORWARD mapexpr ::= BUILD_MAP kvlist kvlist ::= kvlist kv kvlist ::= kv ::= DUP_TOP expr ROT_TWO expr STORE_SUBSCR exprlist ::= exprlist expr exprlist ::= expr nullexprlist ::= ''' def nonterminal(self, nt, args): collect = ('stmts', 'exprlist', 'kvlist') if nt in collect and len(args) > 1: # # Collect iterated thingies together. # rv = args[0] rv.append(args[1]) else: rv = GenericASTBuilder.nonterminal(self, nt, args) return rv def __ambiguity(self, children): # only for debugging! to be removed hG/2000-10-15 print children return GenericASTBuilder.ambiguity(self, children) def resolve(self, list): if len(list) == 2 and 'funcdef' in list and 'assign' in list: return 'funcdef' #sys.stderr.writelines( ['resolve ', str(list), '\n'] ) return GenericASTBuilder.resolve(self, list) nop = lambda self, args: None def parse(tokens, customize): p = Parser() # # Special handling for opcodes that take a variable number # of arguments -- we add a new rule for each: # # expr ::= {expr}^n BUILD_LIST_n # expr ::= {expr}^n BUILD_TUPLE_n # expr ::= {expr}^n BUILD_SLICE_n # unpack_list ::= UNPACK_LIST {expr}^n # unpack ::= UNPACK_TUPLE {expr}^n # unpack ::= UNPACK_SEQEUENE {expr}^n # mkfunc ::= {expr}^n LOAD_CONST MAKE_FUNCTION_n # expr ::= expr {expr}^n CALL_FUNCTION_n # expr ::= expr {expr}^n CALL_FUNCTION_VAR_n POP_TOP # expr ::= expr {expr}^n CALL_FUNCTION_VAR_KW_n POP_TOP # expr ::= expr {expr}^n CALL_FUNCTION_KW_n POP_TOP # for k, v in customize.items(): ## avoid adding the same rule twice to this parser #if p.customized.has_key(k): # continue #p.customized[k] = None #nop = lambda self, args: None op = k[:string.rfind(k, '_')] if op in ('BUILD_LIST', 'BUILD_TUPLE', 'BUILD_SLICE'): rule = 'expr ::= ' + 'expr '*v + k elif op in ('UNPACK_TUPLE', 'UNPACK_SEQUENCE'): rule = 'unpack ::= ' + k + ' designator'*v elif op == 'UNPACK_LIST': rule = 'unpack_list ::= ' + k + ' designator'*v elif op == 'DUP_TOPX': # no need to add a rule pass #rule = 'dup_topx ::= ' + 'expr '*v + k elif op == 'MAKE_FUNCTION': p.addRule('mklambda ::= %s LOAD_LAMBDA %s' % ('expr '*v, k), nop) rule = 'mkfunc ::= %s LOAD_CONST %s' % ('expr '*v, k) elif op in ('CALL_FUNCTION', 'CALL_FUNCTION_VAR', 'CALL_FUNCTION_VAR_KW', 'CALL_FUNCTION_KW'): na = (v & 0xff) # positional parameters nk = (v >> 8) & 0xff # keyword parameters # number of apply equiv arguments: nak = ( len(op)-len('CALL_FUNCTION') ) / 3 rule = 'expr ::= expr ' + 'expr '*na + 'kwarg '*nk \ + 'expr ' * nak + k else: raise 'unknown customize token %s' % k p.addRule(rule, nop) ast = p.parse(tokens) p.cleanup() return ast # # Decompilation (walking AST) # # All table-driven. Step 1 determines a table (T) and a path to a # table key (K) from the node type (N) (other nodes are shown as O): # # N N N&K # / | ... \ / | ... \ / | ... \ # O O O O O K O O O # | # K # # MAP_R0 (TABLE_R0) MAP_R (TABLE_R) MAP_DIRECT (TABLE_DIRECT) # # The default is a direct mapping. The key K is then extracted from the # subtree and used to find a table entry T[K], if any. The result is a # format string and arguments (a la printf()) for the formatting engine. # Escapes in the format string are: # # %c evaluate N[A] recursively* # %C evaluate N[A[0]]..N[A[1]] recursively, separate by A[2]* # %, print ',' if last %C only printed one item (for tuples) # %| tab to current indentation level # %+ increase current indentation level # %- decrease current indentation level # %{...} evaluate ... in context of N # %% literal '%' # # * indicates an argument (A) required. # # The '%' may optionally be followed by a number (C) in square brackets, which # makes the engine walk down to N[C] before evaluating the escape code. # from spark import GenericASTTraversal #TAB = '\t' # as God intended TAB = ' ' *4 # is less spacy than "\t" TABLE_R = { 'build_tuple2': ( '%C', (0,-1,', ') ), 'POP_TOP': ( '%|%c\n', 0 ), 'STORE_ATTR': ( '%c.%[1]{pattr}', 0), # 'STORE_SUBSCR': ( '%c[%c]', 0, 1 ), 'STORE_SLICE+0':( '%c[:]', 0 ), 'STORE_SLICE+1':( '%c[%c:]', 0, 1 ), 'STORE_SLICE+2':( '%c[:%c]', 0, 1 ), 'STORE_SLICE+3':( '%c[%c:%c]', 0, 1, 2 ), 'JUMP_ABSOLUTE':( '%|continue\n', ), 'DELETE_SLICE+0':( '%|del %c[:]\n', 0 ), 'DELETE_SLICE+1':( '%|del %c[%c:]\n', 0, 1 ), 'DELETE_SLICE+2':( '%|del %c[:%c]\n', 0, 1 ), 'DELETE_SLICE+3':( '%|del %c[%c:%c]\n', 0, 1, 2 ), 'DELETE_ATTR': ( '%|del %c.%[-1]{pattr}\n', 0 ), #'EXEC_STMT': ( '%|exec %c in %[1]C\n', 0, (0,sys.maxint,', ') ), 'BINARY_SUBSCR':( '%c[%c]', 0, 1), # required for augmented assign 'UNARY_POSITIVE':( '+%c', 0 ), 'UNARY_NEGATIVE':( '-%c', 0 ), 'UNARY_CONVERT':( '`%c`', 0 ), 'UNARY_INVERT': ( '~%c', 0 ), 'UNARY_NOT': ( '(not %c)', 0 ), 'SLICE+0': ( '%c[:]', 0 ), 'SLICE+1': ( '%c[%c:]', 0, 1 ), 'SLICE+2': ( '%c[:%c]', 0, 1 ), 'SLICE+3': ( '%c[%c:%c]', 0, 1, 2 ), } TABLE_R0 = { # 'BUILD_LIST': ( '[%C]', (0,-1,', ') ), # 'BUILD_TUPLE': ( '(%C)', (0,-1,', ') ), # 'CALL_FUNCTION':( '%c(%C)', 0, (1,-1,', ') ), } TABLE_DIRECT = { 'BINARY_ADD': ( '+' ,), 'BINARY_SUBTRACT': ( '-' ,), 'BINARY_MULTIPLY': ( '*' ,), 'BINARY_DIVIDE': ( '/' ,), 'BINARY_MODULO': ( '%%',), 'BINARY_POWER': ( '**',), 'BINARY_LSHIFT': ( '<<',), 'BINARY_RSHIFT': ( '>>',), 'BINARY_AND': ( '&' ,), 'BINARY_OR': ( '|' ,), 'BINARY_XOR': ( '^' ,), 'INPLACE_ADD': ( '+=' ,), 'INPLACE_SUBTRACT': ( '-=' ,), 'INPLACE_MULTIPLY': ( '*=' ,), 'INPLACE_DIVIDE': ( '/=' ,), 'INPLACE_MODULO': ( '%%=',), 'INPLACE_POWER': ( '**=',), 'INPLACE_LSHIFT': ( '<<=',), 'INPLACE_RSHIFT': ( '>>=',), 'INPLACE_AND': ( '&=' ,), 'INPLACE_OR': ( '|=' ,), 'INPLACE_XOR': ( '^=' ,), 'binary_expr': ( '(%c %c %c)', 0, -1, 1 ), 'IMPORT_FROM': ( '%{pattr}', ), 'LOAD_ATTR': ( '.%{pattr}', ), 'LOAD_FAST': ( '%{pattr}', ), 'LOAD_NAME': ( '%{pattr}', ), 'LOAD_GLOBAL': ( '%{pattr}', ), 'LOAD_LOCALS': ( 'locals()', ), #'LOAD_CONST': ( '%{pattr}', ), handled below 'DELETE_FAST': ( '%|del %{pattr}\n', ), 'DELETE_NAME': ( '%|del %{pattr}\n', ), 'DELETE_GLOBAL':( '%|del %{pattr}\n', ), 'delete_subscr':( '%|del %c[%c]\n', 0, 1,), 'binary_subscr':( '%c[%c]', 0, 1), 'store_subscr': ( '%c[%c]', 0, 1), 'STORE_FAST': ( '%{pattr}', ), 'STORE_NAME': ( '%{pattr}', ), 'STORE_GLOBAL': ( '%{pattr}', ), 'unpack': ( '(%C,)', (1, sys.maxint, ', ') ), 'unpack_list': ( '[%C]', (1, sys.maxint, ', ') ), 'list_compr': ( '[ %c ]', 1), # 'lc_for': ( ' for %c in %c', 3, 0 ), 'lc_for_nest': ( ' for %c in %c%c', 3, 0, 4 ), 'lc_if': ( ' if %c', 0 ), 'lc_body': ( '%c', 1), 'lc_body__': ( '', ), 'assign': ( '%|%c = %c\n', -1, 0 ), 'augassign1': ( '%|%c %c %c\n', 0, 2, 1), 'augassign2': ( '%|%c%c %c %c\n', 0, 2, -3, -4), #'dup_topx': ('%c', 0), 'designList': ( '%c = %c', 0, -1 ), 'and': ( '(%c and %c)', 0, 3 ), 'or': ( '(%c or %c)', 0, 3 ), 'compare': ( '(%c %[-1]{pattr} %c)', 0, 1 ), 'cmp_list': ('%c %c', 0, 1), 'cmp_list1': ('%[3]{pattr} %c %c', 0, -2), 'cmp_list2': ('%[1]{pattr} %c', 0), 'classdef': ( '\n%|class %[0]{pattr[1:-1]}%c:\n%+%{build_class}%-', 1 ), 'funcdef': ( '\n%|def %c\n', 0), 'kwarg': ( '%[0]{pattr[1:-1]}=%c', 1), 'importstmt': ( '%|import %[0]{pattr}\n', ), 'importfrom': ( '%|from %[0]{pattr} import %c\n', 1 ), 'importlist': ( '%C', (0, sys.maxint, ', ') ), 'importstmt2': ( '%|import %c\n', 1), 'importstar2': ( '%|from %[1]{pattr} import *\n', ), 'importfrom2': ( '%|from %[1]{pattr} import %c\n', 2 ), 'importlist2': ( '%C', (0, sys.maxint, ', ') ), 'assert': ( '%|assert %c\n' , 3 ), 'assert2': ( '%|assert %c, %c\n' , 3, -5 ), 'print_stmt': ( '%|print %c,\n', 0 ), 'print_stmt_nl': ( '%|print %[0]C\n', (0,1, None) ), 'print_nl_stmt': ( '%|print\n', ), 'print_to': ( '%|print >> %c, %c,\n', 0, 1 ), 'print_to_nl': ( '%|print >> %c, %c\n', 0, 1 ), 'print_nl_to': ( '%|print >> %c\n', 0 ), 'print_to_items': ( '%C', (0, 2, ', ') ), 'call_stmt': ( '%|%c\n', 0), 'break_stmt': ( '%|break\n', ), 'continue_stmt':( '%|continue\n', ), 'raise_stmt': ( '%|raise %[0]C\n', (0,sys.maxint,', ') ), 'return_stmt': ( '%|return %c\n', 0), 'return_lambda': ( '%c', 0), 'ifstmt': ( '%|if %c:\n%+%c%-', 0, 2 ), 'ifelsestmt': ( '%|if %c:\n%+%c%-%|else:\n%+%c%-', 0, 2, -2 ), 'ifelifstmt': ( '%|if %c:\n%+%c%-%c', 0, 2, -2 ), 'elifelifstmt': ( '%|elif %c:\n%+%c%-%c', 0, 2, -2 ), 'elifstmt': ( '%|elif %c:\n%+%c%-', 0, 2 ), 'elifelsestmt': ( '%|elif %c:\n%+%c%-%|else:\n%+%c%-', 0, 2, -2 ), 'whilestmt': ( '%|while %c:\n%+%c%-\n', 1, 4 ), 'whileelsestmt':( '%|while %c:\n%+%c%-\n%|else:\n%+%c%-\n', 1, 4, 9 ), 'forstmt': ( '%|for %c in %c:\n%+%c%-\n', 4, 1, 5 ), 'forelsestmt': ( '%|for %c in %c:\n%+%c%-\n%|else:\n%+%c%-\n', 4, 1, 5, 9 ), 'trystmt': ( '%|try:\n%+%c%-%c', 1, 5 ), 'except': ( '%|except:\n%+%c%-', 3 ), 'except_cond1': ( '%|except %c:\n%+%c%-', 1, 8 ), 'except_cond2': ( '%|except %c, %c:\n%+%c%-', 1, 6, 8 ), 'except_else': ( '%|else:\n%+%c%-', 2 ), 'tryfinallystmt':( '%|try:\n%+%c%-\n%|finally:\n%+%c%-\n', 1, 5 ), 'passstmt': ( '%|pass\n', ), 'STORE_FAST': ( '%{pattr}', ), 'kv': ( '%c: %c', 3, 1 ), 'mapexpr': ( '{%[1]C}', (0,sys.maxint,', ') ), } MAP_DIRECT = (TABLE_DIRECT, ) MAP_R0 = (TABLE_R0, -1, 0) MAP_R = (TABLE_R, -1) MAP = { 'stmt': MAP_R, 'designator': MAP_R, 'expr': MAP_R, 'exprlist': MAP_R0, } ASSIGN_TUPLE_PARAM = lambda param_name: \ AST('expr', [ Token('LOAD_FAST', pattr=param_name) ]) def get_tuple_parameter(ast, name): """ If the name of the formal parameter starts with dot, it's a tuple parameter, like this: def MyFunc(xx, (a,b,c), yy): print a, b*2, c*42 In byte-code, the whole tuple is assigned to parameter '.1' and then the tuple gets unpacked to 'a', 'b' and 'c'. Since identifiers starting with a dot are illegal in Python, we can search for the byte-code equivalent to '(a,b,c) = .1' """ assert ast == 'code' and ast[0] == 'stmts' for i in xrange(len(ast[0])): # search for an assign-statement assert ast[0][i] == 'stmt' node = ast[0][i][0] if node == 'assign' \ and node[0] == ASSIGN_TUPLE_PARAM(name): # okay, this assigns '.n' to something del ast[0][i] # walk lhs; this # returns a tuple of identifiers as used # within the function definition assert node[1] == 'designator' # if lhs is not a UNPACK_TUPLE (or equiv.), # add parenteses to make this a tuple if node[1][0] not in ('unpack', 'unpack_list'): return '(' + walk(node[1]) + ')' return walk(node[1]) raise "Can't find tuple parameter" % name def make_function(self, code, defparams, isLambda, nested=1): """Dump function defintion, doc string, and function body.""" def build_param(ast, name, default): """build parameters: - handle defaults - handle format tuple parameters """ # if formal parameter is a tuple, the paramater name # starts with a dot (eg. '.1', '.2') if name[0] == '.': # replace the name with the tuple-string name = get_tuple_parameter(ast, name) if default: if Showast: print '--', name print default print '--' result = '%s = %s' % ( name, walk(default, indent=0) ) ##w = Walk(default, 0) ##result = '%s = %s' % ( name, w.traverse() ) ##del w # hg/2000-09-03 if result[-2:] == '= ': # default was 'LOAD_CONST None' result = result + 'None' return result else: return name def writeParams(self, params): for i in range(len(params)): if i > 0: self.f.write(', ') self.f.write(params[i]) assert type(code) == types.CodeType code = Code(code) #assert isinstance(code, Code) ast = _build_ast(self.f, code._tokens, code._customize) code._tokens = None # save memory assert ast == 'code' and ast[0] == 'stmts' if isLambda: # convert 'return' statement to expression #assert len(ast[0]) == 1 wrong, see 'lambda (r,b): r,b,g' assert ast[-1][-1] == 'stmt' assert len(ast[-1][-1]) == 1 assert ast[-1][-1][0] == 'return_stmt' ast[-1][-1][0].type = 'return_lambda' else: if ast[0][-1] == RETURN_NONE: # Python adds a 'return None' to the # end of any function; remove it ast[0].pop() # remove last node # add defaults values to parameter names argc = code.co_argcount paramnames = list(code.co_varnames[:argc]) # defaults are for last n parameters, thus reverse paramnames.reverse(); defparams.reverse() # build parameters # ##This would be a nicer piece of code, but I can't get this to work ## now, have to find a usable lambda constuct hG/2000-09-05 ##params = map(lambda name, default: build_param(ast, name, default), ## paramnames, defparams) params = [] for name, default in map(lambda a,b: (a,b), paramnames, defparams): params.append( build_param(ast, name, default) ) params.reverse() # back to correct order if 4 & code.co_flags: # flag 2 -> variable number of args params.append('*%s' % code.co_varnames[argc]) argc = argc +1 if 8 & code.co_flags: # flag 3 -> keyword args params.append('**%s' % code.co_varnames[argc]) argc = argc +1 # dump parameter list (with default values) indent = TAB * self.indent if isLambda: self.f.write('lambda ') writeParams(self, params) self.f.write(': ') else: self.f.write('(') writeParams(self, params) self.f.write('):\n') #self.f.write('%s#flags:\t%i\n' % (indent, code.co_flags)) if code.co_consts[0] != None: # docstring exists, dump it self.f.writelines([indent, `code.co_consts[0]`, '\n']) _gen_source(self.f, ast, code._customize, self.indent, isLambda=isLambda) code._tokens = None; code._customize = None # save memory def build_class(self, code): """Dump class definition, duc string and class body.""" assert type(code) == types.CodeType code = Code(code) #assert isinstance(code, Code) indent = TAB * self.indent #self.f.write('%s#flags:\t%i\n' % (indent, code.co_flags)) ast = _build_ast(self.f, code._tokens, code._customize) code._tokens = None # save memory assert ast == 'code' and ast[0] == 'stmts' # if docstring exists, dump it if code.co_consts[0] != None \ and ast[0][0] == ASSIGN_DOC_STRING(code.co_consts[0]): #print '\n\n>>-->>doc string set\n\n' self.f.writelines( [indent,repr(code.co_consts[0]), '\n'] ) del ast[0][0] # the function defining a class normally returns locals(); we # don't want this to show up in the source, thus remove the node if ast[0][-1] == RETURN_LOCALS: ast[0].pop() # remove last node _gen_source(self.f, ast, code._customize, self.indent) code._tokens = None; code._customize = None # save memory __globals_tokens__ = ('STORE_GLOBAL', 'DELETE_GLOBAL') # 'LOAD_GLOBAL' def find_globals(node, globals): """Find globals in this statement.""" for n in node: if isinstance(n, AST): if n != 'stmt': # skip nested statements globals = find_globals(n, globals) elif n.type in __globals_tokens__: globals[n.pattr] = None return globals class Walk(GenericASTTraversal): def __init__(self, ast, indent=0, isLambda=0): GenericASTTraversal.__init__(self, ast) self._globals = {} self.f = cStringIO.StringIO() self.f.seek(0) self.indent = indent self.isLambda = isLambda def __del__(self): self.f.close() def traverse(self, node=None): self.preorder(node) return self.f.getvalue() def n_LOAD_CONST(self, node): data = node.pattr if data == 'None': # LOAD_CONST 'None' only occurs, when None is # implicit eg. in 'return' w/o params pass elif data == 'Ellipsis': self.f.write('...') elif data[0] == '-': # assume negative integer constant # convert to hex, since decimal representation # would result in 'LOAD_CONST; UNARY_NEGATIVE' self.f.write('0x%x' % int(data)) else: self.f.write(data) def n_delete_subscr(self, node): #print >>self.f, '>#', node #print >>self.f, '---' maybe_tuple = node[-2][-1] #print >>self.f, '##', maybe_tuple, maybe_tuple.type[:11] if maybe_tuple.type[:11] == 'BUILD_TUPLE': maybe_tuple.type = 'build_tuple2' #print >>self.f, '##', node #print >>self.f, '##', maybe_tuple.type self.default(node) n_store_subscr = n_binary_subscr = n_delete_subscr def __n_stmts(self, node): # optimize "print 1, ; print" last = None; i = 0 while i < len(node): n = node[i] assert(n == 'stmt') if n[0] == 'print_nl_stmt' and \ last is not None and \ last[0] == 'print_stmt': last[0].type = 'print_stmt_nl' del node[i] last = None else: last = n i = i + 1 self.default(node) def n_stmt(self, node): if not self.isLambda: indent = TAB * self.indent for g in find_globals(node, {}).keys(): self.f.writelines( [indent, 'global ', g, '\n'] ) ## nice output does not work since engine() ## creates a new Walk instance when recursing ## TODO: reconsider this: engine() no longer ## creates a new Walk instancew hG/2000-12-31 ## if not self._globals.has_key(g): ## self._globals[g] = None ## self.f.writelines( [TAB * self.indent, ## 'global ', ## g, '\n'] ) self.default(node) def n_exec_stmt(self, node): """ exec_stmt ::= expr exprlist DUP_TOP EXEC_STMT exec_stmt ::= expr exprlist EXEC_STMT """ w = Walk(node, indent=self.indent) w.engine(( '%|exec %c in %[1]C', 0, (0,sys.maxint,', ') ), node) s = w.f.getvalue() del w if s[-3:] == 'in ': s = s[:-3] self.f.writelines( [s, '\n'] ) node[:] = [] # avoid print out when recursive descenting def n_ifelsestmt(self, node, preprocess=0): if len(node[-2]) == 1: ifnode = node[-2][0][0] if ifnode == 'ifelsestmt': node.type = 'ifelifstmt' self.n_ifelsestmt(ifnode, preprocess=1) if ifnode == 'ifelifstmt': ifnode.type = 'elifelifstmt' elif ifnode == 'ifelsestmt': ifnode.type = 'elifelsestmt' elif ifnode == 'ifstmt': node.type = 'ifelifstmt' ifnode.type = 'elifstmt' if not preprocess: self.default(node) def n_import_as(self, node): iname = node[0].pattr; sname = node[-1].pattr if iname == sname \ or iname[:len(sname)+1] == (sname+'.'): self.f.write(iname) else: self.f.writelines([iname, ' as ', sname]) node[:] = [] # avoid print out when recursive descenting def n_mkfunc(self, node): defparams = node[0:-2] code = node[-2].attr node[:] = [] # avoid print out when recursive descenting self.indent = self.indent + 1 self.f.write(code.co_name) make_function(self, code, defparams, isLambda=0) self.indent = self.indent - 1 def n_mklambda(self, node): defparams = node[0:-2] code = node[-2].attr node[:] = [] # avoid print out when recursive descenting make_function(self, code, defparams, isLambda=1) def n_classdef(self, node): self.f.writelines(['\n', TAB * self.indent, 'class ']) self.f.write(node[0].pattr[1:-1]) node._code = node[-4][0].attr # avoid print out when recursive descenting if node[1] == BUILD_TUPLE_0: node[:] = [] else: node[:] = [ node[1] ] def n_classdef_exit(self, node): self.f.write(':\n') self.indent = self.indent +1 # '\n%|class %[0]{pattr[1:-1]}%c:\n%+%{build_class}%-', 1 ), # -4 -> MAKE_FUNCTION; -2 -> LOAD_CONST (code) build_class(self,node._code) self.indent = self.indent -1 node._code = None # save memory def n_lc_for(self, node): node.type = 'lc_for_nest' content = node[4] while content == 'lc_for': content.type = 'lc_for_nest' content = content[4] while content == 'lc_if': content = content[2] assert content == 'lc_body' self.preorder(content) content.type = 'lc_body__' self.default(node) def engine(self, entry, startnode): #self.f.write("-----\n") #self.f.write(str(startnode.__dict__)); self.f.write('\n') escape = re.compile(r''' % ( \[ (?P -? \d+ ) \] )? ((?P [^{] ) | ( [{] (?P [^}]* ) [}] )) ''', re.VERBOSE) fmt = entry[0] n = len(fmt) lastC = 0 arg = 1 i = 0 while i < n: m = escape.match(fmt, i) if m is None: self.f.write(fmt[i]) i = i + 1 continue i = m.end() typ = m.group('type') or '{' node = startnode try: if m.group('child'): node = node[string.atoi(m.group('child'))] except: print node.__dict__ raise if typ == '%': self.f.write('%') elif typ == '+': self.indent = self.indent + 1 elif typ == '-': self.indent = self.indent - 1 elif typ == '|': self.f.write(TAB * self.indent) elif typ == ',': if lastC == 1: self.f.write(',') elif typ == 'c': self.traverse(node[entry[arg]]) ##w = Walk(node[entry[arg]], self.indent) ##self.f.write(w.traverse()) ##del w # hg/2000-09-03 arg = arg + 1 elif typ == 'C': low, high, sep = entry[arg] lastC = remaining = len(node[low:high]) for subnode in node[low:high]: self.traverse(subnode) ##w = Walk(subnode, self.indent) ##self.f.write(w.traverse()) ##del w # hg/2000-09-03 remaining = remaining - 1 if remaining > 0: self.f.write(sep) arg = arg + 1 elif typ == '{': d = node.__dict__ expr = m.group('expr') if expr == 'build_class': # -4 -> MAKE_FUNCTION; -2 -> LOAD_CONST (code) build_class(self,node[-4][-2].attr) else: try: self.f.write(eval(expr, d, d)) except: print node raise def default(self, node): mapping = MAP.get(node, MAP_DIRECT) table = mapping[0] key = node for i in mapping[1:]: key = key[i] if table.has_key(key): self.engine(table[key], node) self.prune() def walk(ast, customize={}, indent=0, isLambda=0): w = Walk(ast, indent, isLambda=isLambda) # # Special handling for opcodes that take a variable number # of arguments -- we add a new entry for each in TABLE_R. # for k, v in customize.items(): op = k[:string.rfind(k, '_')] if op == 'BUILD_LIST': TABLE_R[k] = ( '[%C]', (0,-1,', ') ) elif op == 'BUILD_SLICE': TABLE_R[k] = ( '%C', (0,-1,':') ) elif op == 'BUILD_TUPLE': TABLE_R[k] = ( '(%C%,)', (0,-1,', ') ) elif op == 'CALL_FUNCTION': TABLE_R[k] = ( '%c(%C)', 0, (1,-1,', ') ) elif op in ('CALL_FUNCTION_VAR', 'CALL_FUNCTION_VAR_KW', 'CALL_FUNCTION_KW'): if v == 0: str = '%c(%C' # '%C' is a dummy here ... p2 = (0, 0, None) # .. because of this else: str = '%c(%C, ' p2 = (1,-2, ', ') if op == 'CALL_FUNCTION_VAR': str = str + '*%c)' entry = (str, 0, p2, -2) elif op == 'CALL_FUNCTION_KW': str = str + '**%c)' entry = (str, 0, p2, -2) else: str = str + '*%c, **%c)' if p2[2]: p2 = (1,-3, ', ') entry = (str, 0, p2, -3, -2) TABLE_R[k] = entry result = w.traverse() return result #-- end of (de-)compiler --- #-- start Showasm = 0 Showast = 0 __real_out = None def _tokenize(out, co): """Disassemble code object into a token list""" assert type(co) == types.CodeType tokens, customize = disassemble(co) # See the disassembly.. if Showasm and out is not None: for t in tokens: out.write('%s\n' % t) out.write('\n') return tokens, customize def _build_ast(out, tokens, customize): assert type(tokens) == types.ListType assert isinstance(tokens[0], Token) # Build AST from disassembly. try: ast = parse(tokens, customize) except: # parser failed, dump disassembly #if not Showasm: __real_out.write('--- This code section failed: ---\n') for t in tokens: __real_out.write('%s\n' % t) __real_out.write('\n') raise return ast def _gen_source(out, ast, customize, indent=0, isLambda=0): """convert AST to source code""" if Showast: out.write(`ast`) # if code would be empty, append 'pass' if len(ast[0]) == 0: out.write(indent * TAB) out.write('pass\n') else: out.write(walk(ast, customize, indent, isLambda=isLambda)) def decompyle(co, out=None, indent=0, showasm=0, showast=0): """ diassembles a given code block 'co' """ assert type(co) == types.CodeType global Showasm, Showast Showasm = showasm Showast = showast if not out: out = sys.stdout global __real_out __real_out = out # store final output stream for case of error tokens, customize = _tokenize(out, co) ast = _build_ast(out, tokens, customize) tokens = None # save memory assert ast == 'code' and ast[0] == 'stmts' # convert leading '__doc__ = "..." into doc string if ast[0][0] == ASSIGN_DOC_STRING(co.co_consts[0]): out.writelines( [repr(co.co_consts[0]), '\n'] ) del ast[0][0] if ast[0][-1] == RETURN_NONE: ast[0].pop() # remove last node #todo: if empty, add 'pass' _gen_source(out, ast, customize, indent) def decompyle_file(filename, outstream=None, showasm=0, showast=0): """ decompile Python byte-code file (.pyc) """ co = _load_module(filename) decompyle(co, out=outstream, showasm=showasm, showast=showast) co = None