diff --git a/uncompyle6/__init__.py b/uncompyle6/__init__.py index 4b6baad0..a029bd7d 100644 --- a/uncompyle6/__init__.py +++ b/uncompyle6/__init__.py @@ -1,7 +1,7 @@ """ - Copyright (c) 1999 John Aycock + Copyright (c) 2015, 2018 by Rocky Bernstein Copyright (c) 2000 by hartmut Goebel - Copyright (c) 2015 by Rocky Bernstein + Copyright (c) 1999 John Aycock Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -55,6 +55,7 @@ from uncompyle6.main import decompile_file uncompyle_file = decompile_file # Conventience functions so you can say: -# from uncompyle6 import deparse_code +# from uncompyle6 import (deparse_code, deparse_code2str) deparse_code = uncompyle6.semantics.pysource.deparse_code +deparse_code2str = uncompyle6.semantics.pysource.deparse_code2str diff --git a/uncompyle6/parsers/parse3.py b/uncompyle6/parsers/parse3.py index 646ee597..3c407fc7 100644 --- a/uncompyle6/parsers/parse3.py +++ b/uncompyle6/parsers/parse3.py @@ -135,7 +135,7 @@ class Python3Parser(PythonParser): iflaststmtl ::= testexpr c_stmts_opt JUMP_BACK COME_FROM_LOOP iflaststmtl ::= testexpr c_stmts_opt JUMP_BACK POP_BLOCK - # These are used to keep AST indices the same + # These are used to keep grammar tree indices the same jump_forward_else ::= JUMP_FORWARD ELSE jump_absolute_else ::= JUMP_ABSOLUTE ELSE diff --git a/uncompyle6/semantics/aligner.py b/uncompyle6/semantics/aligner.py index 14e1462e..628f9f6c 100644 --- a/uncompyle6/semantics/aligner.py +++ b/uncompyle6/semantics/aligner.py @@ -100,7 +100,7 @@ def align_deparse_code(version, co, out=sys.stderr, showasm=False, showast=False debug_parser['reduce'] = showgrammar debug_parser['errorstack'] = True - # Build AST from disassembly. + # Build a grammar tree from tokenized and massaged disassembly. deparsed = AligningWalker(version, scanner, out, showast=showast, debug_parser=debug_parser, compile_mode=compile_mode, is_pypy = is_pypy) @@ -125,7 +125,7 @@ def align_deparse_code(version, co, out=sys.stderr, showasm=False, showast=False except: pass - # What we've been waiting for: Generate source from AST! + # What we've been waiting for: Generate Python source from the grammar tree! deparsed.gen_source(deparsed.ast, co.co_name, customize) for g in deparsed.mod_globs: diff --git a/uncompyle6/semantics/check_ast.py b/uncompyle6/semantics/check_ast.py index d8f07deb..39d5c701 100644 --- a/uncompyle6/semantics/check_ast.py +++ b/uncompyle6/semantics/check_ast.py @@ -1,5 +1,5 @@ """ -Python AST grammar checker. +Python grammar tree checker. Our rules sometimes give erroneous results. Until we have perfect rules, This checker will catch mistakes in decompilation we've made. diff --git a/uncompyle6/semantics/consts.py b/uncompyle6/semantics/consts.py index d3f48c6c..da8ff320 100644 --- a/uncompyle6/semantics/consts.py +++ b/uncompyle6/semantics/consts.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017 by Rocky Bernstein +# Copyright (c) 2017, 2018 by Rocky Bernstein """Constants and initial table values used in pysource.py and fragments.py""" import re, sys @@ -16,8 +16,8 @@ else: LINE_LENGTH = 80 -# Some ASTs used for comparing code fragments (like 'return None' at -# the end of functions). +# Some grammar trees created below are used for comparing code +# fragments (like 'return None' at the end of functions). RETURN_LOCALS = AST('return', [ AST('ret_expr', [AST('expr', [ Token('LOAD_LOCALS') ])]), diff --git a/uncompyle6/semantics/fragments.py b/uncompyle6/semantics/fragments.py index b86b0ff8..6b406267 100644 --- a/uncompyle6/semantics/fragments.py +++ b/uncompyle6/semantics/fragments.py @@ -1,7 +1,7 @@ # Copyright (c) 2015-2018 by Rocky Bernstein """ -Creates Python source code from an uncompyle6 abstract syntax tree, +Creates Python source code from an uncompyle6 grammar tree, and indexes fragments which can be accessed by instruction offset address. @@ -63,7 +63,7 @@ from uncompyle6.semantics.check_ast import checker from uncompyle6.show import ( maybe_show_asm, - maybe_show_ast, + maybe_show_tree, ) from uncompyle6.parsers.astnode import AST @@ -1050,7 +1050,7 @@ class FragmentsWalker(pysource.SourceWalker, object): n_classdefdeco2 = n_classdef def gen_source(self, ast, name, customize, is_lambda=False, returnNone=False): - """convert AST to Python source code""" + """convert grammar tree to Python source code""" rn = self.return_none self.return_none = returnNone @@ -1088,7 +1088,7 @@ class FragmentsWalker(pysource.SourceWalker, object): self.p.insts = p_insts except (parser.ParserError, AssertionError) as e: raise ParserError(e, tokens) - maybe_show_ast(self.showast, ast) + maybe_show_tree(self.showast, ast) return ast # The bytecode for the end of the main routine has a @@ -1116,7 +1116,7 @@ class FragmentsWalker(pysource.SourceWalker, object): if len(tokens) == 0: return PASS - # Build AST from disassembly. + # Build grammar tree from tokenized and massaged disassembly. try: # FIXME: have p.insts update in a better way # modularity is broken here @@ -1127,7 +1127,7 @@ class FragmentsWalker(pysource.SourceWalker, object): except (parser.ParserError, AssertionError) as e: raise ParserError(e, tokens) - maybe_show_ast(self.showast, ast) + maybe_show_tree(self.showast, ast) checker(ast, False, self.ast_errors) @@ -1719,12 +1719,12 @@ def deparse_code(version, co, out=StringIO(), showasm=False, showast=False, pass a file like object, into which the asm will be written). :param showast: Flag which determines whether the constructed - abstract syntax tree is written to sys.stdout or + grammar tree is written to sys.stdout or not. (It is also to pass a file like object, into which the ast will be written). - :param showgrammar: Flag which determines whether the grammar + :param showgrammar: Flag which determines whether the grammar reduction rules is written to sys.stdout or not. (It is also to - pass a file like object, into which the grammer + pass a file like object, into which the grammar will be written). :return: The deparsed source fragment. @@ -1745,7 +1745,7 @@ def deparse_code(version, co, out=StringIO(), showasm=False, showast=False, debug_parser['reduce'] = showgrammar debug_parser['errorstack'] = True - # Build AST from disassembly. + # Build Syntax Tree from tokenized and massaged disassembly. # deparsed = pysource.FragmentsWalker(out, scanner, showast=showast) deparsed = walker(version, scanner, showast=showast, debug_parser=debug_parser, compile_mode=compile_mode, diff --git a/uncompyle6/semantics/helper.py b/uncompyle6/semantics/helper.py index a39bed8f..f53acd68 100644 --- a/uncompyle6/semantics/helper.py +++ b/uncompyle6/semantics/helper.py @@ -16,7 +16,7 @@ read_global_ops = frozenset(('STORE_GLOBAL', 'DELETE_GLOBAL')) # FIXME: this and find_globals could be paramaterized with one of the # above global ops def find_all_globals(node, globs): - """Search AST node to find variable names that are global.""" + """Search Syntax Tree node to find variable names that are global.""" for n in node: if isinstance(n, AST): globs = find_all_globals(n, globs) @@ -25,7 +25,7 @@ def find_all_globals(node, globs): return globs def find_globals(node, globs): - """search AST node to find variable names that need a 'global' added.""" + """search grammar-tree node to find variable names that need a 'global' added.""" for n in node: if isinstance(n, AST): globs = find_globals(n, globs) diff --git a/uncompyle6/semantics/make_function.py b/uncompyle6/semantics/make_function.py index 2fb5d3ce..d53d73fb 100644 --- a/uncompyle6/semantics/make_function.py +++ b/uncompyle6/semantics/make_function.py @@ -18,7 +18,7 @@ if PYTHON3: else: from itertools import izip_longest as zip_longest -from uncompyle6.show import maybe_show_ast_param_default +from uncompyle6.show import maybe_show_tree_param_default # FIXME: DRY the below code... @@ -35,7 +35,7 @@ def make_function3_annotate(self, node, is_lambda, nested=1, """ if default: value = self.traverse(default, indent='') - maybe_show_ast_param_default(self.showast, name, value) + maybe_show_tree_param_default(self.showast, name, value) result = '%s=%s' % (name, value) if result[-2:] == '= ': # default was 'LOAD_CONST None' result += 'None' @@ -288,7 +288,7 @@ def make_function2(self, node, is_lambda, nested=1, codeNode=None): if default: value = self.traverse(default, indent='') - maybe_show_ast_param_default(self.showast, name, value) + maybe_show_tree_param_default(self.showast, name, value) result = '%s=%s' % (name, value) if result[-2:] == '= ': # default was 'LOAD_CONST None' result += 'None' @@ -459,7 +459,7 @@ def make_function3(self, node, is_lambda, nested=1, codeNode=None): value = default else: value = self.traverse(default, indent='') - maybe_show_ast_param_default(self.showast, name, value) + maybe_show_tree_param_default(self.showast, name, value) result = '%s=%s' % (name, value) if result[-2:] == '= ': # default was 'LOAD_CONST None' result += 'None' diff --git a/uncompyle6/semantics/pysource.py b/uncompyle6/semantics/pysource.py index c27c16b7..1c6021ff 100644 --- a/uncompyle6/semantics/pysource.py +++ b/uncompyle6/semantics/pysource.py @@ -3,7 +3,7 @@ # Copyright (c) 2000-2002 by hartmut Goebel # Copyright (c) 1999 John Aycock -"""Creates Python source code from an uncompyle6 abstract syntax tree. +"""Creates Python source code from an uncompyle6 grammar tree. The terminal symbols are CPython bytecode instructions. (See the python documentation under module "dis" for a list of instructions @@ -47,7 +47,7 @@ Python. # In the diagram below, N is a nonterminal name, and K also a nonterminal # name but the one used as a key in the table. # we show where those are with respect to each other in the -# AST tree for N. +# grammar tree for N. # # # N&K N N @@ -139,7 +139,7 @@ from uncompyle6.semantics.consts import ( from uncompyle6.show import ( - maybe_show_ast, + maybe_show_tree, ) if PYTHON3: @@ -2565,7 +2565,7 @@ class SourceWalker(GenericASTTraversal, object): self.p.insts = p_insts except (python_parser.ParserError, AssertionError) as e: raise ParserError(e, tokens) - maybe_show_ast(self.showast, ast) + maybe_show_tree(self.showast, ast) return ast # The bytecode for the end of the main routine has a @@ -2587,7 +2587,7 @@ class SourceWalker(GenericASTTraversal, object): if len(tokens) == 0: return PASS - # Build AST from disassembly. + # Build a grammar tree from a tokenized and massaged disassembly. try: # FIXME: have p.insts update in a better way # modularity is broken here @@ -2598,7 +2598,7 @@ class SourceWalker(GenericASTTraversal, object): except (python_parser.ParserError, AssertionError) as e: raise ParserError(e, tokens) - maybe_show_ast(self.showast, ast) + maybe_show_tree(self.showast, ast) checker(ast, False, self.ast_errors) @@ -2633,7 +2633,7 @@ def deparse_code(version, co, out=sys.stdout, showasm=None, showast=False, debug_parser['reduce'] = showgrammar debug_parser['errorstack'] = 'full' - # Build AST from disassembly. + # Build Syntax Tree from disassembly. linestarts = dict(scanner.opc.findlinestarts(co)) deparsed = walker(version, out, scanner, showast=showast, debug_parser=debug_parser, compile_mode=compile_mode, @@ -2667,7 +2667,7 @@ def deparse_code(version, co, out=sys.stdout, showasm=None, showast=False, deparsed.FUTURE_UNICODE_LITERALS = ( COMPILER_FLAG_BIT['FUTURE_UNICODE_LITERALS'] & co.co_flags != 0) - # What we've been waiting for: Generate source from AST! + # What we've been waiting for: Generate source from Syntax Tree! deparsed.gen_source(deparsed.ast, co.co_name, customize) for g in deparsed.mod_globs: @@ -2684,13 +2684,30 @@ def deparse_code(version, co, out=sys.stdout, showasm=None, showast=False, raise SourceWalkerError("Deparsing stopped due to parse error") return deparsed +# +DEFAULT_DEBUG_OPTS = { + 'asm': False, + 'tree': False, + 'grammar': False +} +def deparse_code2str(code, out=sys.stdout, version=None, + debug_opts=DEFAULT_DEBUG_OPTS, + code_objects={}, compile_mode='exec', + is_pypy=False, walker=SourceWalker): + """Return the deparsed text for a Python code object. `out` is where any intermediate + output for assembly or tree output will be sent. + """ + return deparse_code(version, code, out, showasm=debug_opts.get('asm', None), + showast=debug_opts.get('tree', None), + showgrammar=debug_opts.get('grammar', None), code_objects=code_objects, + compile_mode=compile_mode, is_pypy=is_pypy, walker=walker).text + if __name__ == '__main__': def deparse_test(co): "This is a docstring" - sys_version = float(sys.version[0:3]) - deparsed = deparse_code(sys_version, co, showasm='after', showast=True) - # deparsed = deparse_code(sys_version, co, showasm=None, showast=False, - # showgrammar=True) - print(deparsed.text) + s = deparse_code2str(co, debug_opts={'asm':'after', 'tree':True}) + # s = deparse_code2str(co, showasm=None, showast=False, + # showgrammar=True) + print(s) return deparse_test(deparse_test.__code__) diff --git a/uncompyle6/show.py b/uncompyle6/show.py index 8a886f58..3c1b8ac5 100644 --- a/uncompyle6/show.py +++ b/uncompyle6/show.py @@ -18,37 +18,37 @@ def maybe_show_asm(showasm, tokens): stream.write('\n') -def maybe_show_ast(showast, ast): +def maybe_show_tree(show_tree, ast): """ Show the ast based on the showast flag (or file object), writing to the appropriate stream depending on the type of the flag. - :param showasm: Flag which determines whether the abstract syntax tree is - written to sys.stdout or not. (It is also to pass a file - like object, into which the ast will be written). + :param show_tree: Flag which determines whether the grammar tree is + written to sys.stdout or not. (It is also to pass a file + like object, into which the ast will be written). :param ast: The ast to show. """ - if showast: - stream = showast if hasattr(showast, 'write') else sys.stdout + if show_tree: + stream = show_tree if hasattr(show_tree, 'write') else sys.stdout stream.write(str(ast)) stream.write('\n') -def maybe_show_ast_param_default(showast, name, default): +def maybe_show_tree_param_default(show_tree, name, default): """ - Show a function parameter with default for an ast based on the showast flag + Show a function parameter with default for an grammar-tree based on the show_tree flag (or file object), writing to the appropriate stream depending on the type of the flag. - :param showasm: Flag which determines whether the function parameter with - default is written to sys.stdout or not. (It is also to - pass a file like object, into which the ast will be - written). + :param show_tree: Flag which determines whether the function parameter with + default is written to sys.stdout or not. (It is also to + pass a file like object, into which the ast will be + written). :param name: The function parameter name. :param default: The function parameter default. """ - if showast: - stream = showast if hasattr(showast, 'write') else sys.stdout + if show_tree: + stream = show_tree if hasattr(show_tree, 'write') else sys.stdout stream.write('\n') stream.write('--' + name) stream.write('\n')