import opcode import sys import func_smash OP_LOAD = {"LOAD_CONST": True, "LOAD_FAST": False, "LOAD_GLOBAL": False} OP_BINARY = {"BINARY_POWER": "**", "BINARY_MULTIPLY": "*", "BINARY_DIVIDE": "/", "BINARY_MODULO": "%", "BINARY_ADD": "+", "BINARY_SUBTRACT": "-", "BINARY_FLOOR_DIVIDE": "//", "BINARY_TRUE_DIVIDE": "/", "BINARY_LSHIFT": "<<", "BINARY_RSHIFT": ">>", "BINARY_AND": "&", "BINARY_XOR": "^", "BINARY_OR": "|"} OP_INPLACE = {"INPLACE_FLOOR_DIVIDE": "//", "INPLACE_TRUE_DIVIDE": "/", "INPLACE_ADD": "+", "INPLACE_SUBTRACT": "-", "INPLACE_MULTIPLY": "*", "INPLACE_DIVIDE": "/", "INPLACE_MODULO": "%", "INPLACE_POWER": "**", "INPLACE_LSHIFT": "<<", "INPLACE_RSHIFT": ">>", "INPLACE_AND": "&", "INPLACE_XOR": "^", "INPLACE_OR": "|"} OP_SUBSCR = ("BINARY_SUBSCR", "INPLACE_SUBSCR") OP_BUILD = {"BUILD_TUPLE": ("(", ")"), "BUILD_LIST": ("[", "]"), "BUILD_SET": ("{", "}")} TAB = 4 def prettify_function(func, indent=0): args = _get_func_args(func) _print(indent, "def {0}({1}):".format(func.func_name, args)) prettify_code(func.__code__, indent=indent+TAB) def prettify_code(codeobj, indent=0): codes = [] codestring = codeobj.co_code length = len(codestring) i = 0 while i < length: code = ord(codestring[i]) i += 1 if code >= opcode.HAVE_ARGUMENT: arg = func_smash._get_argument(codeobj, codestring, i, code) i += 2 codes.append((code, arg)) else: codes.append((code, None)) block = _parse_codestring(codes) block.display(indent) def _get_func_args(func): codeobj = func.__code__ count = codeobj.co_argcount if count == 0: return "" return ", ".join([arg for arg in codeobj.co_varnames[:count]]) def _parse_codestring(codes): stack = Stack() print_buffer = [] main_block = block = Block() i2block = {} drops = [] elses = [] i = 0 for instruction in codes: code, arg = instruction opname = opcode.opname[code] while i in elses: elses.remove(i) block.toggle() while i in drops: drops.remove(i) block = block.parent() i2block[i] = block if code >= opcode.HAVE_ARGUMENT: i += 3 else: i += 1 if opname in OP_LOAD: stack.push(arg, is_literal=OP_LOAD[opname]) elif opname in OP_BINARY: tos, tos1 = stack.pop(), stack.pop() stack.push(" ".join((tos1, OP_BINARY[opname], tos))) elif opname in OP_INPLACE: # Works, but doesn't use inplace op magic tos, tos1 = stack.pop(), stack.pop() stack.push(" ".join((tos1, OP_INPLACE[opname], tos))) elif opname in OP_SUBSCR: tos, tos1 = stack.pop(), stack.pop() stack.push("{0}[{1}]".format(tos1, tos)) elif opname in OP_BUILD: args = [] for i in xrange(arg): args.append(stack.pop()) args.reverse() start, end = OP_BUILD[opname] stack.push(start + ", ".join(args) + end) elif opname == "BUILD_MAP": stack.push("{}") elif opname == "STORE_FAST": block.put(arg, "=", stack.pop()) elif opname == "STORE_MAP": key, value = stack.pop(), stack.pop() pair = ": ".join((key, value)) oldmap = stack.pop() if oldmap == "{}": newmap = "{" + pair + "}" else: newmap = oldmap[:-1] + ", " + pair + "}" stack.push(newmap) elif opname == "LOAD_ATTR": tos = stack.pop() new_tos = tos + "." + arg stack.push(new_tos) elif opname == "POP_TOP": block.put(stack.pop()) elif opname == "CALL_FUNCTION": numargs, numkwargs = arg args = [] for i in xrange(numkwargs): value = stack.pop() key = stack.pop(never_literal=True) args.append("=".join((key, value))) for i in xrange(numargs): args.append(stack.pop()) args.reverse() funcname = stack.pop() stack.push("{0}({1})".format(funcname, ", ".join(args))) elif opname == "PRINT_ITEM": print_buffer.append(stack.pop()) elif opname == "PRINT_NEWLINE": block.put("print", ", ".join(print_buffer)) print_buffer = [] elif opname == "RETURN_VALUE": block.put("return", stack.pop()) elif opname == "COMPARE_OP": tos, tos1 = stack.pop(), stack.pop() compare = " ".join((tos1, arg, tos)) stack.push(compare) elif opname == "POP_JUMP_IF_FALSE": block = block.child() block.split(stack.pop()) elses.append(arg) elif opname == "POP_JUMP_IF_TRUE": block = block.child() block.split(stack.pop()) elses.append(arg) elif opname == "JUMP_ABSOLUTE": if arg < i: block = block.parent() else: drops.append(arg) elif opname == "JUMP_FORWARD": drops.append(arg + i) elif opname == "SETUP_LOOP": block = block.child(loop=True) elif opname == "POP_BLOCK": block = block.parent() else: raise NotImplementedError(opname, arg, stack) return main_block def _print(indentation, *args, **kwargs): argstring = " ".join([str(arg) for arg in args if str(arg)]) if kwargs.get("debug"): # Ignore debug messages without -d flag if len(sys.argv) > 1 and sys.argv[1] == "-d": print " " * 50 + "#", argstring return print " " * indentation + argstring class Stack(object): def __init__(self): self._items = [] def __iter__(self): while self._items: yield self.pop() def __repr__(self): s = reversed([repr(itm) if lit else itm for itm, lit in self._items]) return "Stack[" + ", ".join(s) + "]" def push(self, item, is_literal=False): self._items.append((item, is_literal)) def pop(self, never_literal=False): item, is_literal = self._items.pop() return repr(item) if is_literal and not never_literal else item class Block(object): def __init__(self, parent=None, loop=False): self._parent = parent self._loop = loop self._true_block = self._focus = [] self._false_block = [] self._split = None def __iter__(self): for item in self._render_lines(): yield item[1] def __repr__(self): return str([item[1] for item in self._render_lines()]) def _has_lines(self): return self._split or self._true_block or self._false_block def _render_subblock(self, block, lines, indent): for item in block: if isinstance(item, Block): lines += item._render_lines(indent) else: lines.append((indent, item)) def _render_lines(self, indent=0): lines = [] if self._split: lines.append((indent, (self._split,))) indent += TAB self._render_subblock(self._true_block, lines, indent) if self._false_block: lines.append((indent - TAB, ("else:",))) self._render_subblock(self._false_block, lines, indent) return lines def child(self, loop=False): if self._loop and not loop and not self._has_lines(): loop = True child = Block(parent=self, loop=loop) self._focus.append(child) return child def parent(self): if self._parent: return self._parent raise RuntimeError("Popping an orphaned block") def put(self, *code): self._focus.append(code) def split(self, test): if self._loop: self._split = "while " + test + ":" else: self._split = "if " + test + ":" def toggle(self): if self._focus is self._true_block: self._focus = self._false_block else: self._focus = self._true_block def display(self, indent=0): for indent, code in self._render_lines(indent): _print(indent, *code) if __name__ == "__main__": def f1(a): b = a + 10 / 7.0 d = long_func(a, b+9, c=42, d="43") print b, d ex_tuple = ("Hello", "world!", abcdef) ex_list = [1, 2, 3] * 3 + [4, 5, 6] ex_set = {99, 98, 97, 96, 95} ex_dict = {d: e, f: False, "testing": g} return ex_dict def f2(a, b, c): if cmp1: line1 if cmp2: line2 elif cmp3: line3 elif cmp4: line4 else: line5 line6 else: line7 line8 if cmp4: line9 if cmp5: if cmp6: if cmp7: if cmp8: if cmp9: line10 return line11 def f3(x, y): if cmp1: while cmp1: if cmp2: while cmp3: line1 else: while cmp4: line2 line3 line4 if cmp5: line5 return line6 prettify_function(f1) print prettify_function(f2) print prettify_function(f3)