Merge branch 'master' into python-3.3-to-3.5

This commit is contained in:
rocky
2022-04-15 08:42:40 -04:00
25 changed files with 324 additions and 149 deletions

View File

@@ -28,6 +28,8 @@ someone to do, especially when you are the primary beneficiary of the
work, or the task is complex, long, or tedious. If your code is over
30 lines long, it fits into this category.
See also https://github.com/rocky/python-uncomp[yle6/blob/master/HOW-TO-REPORT-A-BUG.md ?
-->
<!--
@@ -52,7 +54,7 @@ Bug reports that violate the above may be discarded.
## Description
<!-- Add a clear and concise description of the bug. -->
<!-- Please add a clear and concise description of the bug. -->
## How to Reproduce
@@ -73,6 +75,16 @@ can add that too.
-->
## Output Given
<!--
Please include not just the error message but all output leading to the message which includes echoing input and messages up to the error.
For a command-line environment include command invocation and all the output produced.
If this is too long, then try narrowing the problem to something short.
-->
## Expected behavior
<!-- Add a clear and concise description of what you expected to happen. -->
@@ -84,12 +96,20 @@ can add that too.
Please modify for your setup
- Uncompyle6 version: output from `uncompyle6 --version` or `pip show uncompyle6`
- Python version for the version of Python the byte-compiled the file: `python -c "import sys; print(sys.version)"` where `python` is the correct Cpython or Pypy binary.
- Python version for the version of Python the byte-compiled the file: `python -c "import sys; print(sys.version)"` where `python` is the correct CPython or PyPy binary.
- OS and Version: [e.g. Ubuntu bionic]
-->
## Additional Environment or Context
## Workarounds
<!-- If there is a workaround for the problem, describe that here. -->
## Priority
<!-- If this is blocking some important activity let us know what activity it blocks. -->
## Additional Context
<!-- _This section is optional._

View File

@@ -234,3 +234,5 @@ However feel free to remove any comments, and modify variable names
or constants in the source code.
If there is some legitimate reason to keep confidentiality, you can contact me by email to explain the extenuating circumstances. However I tend to discard without reading anonymous email.
Private consulting available via https://calendly.com/rb3216 rates: $150 for 30 minutes; $250 for 60 minutes.

View File

@@ -5,4 +5,4 @@ if [[ $0 == ${BASH_SOURCE[0]} ]] ; then
echo "This script should be *sourced* rather than run directly through bash"
exit 1
fi
export PYVERSIONS='3.6.15 3.7.12 pyston-2.3 3.8.12 3.9.9 3.10.0'
export PYVERSIONS='3.6.15 3.7.13 pypy3.6-7.3.0 pyston-2.3.2 3.8.13 3.9.12 3.10.4'

View File

@@ -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', '<string>', 'single')
assert code_deparse(code, compile_mode='single').text == expr + '\n'
code = compile(expr + "\n", "<string>", "single")
got = code_deparse(code, compile_mode="single").text
assert got == expr + "\n"

View File

@@ -13,7 +13,7 @@ version = sys.version[0:3]
vers = sys.version_info[:2]
if sys.argv[1] in ("--run", "-r"):
suffix = "_run"
assert sys.argv >= 3
assert len(sys.argv) >= 3
py_source = sys.argv[2:]
i = 2
else:

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -2,6 +2,8 @@
from math import atan2
# RUNNABLE!
"""This program is self-checking!"""
def assertCloseAbs(x, y, eps=1e-09):
"""Return true iff floats x and y "are close\""""
if abs(x) > abs(y):
@@ -32,7 +34,7 @@ def check_div(x, y):
assertClose(q, x)
def test_truediv():
simple_real = [float(i) for i in range(-5, 6)]
simple_real = [float(i) for i in range(-3, 3)]
simple_complex = [complex(x, y) for x in simple_real for y in simple_real]
for x in simple_complex:
for y in simple_complex:
@@ -43,7 +45,7 @@ def test_plus_minus_0j():
assert -0-0j == -0j == complex(0.0, 0.0)
z1, z2 = (0j, -0j)
assert atan2(z1.imag, -1.0) == atan2(0.0, -1.0)
# assert atan2(z2.imag, -1.0), atan2(-0.0, -1.0)
assert atan2(z2.imag, -1.0), atan2(-0.0, -1.0)
# Check that we can handle -inf, and inf as a complex numbers.
# And put it in a tuple and a list to make it harder.

View File

@@ -2,13 +2,17 @@
# String interpolation tests
# RUNNABLE!
var1 = 'x'
var2 = 'y'
abc = 'def'
assert (f"interpolate {var1} strings {var2!r} {var2!s} 'py36" ==
"interpolate x strings 'y' y 'py36")
assert 'def0' == f'{abc}0'
assert 'defdef' == f'{abc}{abc!s}'
"""This program is self-checking!"""
var1 = "x"
var2 = "y"
abc = "def"
assert (
f"interpolate {var1} strings {var2!r} {var2!s} 'py36"
== "interpolate x strings 'y' y 'py36"
)
assert "def0" == f"{abc}0"
assert "defdef" == f"{abc}{abc!s}"
# From 3.6 functools.py
# Bug was handling format operator strings.
@@ -21,51 +25,51 @@ assert y == "functools.1=['2'](2)"
# From 3.6 http/client.py
# Bug is in handling X
chunk = ['a', 'b', 'c']
chunk2 = 'd'
chunk = f'{len(chunk):X}' + chunk2
assert chunk == '3d'
chunk = ["a", "b", "c"]
chunk2 = "d"
chunk = f"{len(chunk):X}" + chunk2
assert chunk == "3d"
chunk = b'abc'
chunk2 = 'd'
chunk = f'{len(chunk):X}\r\n'.encode('ascii') + chunk \
+ b'\r\n'
assert chunk == b'3\r\nabc\r\n'
chunk = b"abc"
chunk2 = "d"
chunk = f"{len(chunk):X}\r\n".encode("ascii") + chunk + b"\r\n"
assert chunk == b"3\r\nabc\r\n"
# From 3.6.8 idlelib/pyshell.py
# Bug was handling '''
import os
filename = '.'
source = 'foo'
source = (f"__file__ = r'''{os.path.abspath(filename)}'''\n"
+ source + "\ndel __file__")
filename = "."
source = "foo"
source = f"__file__ = r'''{os.path.abspath(filename)}'''\n" + source + "\ndel __file__"
# Note how { and } are *not* escaped here
f = 'one'
name = 'two'
assert(f"{f}{'{{name}}'} {f}{'{name}'}") == 'one{{name}} one{name}'
f = "one"
name = "two"
assert (f"{f}{'{{name}}'} {f}{'{name}'}") == "one{{name}} one{name}"
# From 3.7.3 dataclasses.py
log_rounds = 5
assert "05$" == f'{log_rounds:02d}$'
log_rounds = 5
assert "05$" == f"{log_rounds:02d}$"
def testit(a, b, l):
# print(l)
return l
# The call below shows the need for BUILD_STRING to count expr arguments.
# Also note that we use {{ }} to escape braces in contrast to the example
# above.
def _repr_fn(fields):
return testit('__repr__',
('self',),
['return xx + f"(' +
', '.join([f"{f}={{self.{f}!r}}"
for f in fields]) +
')"'])
return testit(
"__repr__",
("self",),
['return xx + f"(' + ", ".join([f"{f}={{self.{f}!r}}" for f in fields]) + ')"'],
)
fields = ['a', 'b', 'c']
fields = ["a", "b", "c"]
assert _repr_fn(fields) == ['return xx + f"(a={self.a!r}, b={self.b!r}, c={self.c!r})"']
@@ -85,28 +89,31 @@ else:
assert False, "f'{lambda x:x}' should be a syntax error"
(x, y, width) = ("foo", 2, 10)
assert f'x={x*y:{width}}' == 'x=foofoo '
assert f"x={x*y:{width}}" == "x=foofoo "
# Why the fact that the distinction of docstring versus stmt is a
# string expression is important academic, but we will decompile an
# equivalent thing. For compatiblity with older Python we'll use "%"
# instead of a format string
def f():
f'''Not a docstring'''
f"""Not a docstring"""
def g():
'''Not a docstring''' \
f''
"""Not a docstring""" f""
assert f.__doc__ is None
assert g.__doc__ is None
import decimal
width, precision, value = (10, 4, decimal.Decimal('12.34567'))
width, precision, value = (10, 4, decimal.Decimal("12.34567"))
# Make sure we don't have additional f'..' inside the format strings below.
assert f'result: {value:{width}.{precision}}' == 'result: 12.35'
assert f'result: {value:{width:0}.{precision:1}}' == 'result: 12.35'
assert f'{2}\t' == '2\t'
assert f"result: {value:{width}.{precision}}" == "result: 12.35"
assert f"result: {value:{width:0}.{precision:1}}" == "result: 12.35"
assert f"{2}\t" == "2\t"
# But below we *do* need the additional f".."
assert f'{f"{0}"*3}' == "000"
@@ -115,4 +122,4 @@ assert f'{f"{0}"*3}' == "000"
# ^
# The former, {{ confuses the format strings so dictionary/set comprehensions
# don't work.
assert f'expr={ {x: y for x, y in [(1, 2), ]}}' == 'expr={1: 2}'
assert f"expr={ {x: y for x, y in [(1, 2), ]}}" == "expr={1: 2}"

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

View File

@@ -469,7 +469,7 @@ class Python37BaseParser(PythonParser):
)
else:
assert token.attr == 3, (
"BUILD_SLICE value must be 2 or 3; is %s" % v
"BUILD_SLICE value must be 2 or 3; is %s" % token.attr
)
self.add_unique_rules(
[

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2021 by Rocky Bernstein
# Copyright (c) 2021-2022 by Rocky Bernstein
"""
Python PyPy 3.7 decompiler scanner.
@@ -6,7 +6,7 @@ Does some additional massaging of xdis-disassembled instructions to
make things easier for decompilation.
"""
import decompyle3.scanners.scanner37 as scan
import uncompyle6.scanners.scanner37 as scan
# bytecode verification, verify(), uses JUMP_OPS from here
from xdis.opcodes import opcode_37pypy as opc # is this right?

View File

@@ -32,8 +32,9 @@ JUMP_OPs = opc.JUMP_OPS
class Scanner37(Scanner37Base):
def __init__(self, show_asm=None):
def __init__(self, show_asm=None, is_pypy: bool=False):
Scanner37Base.__init__(self, (3, 7), show_asm)
self.is_pypy = is_pypy
return
pass
@@ -56,7 +57,7 @@ class Scanner37(Scanner37Base):
pass
elif t.op == self.opc.BUILD_MAP_UNPACK_WITH_CALL:
t.kind = "BUILD_MAP_UNPACK_WITH_CALL_%d" % t.attr
elif t.op == self.opc.BUILD_TUPLE_UNPACK_WITH_CALL:
elif not self.is_pypy and t.op == self.opc.BUILD_TUPLE_UNPACK_WITH_CALL:
t.kind = "BUILD_TUPLE_UNPACK_WITH_CALL_%d" % t.attr
pass
return tokens, customize

View File

@@ -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"),

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2019-2021 by Rocky Bernstein
# Copyright (c) 2019-2022 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
@@ -19,10 +19,12 @@ import re
from uncompyle6.semantics.consts import (
PRECEDENCE,
TABLE_DIRECT,
maxint,
INDENT_PER_LEVEL,
)
from uncompyle6.semantics.helper import flatten_list
#######################
def customize_for_version37(self, version):
########################
# Python 3.7+ changes
@@ -40,7 +42,9 @@ def customize_for_version37(self, version):
PRECEDENCE["formatted_value1"] = 100
PRECEDENCE["if_exp_37a"] = 28
PRECEDENCE["if_exp_37b"] = 28
PRECEDENCE["dict_unpack"] = 0 # **{...}
# fmt: on
TABLE_DIRECT.update(
{
"and_not": ("%c and not %c", (0, "expr"), (2, "expr")),
@@ -84,42 +88,45 @@ def customize_for_version37(self, version):
# nested await expressions like:
# return await (await bar())
# need parenthesis.
# Note there are async dictionary expressions are like await expr's
# the below is just the default fersion
"await_expr": ("await %p", (0, PRECEDENCE["await_expr"]-1)),
"await_stmt": ("%|%c\n", 0),
"c_async_with_stmt": ("%|async with %c:\n%+%c%-", (0, "expr"), 3),
"call_ex": ("%c(%p)", (0, "expr"), (1, 100)),
"compare_chained1a_37": (
' %[3]{pattr.replace("-", " ")} %p %p',
(0, 19),
(-4, 19),
(0, PRECEDENCE["compare"] - 1),
(-4, PRECEDENCE["compare"] - 1),
),
"compare_chained1_false_37": (
' %[3]{pattr.replace("-", " ")} %p %p',
(0, 19),
(-4, 19),
(0, PRECEDENCE["compare"] - 1),
(-4, PRECEDENCE["compare"] - 1),
),
"compare_chained2_false_37": (
' %[3]{pattr.replace("-", " ")} %p %p',
(0, 19),
(-5, 19),
(0, PRECEDENCE["compare"] - 1),
(-5, PRECEDENCE["compare"] - 1),
),
"compare_chained1b_false_37": (
' %[3]{pattr.replace("-", " ")} %p %p',
(0, 19),
(-4, 19),
(0, PRECEDENCE["compare"] - 1),
(-4, PRECEDENCE["compare"] - 1),
),
"compare_chained1c_37": (
' %[3]{pattr.replace("-", " ")} %p %p',
(0, 19),
(-2, 19),
(0, PRECEDENCE["compare"] - 1),
(-2, PRECEDENCE["compare"] - 1),
),
"compare_chained2a_37": ('%[1]{pattr.replace("-", " ")} %p', (0, 19)),
"compare_chained2b_false_37": ('%[1]{pattr.replace("-", " ")} %p', (0, 19)),
"compare_chained2a_false_37": ('%[1]{pattr.replace("-", " ")} %p', (0, 19)),
"compare_chained2a_37": ('%[1]{pattr.replace("-", " ")} %p', (0, PRECEDENCE["compare"] - 1)),
"compare_chained2b_false_37": ('%[1]{pattr.replace("-", " ")} %p', (0, PRECEDENCE["compare"] - 1)),
"compare_chained2a_false_37": ('%[1]{pattr.replace("-", " ")} %p', (0, PRECEDENCE["compare"] - 1)),
"compare_chained2c_37": (
'%[3]{pattr.replace("-", " ")} %p %p',
(0, 19),
(6, 19),
(0, PRECEDENCE["compare"] - 1),
(6, PRECEDENCE["compare"] - 1),
),
'if_exp37': ( '%p if %c else %c',
(1, 'expr', 27), 0, 3 ),
@@ -143,7 +150,6 @@ def customize_for_version37(self, version):
(3, 'importlist37') ),
"importattr37": ("%c", (0, "IMPORT_NAME_ATTR")),
"importlist37": ("%C", (0, maxint, ", ")),
"list_afor": (
" async for %[1]{%c} in %c%[1]{%c}",
@@ -195,6 +201,18 @@ def customize_for_version37(self, version):
self.n_assert_invert = n_assert_invert
def n_async_call(node):
self.f.write("async ")
node.kind == "call"
p = self.prec
self.prec = 80
self.template_engine(("%c(%P)", 0, (1, -4, ", ", 100)), node)
self.prec = p
node.kind == "async_call"
self.prune()
self.n_async_call = n_async_call
def n_attribute37(node):
expr = node[0]
assert expr == "expr"
@@ -209,6 +227,92 @@ def customize_for_version37(self, version):
self.n_attribute37 = n_attribute37
def n_build_list_unpack(node):
"""
prettyprint a list or tuple
"""
p = self.prec
self.prec = 100
lastnode = node.pop()
lastnodetype = lastnode.kind
# If this build list is inside a CALL_FUNCTION_VAR,
# then the first * has already been printed.
# Until I have a better way to check for CALL_FUNCTION_VAR,
# will assume that if the text ends in *.
last_was_star = self.f.getvalue().endswith("*")
if lastnodetype.startswith("BUILD_LIST"):
self.write("[")
endchar = "]"
flat_elems = flatten_list(node)
self.indent_more(INDENT_PER_LEVEL)
sep = ""
for elem in flat_elems:
if elem in ("ROT_THREE", "EXTENDED_ARG"):
continue
assert elem == "expr"
line_number = self.line_number
use_star = True
value = self.traverse(elem)
if value.startswith("("):
assert value.endswith(")")
use_star = False
value = value[1:-1].rstrip(
" "
) # Remove starting '(' and trailing ')' and additional spaces
if value == "":
pass
else:
if value.endswith(","): # if args has only one item
value = value[:-1]
if line_number != self.line_number:
sep += "\n" + self.indent + INDENT_PER_LEVEL[:-1]
else:
if sep != "":
sep += " "
if not last_was_star and use_star:
sep += "*"
pass
else:
last_was_star = False
self.write(sep, value)
sep = ","
self.write(endchar)
self.indent_less(INDENT_PER_LEVEL)
self.prec = p
self.prune()
return
self.n_build_list_unpack = n_build_list_unpack
def n_c_with(node):
if len(node) == 1 and node[0] == "with":
node = node[0]
else:
node.kind = "with"
self.default(node)
self.n_c_with = n_c_with
def n_c_except_suite(node):
node_len = len(node)
if node_len == 1 and node[0] in ("except_suite", "c_returns"):
node = node[0]
self.default(node)
elif node[1] in ("c_suite_stmts", "c_except_suite"):
node = node[1][0]
template = ("%+%c%-", 0)
self.template_engine(template, node)
self.prune()
self.n_c_except_suite = n_c_except_suite
self.n_c_with = n_c_with
def n_call(node):
p = self.prec
self.prec = 100
@@ -277,10 +381,7 @@ def customize_for_version37(self, version):
and opname == "CALL_FUNCTION_1"
or not re.match(r"\d", opname[-1])
):
if node[0][0] == "_lambda_body":
template = "(%c)(%p)"
else:
template = "%c(%p)"
template = "(%c)(%p)" if node[0][0] == "lambda_body" else "%c(%p)"
self.template_engine(
(template, (0, "expr"), (1, PRECEDENCE["yield"] - 1)), node
)

View File

@@ -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"),

View File

@@ -2553,7 +2553,16 @@ class SourceWalker(GenericASTTraversal, object):
else:
self.customize(customize)
self.text = self.traverse(ast, is_lambda=is_lambda)
self.println(self.text)
# In a formatted string using "lambda', we should not add "\n".
# For example in:
# f'{(lambda x:x)("8")!r}'
# Adding a "\n" after "lambda x: x" will give an error message:
# SyntaxError: f-string expression part cannot include a backslash
# So avoid that.
printfn = (
self.write if self.in_format_string and is_lambda else self.println
)
printfn(self.text)
self.name = old_name
self.return_none = rn
@@ -2710,7 +2719,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:
@@ -2812,9 +2822,8 @@ if __name__ == "__main__":
def deparse_test(co):
"This is a docstring"
s = deparse_code2str(co, debug_opts={"asm": "after", "tree": True})
# s = deparse_code2str(co, showasm=None, showast=False,
# showgrammar=True)
s = deparse_code2str(co)
# s = deparse_code2str(co, debug_opts={"asm": "after", "tree": {'before': False, 'after': False}})
print(s)
return