From 962c5031338c259ab57c0bc94844de5084e5f7a4 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 5 Jan 2020 18:57:04 -0500 Subject: [PATCH 01/11] Small typo --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 1023400e..e184eb16 100644 --- a/README.rst +++ b/README.rst @@ -232,7 +232,7 @@ See Also * https://github.com/rocky/python-xdis : Cross Python version disassembler * https://github.com/rocky/python-xasm : Cross Python version assembler * https://github.com/rocky/python-uncompyle6/wiki : Wiki Documents which describe the code and aspects of it in more detail -* https://github.com/zrax/pycdc : The README for this C++ code syas it aims to support all versions of Python. It is best for Python versions around 2.7 and 3.3 when the code was initially developed. Accuracy for current versions of Python3 and early versions of Python is lacking. Without major effort, it is unlikely it can be made to support current Python 3. See its `issue tracker `_ for details. Currently lightly maintained. +* https://github.com/zrax/pycdc : The README for this C++ code says it aims to support all versions of Python. It is best for Python versions around 2.7 and 3.3 when the code was initially developed. Accuracy for current versions of Python3 and early versions of Python is lacking. Without major effort, it is unlikely it can be made to support current Python 3. See its `issue tracker `_ for details. Currently lightly maintained. .. _trepan: https://pypi.python.org/pypi/trepan2g From 3c5ad58e259eccb2f59bb3ea29c3a4f77edbf09b Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 5 Jan 2020 21:07:24 -0500 Subject: [PATCH 02/11] Add 2.x if vs if/else reduction rule check --- test/stdlib/runtests.sh | 2 +- uncompyle6/parsers/parse2.py | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/test/stdlib/runtests.sh b/test/stdlib/runtests.sh index 5a2b8ae0..3a9fd1be 100755 --- a/test/stdlib/runtests.sh +++ b/test/stdlib/runtests.sh @@ -32,11 +32,11 @@ typeset -A SKIP_TESTS case $PYVERSION in 2.4) SKIP_TESTS=( + [test_decimal.py]=1 # [test_dis.py]=1 # We change line numbers - duh! [test_grp.py]=1 # Long test - might work Control flow? [test_pep247.py]=1 # Long test - might work? Control flow? [test_pwd.py]=1 # Long test - might work? Control flow? - [test_pyclbr.py]=1 # Investigate [test_socketserver.py]=1 # -- test takes too long to run: 40 seconds [test_threading.py]=1 # test takes too long to run: 11 seconds [test_thread.py]=1 # test takes too long to run: 36 seconds diff --git a/uncompyle6/parsers/parse2.py b/uncompyle6/parsers/parse2.py index 3d471dc2..f6dce338 100644 --- a/uncompyle6/parsers/parse2.py +++ b/uncompyle6/parsers/parse2.py @@ -643,6 +643,7 @@ class Python2Parser(PythonParser): self.check_reduce["tryelsestmtl"] = "AST" self.check_reduce["aug_assign2"] = "AST" self.check_reduce["or"] = "AST" + self.check_reduce["ifstmt"] = "tokens" # self.check_reduce['_stmts'] = 'AST' # Dead code testing... @@ -667,6 +668,14 @@ class Python2Parser(PythonParser): jmp_false = ast[1] jump_target = jmp_false[0].attr return jump_target > tokens[last].off2int() + elif rule == ("ifstmt", ("testexpr", "_ifstmts_jump")): + for i in range(last-1, last-4, -1): + t = tokens[i] + if t == "JUMP_FORWARD": + return t.attr > tokens[min(last, len(tokens)-1)].off2int() + elif t not in ("POP_TOP", "COME_FROM"): + break + pass elif lhs in ("raise_stmt1",): # We will assume 'LOAD_ASSERT' will be handled by an assert grammar rule return tokens[first] == "LOAD_ASSERT" and (last >= len(tokens)) From 2e0c0f8245d741430c15106d8845f07c1b556c16 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 5 Jan 2020 21:17:28 -0500 Subject: [PATCH 03/11] What's up with test_generators.py --- test/stdlib/runtests.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/test/stdlib/runtests.sh b/test/stdlib/runtests.sh index 3a9fd1be..e0c4f892 100755 --- a/test/stdlib/runtests.sh +++ b/test/stdlib/runtests.sh @@ -34,6 +34,7 @@ case $PYVERSION in SKIP_TESTS=( [test_decimal.py]=1 # [test_dis.py]=1 # We change line numbers - duh! + [test_generators.py]=1 # Investigate - botched if/elif control flow badly [test_grp.py]=1 # Long test - might work Control flow? [test_pep247.py]=1 # Long test - might work? Control flow? [test_pwd.py]=1 # Long test - might work? Control flow? From 444bab760bac3df7c1aca9171606052f74fdd178 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 5 Jan 2020 21:25:34 -0500 Subject: [PATCH 04/11] Update runtests.sh exception list --- test/stdlib/runtests.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/stdlib/runtests.sh b/test/stdlib/runtests.sh index e0c4f892..63756456 100755 --- a/test/stdlib/runtests.sh +++ b/test/stdlib/runtests.sh @@ -35,6 +35,7 @@ case $PYVERSION in [test_decimal.py]=1 # [test_dis.py]=1 # We change line numbers - duh! [test_generators.py]=1 # Investigate - botched if/elif control flow badly + [test_grammar.py]=1 # Too many stmts. Handle large stmts [test_grp.py]=1 # Long test - might work Control flow? [test_pep247.py]=1 # Long test - might work? Control flow? [test_pwd.py]=1 # Long test - might work? Control flow? @@ -47,8 +48,7 @@ case $PYVERSION in ;; 2.5) SKIP_TESTS=( - [test_contextlib.py]=1 # Syntax error - look at - [test_dis.py]=1 # We change line numbers - duh! + [test_dis.py]=1 # We change line numbers - duh! [test_grammar.py]=1 # Too many stmts. Handle large stmts [test_grp.py]=1 # Long test - might work Control flow? [test_pdb.py]=1 # Line-number specific From 67c047df75736141b396a9688e64fc060d7c696a Mon Sep 17 00:00:00 2001 From: rocky Date: Mon, 6 Jan 2020 04:43:52 -0500 Subject: [PATCH 05/11] Python 2.4- doesn't have condition expresions --- uncompyle6/parsers/parse21.py | 5 +---- uncompyle6/parsers/parse24.py | 9 ++++++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/uncompyle6/parsers/parse21.py b/uncompyle6/parsers/parse21.py index 34c5c0d9..07bf5ac7 100644 --- a/uncompyle6/parsers/parse21.py +++ b/uncompyle6/parsers/parse21.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016-2017 Rocky Bernstein +# Copyright (c) 2016-2017, 2020 Rocky Bernstein # Copyright (c) 2000-2002 by hartmut Goebel from spark_parser import DEFAULT_DEBUG as PARSER_DEFAULT_DEBUG @@ -19,9 +19,6 @@ class Python21Parser(Python22Parser): for ::= SETUP_LOOP expr for_iter store l_stmts_opt _jump_back POP_BLOCK COME_FROM - - expr ::= conditional - conditional ::= expr jmp_false expr JUMP_ABSOLUTE expr """ def p_import21(self, args): diff --git a/uncompyle6/parsers/parse24.py b/uncompyle6/parsers/parse24.py index 8db81f67..bc3e3625 100644 --- a/uncompyle6/parsers/parse24.py +++ b/uncompyle6/parsers/parse24.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016-2018 Rocky Bernstein +# Copyright (c) 2016-2018, 2020 Rocky Bernstein """ spark grammar differences over Python2.5 for Python 2.4. """ @@ -56,6 +56,12 @@ class Python24Parser(Python25Parser): kv2 ::= DUP_TOP expr expr ROT_THREE STORE_SUBSCR ''' + def remove_rules_24(self): + self.remove_rules(""" + expr ::= conditional + """) + + def customize_grammar_rules(self, tokens, customize): self.remove_rules(""" gen_comp_body ::= expr YIELD_VALUE POP_TOP @@ -72,6 +78,7 @@ class Python24Parser(Python25Parser): stmt ::= withasstmt """) super(Python24Parser, self).customize_grammar_rules(tokens, customize) + self.remove_rules_24() if self.version == 2.4: self.check_reduce['nop_stmt'] = 'tokens' From 63a88b8eea4cb57b34b10204c9b9cfffbf02522e Mon Sep 17 00:00:00 2001 From: rocky Date: Mon, 6 Jan 2020 10:54:14 -0500 Subject: [PATCH 06/11] Some Python 3.x lambda params are in reverse order --- test/stdlib/runtests.sh | 11 ++++------- uncompyle6/semantics/make_function3.py | 5 ++--- uncompyle6/semantics/make_function36.py | 6 ++---- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/test/stdlib/runtests.sh b/test/stdlib/runtests.sh index 63756456..7e5395be 100755 --- a/test/stdlib/runtests.sh +++ b/test/stdlib/runtests.sh @@ -232,9 +232,8 @@ case $PYVERSION in 3.5) SKIP_TESTS=( [test_ast.py]=1 # line 379, in test_literal_eval self.assertEqual(ast.literal_eval('b"hi"'), 'hi') - [test_atexit.py]=1 # + [test_atexit.py]=1 [test_builtin.py]=1 # - [test_compare.py]=1 [test_dis.py]=1 # We change line numbers - duh! ) if (( batch )) ; then @@ -247,11 +246,9 @@ case $PYVERSION in 3.6) SKIP_TESTS=( - [test_ast.py]=1 # [test_atexit.py]=1 # [test_bdb.py]=1 # - [test_builtin.py]=1 # - [test_compare.py]=1 + [test_builtin.py]=1 # Fails on its own [test_compile.py]=1 [test_contains.py]=1 # Code "while False: yield None" is optimized away in compilation [test_contextlib_async.py]=1 # Investigate @@ -263,7 +260,7 @@ case $PYVERSION in ;; 3.7) SKIP_TESTS=( - [test_ast.py]=1 # + [test_ast.py]=1 # test assertion error [test_atexit.py]=1 # [test_baseexception.py]=1 # [test_bdb.py]=1 # @@ -271,7 +268,7 @@ case $PYVERSION in [test_builtin.py]=1 # parser error [test_cmdline.py]=1 # Interactive? [test_collections.py]=1 # Fixed I think in decompyle3 - pull from there - [test_compare.py]=1 + [test_compare.py]=1 # test assert fail - investigate [test_compile.py]=1 [test_configparser.py]=1 [test_contains.py]=1 # Code "while False: yield None" is optimized away in compilation diff --git a/uncompyle6/semantics/make_function3.py b/uncompyle6/semantics/make_function3.py index 02b64f69..91eae46c 100644 --- a/uncompyle6/semantics/make_function3.py +++ b/uncompyle6/semantics/make_function3.py @@ -482,9 +482,8 @@ def make_function3(self, node, is_lambda, nested=1, code_node=None): kwargs = list(scanner_code.co_varnames[argc : argc + kwonlyargcount]) # defaults are for last n parameters when not in a lambda, thus reverse - if not is_lambda: - paramnames.reverse() - defparams.reverse() + paramnames.reverse() + defparams.reverse() try: ast = self.build_ast( diff --git a/uncompyle6/semantics/make_function36.py b/uncompyle6/semantics/make_function36.py index 97d3baa4..142d7601 100644 --- a/uncompyle6/semantics/make_function36.py +++ b/uncompyle6/semantics/make_function36.py @@ -201,10 +201,8 @@ def make_function36(self, node, is_lambda, nested=1, code_node=None): paramnames = list(scanner_code.co_varnames[:argc]) kwargs = list(scanner_code.co_varnames[argc : argc + kwonlyargcount]) - # defaults are for last n parameters when not in a lambda, thus reverse - if not is_lambda: - paramnames.reverse() - defparams.reverse() + paramnames.reverse() + defparams.reverse() try: ast = self.build_ast( From 078cca335accecf1e9d8c37d145b56dfaaea4b39 Mon Sep 17 00:00:00 2001 From: rocky Date: Mon, 6 Jan 2020 14:57:11 -0500 Subject: [PATCH 07/11] Seomtiems we need to add "yield"... in order to get the generator bit flag set, such as in 3.x where the yield is optimized away. --- test/stdlib/runtests.sh | 2 -- uncompyle6/semantics/make_function2.py | 22 +++++++++++++++++++--- uncompyle6/semantics/make_function3.py | 20 ++++++++++++++++++-- uncompyle6/semantics/make_function36.py | 21 ++++++++++++++++++--- 4 files changed, 55 insertions(+), 10 deletions(-) diff --git a/test/stdlib/runtests.sh b/test/stdlib/runtests.sh index 7e5395be..dcfc9b78 100755 --- a/test/stdlib/runtests.sh +++ b/test/stdlib/runtests.sh @@ -271,7 +271,6 @@ case $PYVERSION in [test_compare.py]=1 # test assert fail - investigate [test_compile.py]=1 [test_configparser.py]=1 - [test_contains.py]=1 # Code "while False: yield None" is optimized away in compilation [test_contextlib_async.py]=1 # Investigate [test_context.py]=1 [test_coroutines.py]=1 # Parse error @@ -293,7 +292,6 @@ case $PYVERSION in ;; 3.8) SKIP_TESTS=( - [test_contains.py]=1 # Code "while False: yield None" is optimized away in compilation [test_collections.py]=1 # Investigate [test_decorators.py]=1 # Control flow wrt "if elif" [test_exceptions.py]=1 # parse error diff --git a/uncompyle6/semantics/make_function2.py b/uncompyle6/semantics/make_function2.py index 10fd0e87..0e53ff45 100644 --- a/uncompyle6/semantics/make_function2.py +++ b/uncompyle6/semantics/make_function2.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015-2019 by Rocky Bernstein +# Copyright (c) 2015-2020 by Rocky Bernstein # Copyright (c) 2000-2002 by hartmut Goebel # # This program is free software: you can redistribute it and/or modify @@ -18,6 +18,7 @@ All the crazy things we have to do to handle Python functions in Python before 3 The saga of changes continues in 3.0 and above and in other files. """ from xdis.code import iscode, code_has_star_arg, code_has_star_star_arg +from xdis.util import CO_GENERATOR from uncompyle6.scanner import Code from uncompyle6.parsers.treenode import SyntaxTree from uncompyle6 import PYTHON3 @@ -182,7 +183,6 @@ def make_function2(self, node, is_lambda, nested=1, code_node=None): # docstring exists, dump it print_docstring(self, indent, code.co_consts[0]) - code._tokens = None # save memory if not is_lambda: assert ast == "stmts" @@ -203,5 +203,21 @@ def make_function2(self, node, is_lambda, nested=1, code_node=None): self.gen_source( ast, code.co_name, code._customize, is_lambda=is_lambda, returnNone=rn ) - code._tokens = None + + # In obscure cases, a function may be a generator but the "yield" + # was optimized away. Here, we need to put in unreachable code to + # add in "yield" just so that the compiler will mark + # the GENERATOR bit of the function. See for example + # Python 3.x's test_generator.py test program. + if code.co_flags & CO_GENERATOR: + need_bogus_yield = True + for token in scanner_code._tokens: + if token == "YIELD_VALUE": + need_bogus_yield = False + break + pass + if need_bogus_yield: + self.template_engine(("%|if False:\n%+%|yield None%-",), node) + + code._tokens = None # save memory code._customize = None # save memory diff --git a/uncompyle6/semantics/make_function3.py b/uncompyle6/semantics/make_function3.py index 91eae46c..600681c1 100644 --- a/uncompyle6/semantics/make_function3.py +++ b/uncompyle6/semantics/make_function3.py @@ -17,6 +17,7 @@ All the crazy things we have to do to handle Python functions in 3.0-3.5 or so. The saga of changes before and after is in other files. """ from xdis.code import iscode, code_has_star_arg, code_has_star_star_arg +from xdis.util import CO_GENERATOR from uncompyle6.scanner import Code from uncompyle6.parsers.treenode import SyntaxTree from uncompyle6 import PYTHON3 @@ -645,7 +646,6 @@ def make_function3(self, node, is_lambda, nested=1, code_node=None): # docstring exists, dump it print_docstring(self, self.indent, code.co_consts[0]) - scanner_code._tokens = None # save memory assert ast == "stmts" all_globals = find_all_globals(ast, set()) @@ -665,5 +665,21 @@ def make_function3(self, node, is_lambda, nested=1, code_node=None): self.gen_source( ast, code.co_name, scanner_code._customize, is_lambda=is_lambda, returnNone=rn ) - scanner_code._tokens = None + + # In obscure cases, a function may be a generator but the "yield" + # was optimized away. Here, we need to put in unreachable code to + # add in "yield" just so that the compiler will mark + # the GENERATOR bit of the function. See for example + # Python 3.x's test_generator.py test program. + if code.co_flags & CO_GENERATOR: + need_bogus_yield = True + for token in scanner_code._tokens: + if token in ("YIELD_VALUE", "YIELD_FROM"): + need_bogus_yield = False + break + pass + if need_bogus_yield: + self.template_engine(("%|if False:\n%+%|yield None%-",), node) + + scanner_code._tokens = None # save memory scanner_code._customize = None # save memory diff --git a/uncompyle6/semantics/make_function36.py b/uncompyle6/semantics/make_function36.py index 142d7601..b0abfe43 100644 --- a/uncompyle6/semantics/make_function36.py +++ b/uncompyle6/semantics/make_function36.py @@ -1,4 +1,4 @@ -# Copyright (c) 2019 by Rocky Bernstein +# Copyright (c) 2019-2020 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 @@ -17,6 +17,7 @@ All the crazy things we have to do to handle Python functions in 3.6 and above. The saga of changes before 3.6 is in other files. """ from xdis.code import iscode, code_has_star_arg, code_has_star_star_arg +from xdis.util import CO_GENERATOR from uncompyle6.scanner import Code from uncompyle6.parsers.treenode import SyntaxTree from uncompyle6.semantics.parser_error import ParserError @@ -371,7 +372,6 @@ def make_function36(self, node, is_lambda, nested=1, code_node=None): # docstring exists, dump it self.println(self.traverse(node[-2])) - scanner_code._tokens = None # save memory assert ast == "stmts" all_globals = find_all_globals(ast, set()) @@ -392,5 +392,20 @@ def make_function36(self, node, is_lambda, nested=1, code_node=None): ast, code.co_name, scanner_code._customize, is_lambda=is_lambda, returnNone=rn ) - scanner_code._tokens = None + # In obscure cases, a function may be a generator but the "yield" + # was optimized away. Here, we need to put in unreachable code to + # add in "yield" just so that the compiler will mark + # the GENERATOR bit of the function. See for example + # Python 3.x's test_generator.py test program. + if code.co_flags & CO_GENERATOR: + need_bogus_yield = True + for token in scanner_code._tokens: + if token == "YIELD_VALUE": + need_bogus_yield = False + break + pass + if need_bogus_yield: + self.template_engine(("%|if False:\n%+%|yield None%-",), node) + + scanner_code._tokens = None # save memory scanner_code._customize = None # save memory From 839eb6fe0bef81b79862b983f42b0edf037a4f3a Mon Sep 17 00:00:00 2001 From: rocky Date: Mon, 6 Jan 2020 15:02:40 -0500 Subject: [PATCH 08/11] bogus "yield" insertion not needed in 2.x --- uncompyle6/semantics/make_function2.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/uncompyle6/semantics/make_function2.py b/uncompyle6/semantics/make_function2.py index 0e53ff45..d1ab3791 100644 --- a/uncompyle6/semantics/make_function2.py +++ b/uncompyle6/semantics/make_function2.py @@ -204,20 +204,5 @@ def make_function2(self, node, is_lambda, nested=1, code_node=None): ast, code.co_name, code._customize, is_lambda=is_lambda, returnNone=rn ) - # In obscure cases, a function may be a generator but the "yield" - # was optimized away. Here, we need to put in unreachable code to - # add in "yield" just so that the compiler will mark - # the GENERATOR bit of the function. See for example - # Python 3.x's test_generator.py test program. - if code.co_flags & CO_GENERATOR: - need_bogus_yield = True - for token in scanner_code._tokens: - if token == "YIELD_VALUE": - need_bogus_yield = False - break - pass - if need_bogus_yield: - self.template_engine(("%|if False:\n%+%|yield None%-",), node) - code._tokens = None # save memory code._customize = None # save memory From a3beccc8745d3e958b0d3142ad9fd885f8c5cba8 Mon Sep 17 00:00:00 2001 From: rocky Date: Mon, 6 Jan 2020 17:22:50 -0500 Subject: [PATCH 09/11] Another case where we need code to set the generator bit.. via a dead-code "yield". --- uncompyle6/semantics/make_function36.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/uncompyle6/semantics/make_function36.py b/uncompyle6/semantics/make_function36.py index b0abfe43..02f0f7f2 100644 --- a/uncompyle6/semantics/make_function36.py +++ b/uncompyle6/semantics/make_function36.py @@ -17,7 +17,7 @@ All the crazy things we have to do to handle Python functions in 3.6 and above. The saga of changes before 3.6 is in other files. """ from xdis.code import iscode, code_has_star_arg, code_has_star_star_arg -from xdis.util import CO_GENERATOR +from xdis.util import CO_GENERATOR, CO_ASYNC_GENERATOR from uncompyle6.scanner import Code from uncompyle6.parsers.treenode import SyntaxTree from uncompyle6.semantics.parser_error import ParserError @@ -397,7 +397,7 @@ def make_function36(self, node, is_lambda, nested=1, code_node=None): # add in "yield" just so that the compiler will mark # the GENERATOR bit of the function. See for example # Python 3.x's test_generator.py test program. - if code.co_flags & CO_GENERATOR: + if code.co_flags & (CO_GENERATOR | CO_ASYNC_GENERATOR): need_bogus_yield = True for token in scanner_code._tokens: if token == "YIELD_VALUE": From 8e2c290e96fba6bcd6a344877ba5034950f69c44 Mon Sep 17 00:00:00 2001 From: rocky Date: Mon, 6 Jan 2020 18:29:10 -0500 Subject: [PATCH 10/11] Disambiguate 2.7 "try" and "try/else" will expand to other 2.x versions later --- test/stdlib/runtests.sh | 3 --- uncompyle6/parsers/parse2.py | 36 +++++++++++++++++++++++++++++++++++ uncompyle6/parsers/parse27.py | 10 +++++++--- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/test/stdlib/runtests.sh b/test/stdlib/runtests.sh index dcfc9b78..318d97e6 100755 --- a/test/stdlib/runtests.sh +++ b/test/stdlib/runtests.sh @@ -110,11 +110,9 @@ case $PYVERSION in [test_capi.py]=1 [test_curses.py]=1 # Possibly fails on its own but not detected [test_cmd_line.py]=1 # Takes too long, maybe hangs, or looking for interactive input? - [test_compilex.py]=1 # Probably complex literals again. Investigate [test_dis.py]=1 # We change line numbers - duh! [test_doctest.py]=1 # Fails on its own [test_exceptions.py]=1 - [test_format.py]=1 # control flow. uncompyle2 does not have problems here [test_grammar.py]=1 # Too many stmts. Handle large stmts [test_grp.py]=1 # test takes to long, works interactively though [test_io.py]=1 # Test takes too long to run @@ -137,7 +135,6 @@ case $PYVERSION in [test_unicode.py]=1 # Too long to run 11 seconds [test_xpickle.py]=1 # Runs ok but takes 72 seconds [test_zipfile64.py]=1 # Runs ok but takes 204 seconds - [test_zipimport.py]=1 # FIXME: improper try from try/else ? ) if (( batch )) ; then # Fails in crontab environment? diff --git a/uncompyle6/parsers/parse2.py b/uncompyle6/parsers/parse2.py index f6dce338..a5b3677d 100644 --- a/uncompyle6/parsers/parse2.py +++ b/uncompyle6/parsers/parse2.py @@ -161,6 +161,17 @@ class Python2Parser(PythonParser): except_handler ::= jmp_abs COME_FROM except_stmts END_FINALLY + # except_handler_else is intended to be used only in a + # try_else. The disambiguation comes from reduction rule + # checking where we make sure that the JUMP_FORWARD mismatches + # the JUMP_FORWARD before the END_FINALLY + except_handler_else ::= JUMP_FORWARD COME_FROM except_stmts + END_FINALLY come_froms + + except_handler_else ::= jmp_abs COME_FROM except_stmts + END_FINALLY + + except_stmts ::= except_stmt+ except_stmt ::= except_cond1 except_suite @@ -637,6 +648,8 @@ class Python2Parser(PythonParser): self.addRule(rule, nop_func) pass + self.check_reduce["except_handler"] = "tokens" + self.check_reduce["except_handler_else"] = "tokens" self.check_reduce["raise_stmt1"] = "tokens" self.check_reduce["assert_expr_and"] = "AST" self.check_reduce["tryelsestmt"] = "AST" @@ -668,6 +681,29 @@ class Python2Parser(PythonParser): jmp_false = ast[1] jump_target = jmp_false[0].attr return jump_target > tokens[last].off2int() + elif lhs in ("except_handler, except_handler_else"): + + # FIXME: expand this to other 2.x version + if self.version != 2.7: return False + + if tokens[first] in ("JUMP_FORWARD", "JUMP_ABSOLUTE"): + first_jump_target = tokens[first].pattr + last = min(last, len(tokens)-1) + for i in range(last, first, -1): + if tokens[i] == "END_FINALLY": + i -= 1 + second_jump = tokens[i] + if second_jump in ("JUMP_FORWARD", "JUMP_ABSOLUTE"): + second_jump_target = second_jump.pattr + equal_target = second_jump_target == first_jump_target + if equal_target: + return lhs != "except_handler" + else: + return lhs != "except_handler_else" + pass + pass + pass + pass elif rule == ("ifstmt", ("testexpr", "_ifstmts_jump")): for i in range(last-1, last-4, -1): t = tokens[i] diff --git a/uncompyle6/parsers/parse27.py b/uncompyle6/parsers/parse27.py index 9e947f6c..f36f9de7 100644 --- a/uncompyle6/parsers/parse27.py +++ b/uncompyle6/parsers/parse27.py @@ -60,13 +60,13 @@ class Python27Parser(Python2Parser): COME_FROM_FINALLY suite_stmts_opt END_FINALLY tryelsestmt ::= SETUP_EXCEPT suite_stmts_opt POP_BLOCK - except_handler else_suite COME_FROM + except_handler_else else_suite COME_FROM tryelsestmtl ::= SETUP_EXCEPT suite_stmts_opt POP_BLOCK - except_handler else_suitel JUMP_BACK COME_FROM + except_handler_else else_suitel JUMP_BACK COME_FROM tryelsestmtl ::= SETUP_EXCEPT suite_stmts_opt POP_BLOCK - except_handler else_suitel + except_handler_else else_suitel except_stmt ::= except_cond2 except_suite @@ -216,6 +216,10 @@ class Python27Parser(Python2Parser): super(Python27Parser, self).customize_grammar_rules(tokens, customize) self.check_reduce["and"] = "AST" self.check_reduce["conditional"] = "AST" + + self.check_reduce["except_handler"] = "tokens" + self.check_reduce["except_handler_else"] = "tokens" + # self.check_reduce["or"] = "AST" self.check_reduce["raise_stmt1"] = "AST" self.check_reduce["iflaststmtl"] = "AST" From 19cac525eeeb49bf218f5572d15df63af96751ef Mon Sep 17 00:00:00 2001 From: rocky Date: Mon, 6 Jan 2020 18:57:05 -0500 Subject: [PATCH 11/11] Accomodate "return" in an except handler --- uncompyle6/parsers/parse2.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/uncompyle6/parsers/parse2.py b/uncompyle6/parsers/parse2.py index a5b3677d..d0c67674 100644 --- a/uncompyle6/parsers/parse2.py +++ b/uncompyle6/parsers/parse2.py @@ -701,6 +701,8 @@ class Python2Parser(PythonParser): else: return lhs != "except_handler_else" pass + else: + return False pass pass pass