You've already forked python-uncompyle6
mirror of
https://github.com/rocky/python-uncompyle6.git
synced 2025-08-03 00:45:53 +08:00
Merge branch 'master' into python-3.3-to-3.5
This commit is contained in:
26
.github/ISSUE_TEMPLATE/bug-report.md
vendored
26
.github/ISSUE_TEMPLATE/bug-report.md
vendored
@@ -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._
|
||||
|
||||
|
@@ -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.
|
||||
|
@@ -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'
|
||||
|
@@ -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"
|
||||
|
@@ -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.
BIN
test/bytecode_3.8_run/02_named_expr.pyc
Normal file
BIN
test/bytecode_3.8_run/02_named_expr.pyc
Normal file
Binary file not shown.
Binary file not shown.
@@ -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.
|
||||
|
@@ -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}"
|
||||
|
14
test/simple_source/bug38/02_named_expr.py
Normal file
14
test/simple_source/bug38/02_named_expr.py
Normal 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
|
@@ -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
|
||||
|
@@ -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(
|
||||
[
|
||||
|
@@ -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?
|
||||
|
@@ -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
|
||||
|
@@ -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"),
|
||||
|
@@ -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
|
||||
)
|
||||
|
@@ -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"),
|
||||
|
@@ -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
|
||||
|
||||
|
Reference in New Issue
Block a user