Handle walrus operator

Or rather set precedence on call_stmt and expr_stmt

Adjust pytest test_single_compile so it works now
This commit is contained in:
rocky
2022-04-12 05:21:13 -04:00
parent e7fd592313
commit a1fe069c8c
7 changed files with 104 additions and 70 deletions

View File

@@ -1,22 +1,24 @@
import pytest import pytest
from uncompyle6 import code_deparse from uncompyle6 import code_deparse
from xdis.version_info import PYTHON_VERSION_TRIPLE 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(): def test_single_mode():
single_expressions = ( single_expressions = (
'i = 1', "i = 1",
'i and (j or k)', "i and (j or k)",
'i += 1', "i += 1",
'i = j % 4', "i = j % 4",
'i = {}', "i = {}",
'i = []', "i = []",
'for i in range(10):\n i\n', "for i in range(10):\n i\n",
'for i in range(10):\n for j in range(10):\n i + j\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' # 'try:\n i\nexcept Exception:\n j\nelse:\n k\n'
) )
for expr in single_expressions: for expr in single_expressions:
code = compile(expr + '\n', '<string>', 'single') code = compile(expr + "\n", "<string>", "single")
assert code_deparse(code, compile_mode='single').text == expr + '\n' got = code_deparse(code, compile_mode="single").text
assert got == expr + "\n"

Binary file not shown.

View File

@@ -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

View File

@@ -43,6 +43,13 @@ class Python37Parser(Python37BaseParser):
call_stmt ::= expr POP_TOP 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): def p_stmt(self, args):
""" """
pass ::= pass ::=
@@ -99,6 +106,7 @@ class Python37Parser(Python37BaseParser):
else_suite_opt ::= pass else_suite_opt ::= pass
stmt ::= classdef stmt ::= classdef
stmt ::= expr_stmt
stmt ::= call_stmt stmt ::= call_stmt
stmt ::= ifstmt stmt ::= ifstmt

View File

@@ -52,6 +52,9 @@ PRECEDENCE = {
"named_expr": 40, # := "named_expr": 40, # :=
"yield": 38, # Needs to be below named_expr "yield": 38, # Needs to be below named_expr
"yield_from": 38, "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_body": 30, # lambda ... : lambda_body "lambda_body": 30, # lambda ... : lambda_body
@@ -59,7 +62,7 @@ PRECEDENCE = {
"if_exp": 28, # IfExp ( a if x else b) "if_exp": 28, # IfExp ( a if x else b)
"if_exp_lambda": 28, # IfExp involving a lambda expression "if_exp_lambda": 28, # IfExp involving a lambda expression
"if_exp_not_lambda": 28, # negated IfExp involving a lambda expression "if_exp_not_lambda": 28, # negated IfExp involving a lambda expression
"if_exp_not": 28, "if_exp_not": 28, # IfExp ( a if not x else b)
"if_exp_true": 28, # (a if True else b) "if_exp_true": 28, # (a if True else b)
"if_exp_ret": 28, "if_exp_ret": 28,
@@ -89,9 +92,9 @@ PRECEDENCE = {
"BINARY_MULTIPLY": 8, # * "BINARY_MULTIPLY": 8, # *
"BINARY_TRUE_DIVIDE": 8, # Division / "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, *
@@ -123,22 +126,6 @@ LINE_LENGTH = 80
# Some parse trees created below are used for comparing code # Some parse trees created below are used for comparing code
# fragments (like "return None" at the end of functions). # 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_DOC_STRING = lambda doc_string, doc_load: SyntaxTree(
"assign", "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( NAME_MODULE = SyntaxTree(
"assign", "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. # God intended \t, but Python has decided to use 4 spaces.
# If you want real tabs, use Go. # If you want real tabs, use Go.
# TAB = "\t" # TAB = "\t"
@@ -312,6 +315,7 @@ TABLE_DIRECT = {
# "classdef": (), # handled by n_classdef() # "classdef": (), # handled by n_classdef()
# A custom rule in n_function def distinguishes whether to call this or # A custom rule in n_function def distinguishes whether to call this or
# function_def_async # function_def_async
"function_def": ("\n\n%|def %c\n", -2), # -2 to handle closures "function_def": ("\n\n%|def %c\n", -2), # -2 to handle closures
"function_def_deco": ("\n\n%c", 0), "function_def_deco": ("\n\n%c", 0),
"mkfuncdeco": ("%|@%c\n%c", 0, 1), "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), "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), "whileelsestmt2": ("%|while %c:\n%+%c%-%|else:\n%+%c%-\n\n", 1, 2, -3),
"whileelselaststmt": ("%|while %c:\n%+%c%-%|else:\n%+%c%-", 1, 2, -2), "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 # Note: Python 3.8+ changes this
"for": ("%|for %c in %c:\n%+%c%-\n\n", (3, "store"), (1, "expr"), (4, "for_block")), "for": ("%|for %c in %c:\n%+%c%-\n\n", (3, "store"), (1, "expr"), (4, "for_block")),
"forelsestmt": ( "forelsestmt": (
"%|for %c in %c:\n%+%c%-%|else:\n%+%c%-\n\n", "%|for %c in %c:\n%+%c%-%|else:\n%+%c%-\n\n",
(3, "store"), (3, "store"),

View File

@@ -47,10 +47,6 @@ def customize_for_version38(self, version):
(7, "suite_stmts") (7, "suite_stmts")
), ),
"call_stmt": (
"%|%c\n", 0
),
"except_cond_as": ( "except_cond_as": (
"%|except %c as %c:\n", "%|except %c as %c:\n",
(1, "expr"), (1, "expr"),

View File

@@ -2710,7 +2710,8 @@ def code_deparse(
elif compile_mode == "exec": elif compile_mode == "exec":
expected_start = "stmts" expected_start = "stmts"
elif compile_mode == "single": elif compile_mode == "single":
expected_start = "single_start" # expected_start = "single_start"
expected_start = None
else: else:
expected_start = None expected_start = None
if expected_start: if expected_start: