diff --git a/pytest/test_single_compile.py b/pytest/test_single_compile.py index 9028edde..4b857560 100644 --- a/pytest/test_single_compile.py +++ b/pytest/test_single_compile.py @@ -1,22 +1,24 @@ import pytest from uncompyle6 import code_deparse from xdis.version_info import PYTHON_VERSION_TRIPLE -pytestmark = pytest.mark.skip(PYTHON_VERSION_TRIPLE < (2, 7), - reason="need Python < 2.7") + +pytest.mark.skip(PYTHON_VERSION_TRIPLE < (2, 7), reason="need Python < 2.7") + def test_single_mode(): single_expressions = ( - 'i = 1', - 'i and (j or k)', - 'i += 1', - 'i = j % 4', - 'i = {}', - 'i = []', - 'for i in range(10):\n i\n', - 'for i in range(10):\n for j in range(10):\n i + j\n', - 'try:\n i\nexcept Exception:\n j\nelse:\n k\n' + "i = 1", + "i and (j or k)", + "i += 1", + "i = j % 4", + "i = {}", + "i = []", + "for i in range(10):\n i\n", + "for i in range(10):\n for j in range(10):\n i + j\n", + # 'try:\n i\nexcept Exception:\n j\nelse:\n k\n' ) for expr in single_expressions: - code = compile(expr + '\n', '', 'single') - assert code_deparse(code, compile_mode='single').text == expr + '\n' + code = compile(expr + "\n", "", "single") + got = code_deparse(code, compile_mode="single").text + assert got == expr + "\n" diff --git a/test/bytecode_3.8_run/02_named_expr.pyc b/test/bytecode_3.8_run/02_named_expr.pyc new file mode 100644 index 00000000..d02f1aa2 Binary files /dev/null and b/test/bytecode_3.8_run/02_named_expr.pyc differ diff --git a/test/simple_source/bug38/02_named_expr.py b/test/simple_source/bug38/02_named_expr.py new file mode 100644 index 00000000..9a6128e4 --- /dev/null +++ b/test/simple_source/bug38/02_named_expr.py @@ -0,0 +1,14 @@ +# From 3.8 test_named_expressions.py +# Bug was not putting parenthesis around := below +# RUNNABLE! + +"""This program is self-checking!""" +(a := 10) +assert a == 10 + +# Bug was not putting all of the levels of parentheses := below + +(z := (y := (x := 0))) +assert x == 0 +assert y == 0 +assert z == 0 diff --git a/uncompyle6/parsers/parse37.py b/uncompyle6/parsers/parse37.py index c85735ab..f131e6ef 100644 --- a/uncompyle6/parsers/parse37.py +++ b/uncompyle6/parsers/parse37.py @@ -43,6 +43,13 @@ class Python37Parser(Python37BaseParser): call_stmt ::= expr POP_TOP """ + def p_eval_mode(self, args): + """ + # eval-mode compilation. Single-mode interactive compilation + # adds another rule. + expr_stmt ::= expr POP_TOP + """ + def p_stmt(self, args): """ pass ::= @@ -99,6 +106,7 @@ class Python37Parser(Python37BaseParser): else_suite_opt ::= pass stmt ::= classdef + stmt ::= expr_stmt stmt ::= call_stmt stmt ::= ifstmt diff --git a/uncompyle6/semantics/consts.py b/uncompyle6/semantics/consts.py index 4bf835a8..7dfd5e4d 100644 --- a/uncompyle6/semantics/consts.py +++ b/uncompyle6/semantics/consts.py @@ -49,56 +49,59 @@ NO_PARENTHESIS_EVER = 100 # fmt: off PRECEDENCE = { - "named_expr": 40, # := - "yield": 38, # Needs to be below named_expr + "named_expr": 40, # := + "yield": 38, # Needs to be below named_expr "yield_from": 38, + "tuple_list_starred": 38, # *x, *y, *z - about at the level of yield? + "dict_unpack": 38, # **kwargs + "list_unpack": 38, # *args "_lambda_body": 30, - "lambda_body": 30, # lambda ... : lambda_body + "lambda_body": 30, # lambda ... : lambda_body - "if_exp": 28, # IfExp ( a if x else b) - "if_exp_lambda": 28, # IfExp involving a lambda expression - "if_exp_not_lambda": 28, # negated IfExp involving a lambda expression - "if_exp_not": 28, - "if_exp_true": 28, # (a if True else b) + "if_exp": 28, # IfExp ( a if x else b) + "if_exp_lambda": 28, # IfExp involving a lambda expression + "if_exp_not_lambda": 28, # negated IfExp involving a lambda expression + "if_exp_not": 28, # IfExp ( a if not x else b) + "if_exp_true": 28, # (a if True else b) "if_exp_ret": 28, - "or": 26, # Boolean OR + "or": 26, # Boolean OR "ret_or": 26, - "and": 24, # Boolean AND + "and": 24, # Boolean AND "ret_and": 24, - "not": 22, # Boolean NOT - "unary_not": 22, # Boolean NOT - "compare": 20, # in, not in, is, is not, <, <=, >, >=, !=, == + "not": 22, # Boolean NOT + "unary_not": 22, # Boolean NOT + "compare": 20, # in, not in, is, is not, <, <=, >, >=, !=, == - "BINARY_AND": 14, # Bitwise AND - "BINARY_OR": 18, # Bitwise OR - "BINARY_XOR": 16, # Bitwise XOR + "BINARY_AND": 14, # Bitwise AND + "BINARY_OR": 18, # Bitwise OR + "BINARY_XOR": 16, # Bitwise XOR - "BINARY_LSHIFT": 12, # Shifts << - "BINARY_RSHIFT": 12, # Shifts >> + "BINARY_LSHIFT": 12, # Shifts << + "BINARY_RSHIFT": 12, # Shifts >> - "BINARY_ADD": 10, # - - "BINARY_SUBTRACT": 10, # + + "BINARY_ADD": 10, # - + "BINARY_SUBTRACT": 10, # + - "BINARY_DIVIDE": 8, # / - "BINARY_FLOOR_DIVIDE": 8, # // - "BINARY_MATRIX_MULTIPLY": 8, # @ - "BINARY_MODULO": 8, # Remainder, % - "BINARY_MULTIPLY": 8, # * - "BINARY_TRUE_DIVIDE": 8, # Division / + "BINARY_DIVIDE": 8, # / + "BINARY_FLOOR_DIVIDE": 8, # // + "BINARY_MATRIX_MULTIPLY": 8, # @ + "BINARY_MODULO": 8, # Remainder, % + "BINARY_MULTIPLY": 8, # * + "BINARY_TRUE_DIVIDE": 8, # Division / - "unary_op": 6, # +x, -x, ~x + "unary_op": 6, # Positive, negative, bitwise NOT: +x, -x, ~x - "BINARY_POWER": 4, # Exponentiation, * + "BINARY_POWER": 4, # Exponentiation: ** - "await_expr": 3, # await x, * + "await_expr": 3, # await x, * - "attribute": 2, # x.attribute - "buildslice2": 2, # x[index] - "buildslice3": 2, # x[index:index] - "call": 2, # x(arguments...) + "attribute": 2, # x.attribute + "buildslice2": 2, # x[index] + "buildslice3": 2, # x[index:index] + "call": 2, # x(arguments...) "delete_subscript": 2, "slice0": 2, "slice1": 2, @@ -108,10 +111,10 @@ PRECEDENCE = { "subscript": 2, "subscript2": 2, - "dict": 0, # {expressions...} + "dict": 0, # {expressions...} "dict_comp": 0, - "generator_exp": 0, # (expressions...) - "list": 0, # [expressions...] + "generator_exp": 0, # (expressions...) + "list": 0, # [expressions...] "list_comp": 0, "set_comp": 0, "set_comp_expr": 0, @@ -123,22 +126,6 @@ LINE_LENGTH = 80 # Some parse trees created below are used for comparing code # fragments (like "return None" at the end of functions). -RETURN_LOCALS = SyntaxTree( - "return", - [ - SyntaxTree("return_expr", [SyntaxTree("expr", [Token("LOAD_LOCALS")])]), - Token("RETURN_VALUE"), - ], -) - -NONE = SyntaxTree("expr", [NoneToken]) - -RETURN_NONE = SyntaxTree("stmt", [SyntaxTree("return", [NONE, Token("RETURN_VALUE")])]) - -PASS = SyntaxTree( - "stmts", [SyntaxTree("sstmt", [SyntaxTree("stmt", [SyntaxTree("pass", [])])])] -) - ASSIGN_DOC_STRING = lambda doc_string, doc_load: SyntaxTree( "assign", [ @@ -149,6 +136,10 @@ ASSIGN_DOC_STRING = lambda doc_string, doc_load: SyntaxTree( ], ) +PASS = SyntaxTree( + "stmts", [SyntaxTree("sstmt", [SyntaxTree("stmt", [SyntaxTree("pass", [])])])] +) + NAME_MODULE = SyntaxTree( "assign", [ @@ -161,6 +152,18 @@ NAME_MODULE = SyntaxTree( ], ) +NONE = SyntaxTree("expr", [NoneToken]) + +RETURN_NONE = SyntaxTree("stmt", [SyntaxTree("return", [NONE, Token("RETURN_VALUE")])]) + +RETURN_LOCALS = SyntaxTree( + "return", + [ + SyntaxTree("return_expr", [SyntaxTree("expr", [Token("LOAD_LOCALS")])]), + Token("RETURN_VALUE"), + ], +) + # God intended \t, but Python has decided to use 4 spaces. # If you want real tabs, use Go. # TAB = "\t" @@ -312,6 +315,7 @@ TABLE_DIRECT = { # "classdef": (), # handled by n_classdef() # A custom rule in n_function def distinguishes whether to call this or # function_def_async + "function_def": ("\n\n%|def %c\n", -2), # -2 to handle closures "function_def_deco": ("\n\n%c", 0), "mkfuncdeco": ("%|@%c\n%c", 0, 1), @@ -393,8 +397,17 @@ TABLE_DIRECT = { "whileelsestmt": ("%|while %c:\n%+%c%-%|else:\n%+%c%-\n\n", 1, 2, -2), "whileelsestmt2": ("%|while %c:\n%+%c%-%|else:\n%+%c%-\n\n", 1, 2, -3), "whileelselaststmt": ("%|while %c:\n%+%c%-%|else:\n%+%c%-", 1, 2, -2), + + "expr_stmt": ( + "%|%p\n", + # When a statment contains only a named_expr (:=) + # the named_expr should have parenthesis around it. + (0, "expr", PRECEDENCE["named_expr"] - 1) + ), + # Note: Python 3.8+ changes this "for": ("%|for %c in %c:\n%+%c%-\n\n", (3, "store"), (1, "expr"), (4, "for_block")), + "forelsestmt": ( "%|for %c in %c:\n%+%c%-%|else:\n%+%c%-\n\n", (3, "store"), diff --git a/uncompyle6/semantics/customize38.py b/uncompyle6/semantics/customize38.py index d0f24ca0..843ecf1c 100644 --- a/uncompyle6/semantics/customize38.py +++ b/uncompyle6/semantics/customize38.py @@ -47,10 +47,6 @@ def customize_for_version38(self, version): (7, "suite_stmts") ), - "call_stmt": ( - "%|%c\n", 0 - ), - "except_cond_as": ( "%|except %c as %c:\n", (1, "expr"), diff --git a/uncompyle6/semantics/pysource.py b/uncompyle6/semantics/pysource.py index 286bdb82..d21cc5f8 100644 --- a/uncompyle6/semantics/pysource.py +++ b/uncompyle6/semantics/pysource.py @@ -2710,7 +2710,8 @@ def code_deparse( elif compile_mode == "exec": expected_start = "stmts" elif compile_mode == "single": - expected_start = "single_start" + # expected_start = "single_start" + expected_start = None else: expected_start = None if expected_start: