diff --git a/HISTORY.md b/HISTORY.md index 654a22d7..280d46f0 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,4 +1,4 @@ -This project has history of over 17 years spanning back to Python 1.5 +This project has history of over 18 years spanning back to Python 1.5 There have been a number of people who have worked on this. I am awed by the amount of work, number of people who have contributed to this, diff --git a/uncompyle6/__init__.py b/uncompyle6/__init__.py index a029bd7d..f4d69849 100644 --- a/uncompyle6/__init__.py +++ b/uncompyle6/__init__.py @@ -55,7 +55,10 @@ from uncompyle6.main import decompile_file uncompyle_file = decompile_file # Conventience functions so you can say: -# from uncompyle6 import (deparse_code, deparse_code2str) +# from uncompyle6 import (code_deparse, deparse_code2str) -deparse_code = uncompyle6.semantics.pysource.deparse_code +code_deparse = uncompyle6.semantics.pysource.code_deparse deparse_code2str = uncompyle6.semantics.pysource.deparse_code2str + +# This is deprecated: +deparse_code = uncompyle6.semantics.pysource.deparse_code diff --git a/uncompyle6/main.py b/uncompyle6/main.py index f50d69a1..bdfabee1 100644 --- a/uncompyle6/main.py +++ b/uncompyle6/main.py @@ -23,8 +23,8 @@ from uncompyle6.parser import ParserError from uncompyle6.version import VERSION # from uncompyle6.linenumbers import line_number_mapping -from uncompyle6.semantics.pysource import deparse_code -from uncompyle6.semantics.fragments import deparse_code as deparse_code_fragments +from uncompyle6.semantics.pysource import code_deparse +from uncompyle6.semantics.fragments import code_deparse as code_deparse_fragments from uncompyle6.semantics.linemap import deparse_code_with_map from xdis.load import load_module @@ -43,7 +43,7 @@ def _get_outstream(outfile): def decompile( bytecode_version, co, out=None, showasm=None, showast=False, timestamp=None, showgrammar=False, code_objects={}, - source_size=None, is_pypy=False, magic_int=None, + source_size=None, is_pypy=None, magic_int=None, mapstream=None, do_fragments=False): """ ingests and deparses a given code block 'co' @@ -89,6 +89,12 @@ def decompile( real_out.write('# Size of source mod 2**32: %d bytes\n' % source_size) + debug_opts = { + 'asm': showasm, + 'ast': showast, + 'grammar': showgrammar + } + try: if mapstream: if isinstance(mapstream, str): @@ -106,12 +112,12 @@ def decompile( mapstream.write("\n\n# %s\n" % linemap) else: if do_fragments: - deparse_fn = deparse_code_fragments + deparse_fn = code_deparse_fragments else: - deparse_fn = deparse_code - deparsed = deparse_fn(bytecode_version, co, out, showasm, showast, - showgrammar, code_objects=code_objects, - is_pypy=is_pypy) + deparse_fn = code_deparse + deparsed = deparse_fn(co, out, bytecode_version, + debug_opts = debug_opts, + is_pypy=is_pypy) pass return deparsed except pysource.SourceWalkerError, e: diff --git a/uncompyle6/semantics/consts.py b/uncompyle6/semantics/consts.py index 54d0549f..39ef3a3d 100644 --- a/uncompyle6/semantics/consts.py +++ b/uncompyle6/semantics/consts.py @@ -1,4 +1,17 @@ # Copyright (c) 2017, 2018 by Rocky Bernstein +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . """Constants and initial table values used in pysource.py and fragments.py""" import re, sys diff --git a/uncompyle6/semantics/fragments.py b/uncompyle6/semantics/fragments.py index 8c251586..cabc5f5b 100644 --- a/uncompyle6/semantics/fragments.py +++ b/uncompyle6/semantics/fragments.py @@ -1,4 +1,17 @@ # Copyright (c) 2015-2018 by Rocky Bernstein +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . """ Creates Python source code from an uncompyle6 parse tree, @@ -58,6 +71,7 @@ from uncompyle6 import parser from uncompyle6.scanner import Token, Code, get_scanner import uncompyle6.parser as python_parser from uncompyle6.semantics.check_ast import checker +from uncompyle6 import IS_PYPY from uncompyle6.show import ( maybe_show_asm, @@ -1716,9 +1730,29 @@ class FragmentsWalker(pysource.SourceWalker, object): pass +# +DEFAULT_DEBUG_OPTS = { + 'asm': False, + 'tree': False, + 'grammar': False +} + +# This interface is deprecated def deparse_code(version, co, out=StringIO(), showasm=False, showast=False, showgrammar=False, code_objects={}, compile_mode='exec', - is_pypy=False, walker=FragmentsWalker): + is_pypy=None, walker=FragmentsWalker): + debug_opts = { + 'asm': showasm, + 'ast': showast, + 'grammar': showgrammar + } + return code_deparse(co, out, version, debug_opts, code_objects, compile_mode, + is_pypy, walker) + +def code_deparse(co, out=StringIO(), version=None, is_pypy=None, + debug_opts=DEFAULT_DEBUG_OPTS, + code_objects={}, compile_mode='exec', + walker=FragmentsWalker): """ Convert the code object co into a python source fragment. @@ -1726,40 +1760,44 @@ def deparse_code(version, co, out=StringIO(), showasm=False, showast=False, example 2.6, 2.7, 3.2, 3.3, 3.4, 3.5 etc. :param co: The code object to parse. :param out: File like object to write the output to. - :param showasm: Flag which determines whether the ingestd code - is written to sys.stdout or not. (It is also to - pass a file like object, into which the asm will be - written). - :param showast: Flag which determines whether the constructed - parse 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 reduction rules - is written to sys.stdout or not. (It is also to - pass a file like object, into which the grammar - will be written). + :param debug_opts: A dictionary with keys + 'asm': value determines whether to show + mangled bytecode disdassembly + 'ast': value determines whether to show + 'grammar': boolean determining whether to show + grammar reduction rules. + If value is a file-like object, output that object's write method will + be used rather than sys.stdout :return: The deparsed source fragment. """ assert iscode(co) - # store final output stream for case of error + + if version is None: + version = float(sys.version[0:3]) + if is_pypy is None: + is_pypy = IS_PYPY + scanner = get_scanner(version, is_pypy=is_pypy) + show_asm = debug_opts.get('asm', None) tokens, customize = scanner.ingest(co, code_objects=code_objects, - show_asm=showasm) + show_asm=show_asm) tokens, customize = scanner.ingest(co) - maybe_show_asm(showasm, tokens) + maybe_show_asm(show_asm, tokens) debug_parser = dict(PARSER_DEFAULT_DEBUG) - if showgrammar: - debug_parser['reduce'] = showgrammar + show_grammar = debug_opts.get('grammar', None) + if show_grammar: + debug_parser['reduce'] = show_grammar debug_parser['errorstack'] = True - # Build Syntax Tree from tokenized and massaged disassembly. + # Build Syntax Tree from tokenized and massaged disassembly. # deparsed = pysource.FragmentsWalker(out, scanner, showast=showast) - deparsed = walker(version, scanner, showast=showast, + show_ast = debug_opts.get('ast', None) + deparsed = walker(version, scanner, showast=show_ast, debug_parser=debug_parser, compile_mode=compile_mode, is_pypy=is_pypy) @@ -1869,12 +1907,8 @@ def deparsed_find(tup, deparsed, code): # if __name__ == '__main__': -# from uncompyle6 import IS_PYPY # def deparse_test(co, is_pypy=IS_PYPY): -# from xdis.magics import sysinfo2float -# float_version = sysinfo2float() -# deparsed = deparse_code(float_version, co, showasm=False, showast=False, -# showgrammar=False, is_pypy=IS_PYPY) +# deparsed = code_deparse(co, is_pypy=IS_PYPY) # print("deparsed source") # print(deparsed.text, "\n") # print('------------------------') diff --git a/uncompyle6/semantics/pysource.py b/uncompyle6/semantics/pysource.py index 04f04633..6498981d 100644 --- a/uncompyle6/semantics/pysource.py +++ b/uncompyle6/semantics/pysource.py @@ -2,6 +2,19 @@ # Copyright (c) 2005 by Dan Pascu # Copyright (c) 2000-2002 by hartmut Goebel # Copyright (c) 1999 John Aycock +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . """Creates Python source code from an uncompyle6 parse tree. @@ -1521,7 +1534,7 @@ class SourceWalker(GenericASTTraversal, object): def comprehension_walk3(self, node, iter_index, code_index=-5): """ List comprehensions the way they are done in Python3. - They're more other comprehensions, e.g. set comprehensions + They are other comprehensions, e.g. set comprehensions See if we can combine code. """ p = self.prec @@ -1534,8 +1547,13 @@ class SourceWalker(GenericASTTraversal, object): ast = self.build_ast(code._tokens, code._customize) self.customize(code._customize) - # skip over stmt return ret_expr - ast = ast[0][0][0] + # skip over: sstmt, stmt, return, ret_expr + # and other singleton derivations + while (len(ast) == 1 + or (ast in ('sstmt', 'return') + and ast[-1] in ('RETURN_LAST', 'RETURN_VALUE'))): + ast = ast[0] + store = None if ast in ['setcomp_func', 'dictcomp_func']: for k in ast: @@ -1547,7 +1565,6 @@ class SourceWalker(GenericASTTraversal, object): pass pass else: - ast = ast[0][0] n = ast[iter_index] assert n == 'list_iter', n @@ -2615,9 +2632,27 @@ class SourceWalker(GenericASTTraversal, object): return MAP.get(node, MAP_DIRECT) +# +DEFAULT_DEBUG_OPTS = { + 'asm': False, + 'tree': False, + 'grammar': False +} + +# This interface is deprecated. Use simpler code_deparse. def deparse_code(version, co, out=sys.stdout, showasm=None, showast=False, showgrammar=False, code_objects={}, compile_mode='exec', is_pypy=False, walker=SourceWalker): + debug_opts = { + 'asm': showasm, + 'ast': showast, + 'grammar': showgrammar + } + return code_deparse(co, out, version, debug_opts, code_objects, compile_mode, + is_pypy, walker) + +def code_deparse(co, out=sys.stdout, version=None, debug_opts=DEFAULT_DEBUG_OPTS, + code_objects={}, compile_mode='exec', is_pypy=False, walker=SourceWalker): """ ingests and deparses a given code block 'co'. If version is None, we will use the current Python interpreter version. @@ -2632,16 +2667,16 @@ def deparse_code(version, co, out=sys.stdout, showasm=None, showast=False, scanner = get_scanner(version, is_pypy=is_pypy) tokens, customize = scanner.ingest(co, code_objects=code_objects, - show_asm=showasm) + show_asm=debug_opts['asm']) debug_parser = dict(PARSER_DEFAULT_DEBUG) - if showgrammar: - debug_parser['reduce'] = showgrammar + if debug_opts.get('grammar', None): + debug_parser['reduce'] = debug_opts['grammar'] debug_parser['errorstack'] = 'full' # Build Syntax Tree from disassembly. linestarts = dict(scanner.opc.findlinestarts(co)) - deparsed = walker(version, out, scanner, showast=showast, + deparsed = walker(version, out, scanner, showast=debug_opts['ast'], debug_parser=debug_parser, compile_mode=compile_mode, is_pypy=is_pypy, linestarts=linestarts) @@ -2690,12 +2725,6 @@ 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',