diff --git a/.gitignore b/.gitignore index d4e5cf9c..856518e3 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,5 @@ __pycache__ build /.venv* -/.idea \ No newline at end of file +/.idea +/.hypothesis diff --git a/pytest/test_grammar.py b/pytest/test_grammar.py index adccec02..241d064d 100644 --- a/pytest/test_grammar.py +++ b/pytest/test_grammar.py @@ -39,13 +39,14 @@ def test_grammar(): s = get_scanner(PYTHON_VERSION, IS_PYPY) ignore_set = set( """ - JUMP_BACK CONTINUE RETURN_END_IF + JUMP_BACK CONTINUE COME_FROM COME_FROM_EXCEPT COME_FROM_EXCEPT_CLAUSE COME_FROM_LOOP COME_FROM_WITH COME_FROM_FINALLY ELSE LOAD_GENEXPR LOAD_ASSERT LOAD_SETCOMP LOAD_DICTCOMP - LAMBDA_MARKER RETURN_LAST + LAMBDA_MARKER + RETURN_END_IF RETURN_END_IF_LAMBDA RETURN_VALUE_LAMBDA RETURN_LAST """.split()) if 2.6 <= PYTHON_VERSION <= 2.7: opcode_set = set(s.opc.opname).union(ignore_set) diff --git a/test/simple_source/branching/02_ifelse_lambda.py b/test/simple_source/branching/02_ifelse_lambda.py new file mode 100644 index 00000000..b21fa4f5 --- /dev/null +++ b/test/simple_source/branching/02_ifelse_lambda.py @@ -0,0 +1,5 @@ +# We have to do contortions here because +# lambda's have to be more or less on a line + +f = lambda x: 1 if x<2 else 3 +f(5) diff --git a/uncompyle6/parser.py b/uncompyle6/parser.py index ee18b737..f8495777 100644 --- a/uncompyle6/parser.py +++ b/uncompyle6/parser.py @@ -256,8 +256,11 @@ class PythonParser(GenericASTBuilder): stmt ::= return_stmt return_stmt ::= ret_expr RETURN_VALUE + return_stmt_lambda ::= ret_expr RETURN_VALUE_LAMBDA + return_stmts ::= return_stmt return_stmts ::= _stmts return_stmt + """ pass @@ -532,7 +535,9 @@ class PythonParser(GenericASTBuilder): stmt ::= return_lambda stmt ::= conditional_lambda - return_lambda ::= ret_expr RETURN_VALUE LAMBDA_MARKER + return_lambda ::= ret_expr RETURN_VALUE_LAMBDA LAMBDA_MARKER + return_lambda ::= ret_expr RETURN_VALUE_LAMBDA + conditional_lambda ::= expr jmp_false return_if_stmt return_stmt LAMBDA_MARKER cmp ::= cmp_list diff --git a/uncompyle6/parsers/parse26.py b/uncompyle6/parsers/parse26.py index c8561d59..a8ce8f38 100644 --- a/uncompyle6/parsers/parse26.py +++ b/uncompyle6/parsers/parse26.py @@ -247,7 +247,9 @@ class Python26Parser(Python2Parser): and ::= expr JUMP_IF_FALSE POP_TOP expr JUMP_IF_FALSE POP_TOP cmp_list ::= expr cmp_list1 ROT_TWO COME_FROM POP_TOP _come_from - conditional_lambda ::= expr jmp_false_then return_if_stmt return_stmt LAMBDA_MARKER + return_if_lambda ::= RETURN_END_IF_LAMBDA POP_TOP + conditional_lambda ::= expr jmp_false_then expr return_if_lambda + return_stmt_lambda LAMBDA_MARKER """ def add_custom_rules(self, tokens, customize): diff --git a/uncompyle6/parsers/parse27.py b/uncompyle6/parsers/parse27.py index d7392810..614b30da 100644 --- a/uncompyle6/parsers/parse27.py +++ b/uncompyle6/parsers/parse27.py @@ -94,6 +94,10 @@ class Python27Parser(Python2Parser): WITH_CLEANUP END_FINALLY # Common with 2.6 + return_if_lambda ::= RETURN_END_IF_LAMBDA COME_FROM + conditional_lambda ::= expr jmp_false expr return_if_lambda + return_stmt_lambda LAMBDA_MARKER + while1stmt ::= SETUP_LOOP return_stmts bp_come_from while1stmt ::= SETUP_LOOP return_stmts COME_FROM """ diff --git a/uncompyle6/parsers/parse3.py b/uncompyle6/parsers/parse3.py index d5e20775..df65996a 100644 --- a/uncompyle6/parsers/parse3.py +++ b/uncompyle6/parsers/parse3.py @@ -418,6 +418,13 @@ class Python3Parser(PythonParser): # a JUMP_ABSOLUTE with no COME_FROM conditional ::= expr jmp_false expr jump_absolute_else expr + return_if_lambda ::= RETURN_END_IF_LAMBDA + conditional_lambda ::= expr jmp_false return_stmt_lambda + return_stmt_lambda LAMBDA_MARKER + conditional_lambda ::= expr jmp_false expr return_if_lambda + return_stmt_lambda LAMBDA_MARKER + + expr ::= LOAD_CLASSNAME # Python 3.4+ diff --git a/uncompyle6/semantics/consts.py b/uncompyle6/semantics/consts.py index b5d49644..db0390f1 100644 --- a/uncompyle6/semantics/consts.py +++ b/uncompyle6/semantics/consts.py @@ -173,8 +173,13 @@ TABLE_DIRECT = { 'ret_cond': ( '%p if %p else %p', (2, 27), (0, 27), (-1, 27) ), 'conditionalnot': ( '%p if not %p else %p', (2, 27), (0, 22), (4, 27) ), 'ret_cond_not': ( '%p if not %p else %p', (2, 27), (0, 22), (-1, 27) ), - 'conditional_lambda': ( '(%c if %c else %c)', 2, 0, 3), - 'return_lambda': ('%c', 0), + 'conditional_lambda': ( '%c if %c else %c', 2, 0, 4), + + # The semicolon is because Python 3.x can have be dead code as a result of its + # optimization. We don't Python's remove dead code (yet) anymore than Python does. + # So without that we would have "return 2return3" rather than "return 2;return 3" + 'return_lambda': ('return %c;', 0), + 'compare': ( '%p %[-1]{pattr.replace("-", " ")} %p', (0, 19), (1, 19) ), 'cmp_list': ( '%p %p', (0, 29), (1, 30)), 'cmp_list1': ( '%[3]{pattr} %p %p', (0, 19), (-2, 19)), @@ -209,6 +214,7 @@ TABLE_DIRECT = { 'raise_stmt3': ( '%|raise %c, %c, %c\n', 0, 1, 2), # 'yield': ( 'yield %c', 0), # 'return_stmt': ( '%|return %c\n', 0), + 'return_if_stmt': ( 'return %c\n', 0), 'ifstmt': ( '%|if %c:\n%+%c%-', 0, 1 ), 'iflaststmt': ( '%|if %c:\n%+%c%-', 0, 1 ), @@ -331,6 +337,7 @@ PRECEDENCE = { 'ret_or': 26, 'conditional': 28, + 'conditional_lamdba': 28, 'conditionalnot': 28, 'ret_cond': 28, 'ret_cond_not': 28, diff --git a/uncompyle6/semantics/pysource.py b/uncompyle6/semantics/pysource.py index ba82498c..c9c73afe 100644 --- a/uncompyle6/semantics/pysource.py +++ b/uncompyle6/semantics/pysource.py @@ -660,6 +660,7 @@ class SourceWalker(GenericASTTraversal, object): def n_return_if_stmt(self, node): if self.params['isLambda']: + self.write(' return ') self.preorder(node[0]) self.prune() else: @@ -2129,6 +2130,11 @@ class SourceWalker(GenericASTTraversal, object): # assert isinstance(tokens[0], Token) if isLambda: + for t in tokens: + if t.type == 'RETURN_END_IF': + t.type = 'RETURN_END_IF_LAMBDA' + elif t.type == 'RETURN_VALUE': + t.type = 'RETURN_VALUE_LAMBDA' tokens.append(Token('LAMBDA_MARKER')) try: ast = python_parser.parse(self.p, tokens, customize) @@ -2143,7 +2149,7 @@ class SourceWalker(GenericASTTraversal, object): # than fight (with the grammar to not emit "return None"). if self.hide_internal: if len(tokens) >= 2 and not noneInNames: - if tokens[-1].type == 'RETURN_VALUE': + if tokens[-1].type in ('RETURN_VALUE', 'RETURN_VALUE_LAMBDA'): # Python 3.4's classes can add a "return None" which is # invalid syntax. if tokens[-2].type == 'LOAD_CONST':