Merging in recent 3.7 and 3.8 improvements from decompyle6

This rebases 3.7, 3.8 ...decompilation off of 3.7ish rather than a 3.4
base.

Add more 3.7 and 3.8 tests
This commit is contained in:
rocky
2019-12-08 13:12:05 -05:00
parent 301464d646
commit c6ddefcef5
30 changed files with 3971 additions and 97 deletions

View File

@@ -20,8 +20,12 @@ def test_grammar():
(lhs, rhs, tokens, right_recursive, dup_rhs) = p.check_sets()
# We have custom rules that create the below
expect_lhs = set(["pos_arg", "attribute"])
expect_lhs = set(["pos_arg"])
if PYTHON_VERSION < 3.8:
if PYTHON_VERSION < 3.7:
expect_lhs.add("attribute")
expect_lhs.add("get_iter")
else:
expect_lhs.add("async_with_as_stmt")
@@ -31,7 +35,7 @@ def test_grammar():
expect_right_recursive = set([("designList", ("store", "DUP_TOP", "designList"))])
if PYTHON_VERSION < 3.7:
if PYTHON_VERSION <= 3.7:
unused_rhs.add("call")
if PYTHON_VERSION > 2.6:
@@ -50,9 +54,11 @@ def test_grammar():
)
)
if PYTHON_VERSION >= 3.0:
if PYTHON_VERSION < 3.7:
expect_lhs.add("annotate_arg")
expect_lhs.add("annotate_tuple")
unused_rhs.add("mkfunc_annotate")
unused_rhs.add("dict_comp")
unused_rhs.add("classdefdeco1")
unused_rhs.add("tryelsestmtl")

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.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,24 @@
# From decompyle3/semantics/customize3.py
# The bug is handling "for" loop inside the
# chained compare ifelse
def n_classdef3(a, b, c, l):
r = 1
if 3.0 <= a <= 3.2:
for n in l:
if b:
break
elif c:
r = 2
pass
pass
else:
r = 3
pass
return r
assert n_classdef3(10, True, True, []) == 3
assert n_classdef3(0, False, True, []) == 3
assert n_classdef3(3.1, True, True, []) == 1
assert n_classdef3(3.1, True, False, [1]) == 1
assert n_classdef3(3.1, True, True, [2]) == 1
assert n_classdef3(3.1, False, True, [3]) == 2

View File

@@ -0,0 +1,5 @@
(x, y) = "foo", 0
if x := __name__:
y = 1
assert x == "__main__", "Walrus operator changes value"
assert y == 1, "Walrus operator branch taken"

View File

@@ -0,0 +1,17 @@
# from mult_by_const/instruction.py
# Bug in 3.8 was handling no JUMP_BACK in "for" loop. It is
# in the "if" instead
def instruction_sequence_value(instrs, a, b):
for instr in instrs:
if a:
a = 6
elif b:
return 0
pass
return a
assert instruction_sequence_value([], True, True) == 1
assert instruction_sequence_value([1], True, True) == 6
assert instruction_sequence_value([1], False, True) == 0
assert instruction_sequence_value([1], False, False) == False

View File

@@ -0,0 +1,53 @@
# Covers a large number of operators
#
# This code is RUNNABLE!
import sys
PYTHON_VERSION = sys.version_info[0] + (sys.version_info[1] / 10.0)
assert PYTHON_VERSION >= 3.7
# some floats (from 01_float.py)
x = 1e300
assert 0.0 == x * 0
assert x * 1e300 == float("inf")
assert str(float("inf") * 0.0) == "nan"
assert str(float("-inf") * 0.0) == "nan"
assert -1e300 * 1e300 == float("-inf")
# Complex (adapted from 02_complex.py)
y = 5j
assert y ** 2 == -25
y **= 3
assert y == (-0-125j)
# Tests BINARY_TRUE_DIVIDE and INPLACE_TRUE_DIVIDE (from 02_try_divide.py)
x = 2
assert 4 / x == 2
x = 5
assert x / 2 == 2.5
x = 3
x /= 2
assert x == 1.5
x = 2
assert 4 // x == 2
x = 7
x //= 2
assert x == 3
x = 3
assert x % 2 == 1
x %= 2
assert x == 1
assert x << 2 == 4
x <<= 3
assert x == 8
assert x >> 1 == 4
x >>= 1
assert x == 4

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -22,6 +22,12 @@ from spark_parser import DEFAULT_DEBUG as PARSER_DEFAULT_DEBUG
from uncompyle6.parsers.parse37 import Python37Parser
class Python38Parser(Python37Parser):
def p_38walrus(self, args):
"""
# named_expr is also known as the "walrus op" :=
expr ::= named_expr
named_expr ::= expr DUP_TOP store
"""
def p_38misc(self, args):
"""
@@ -38,6 +44,12 @@ class Python38Parser(Python37Parser):
stmt ::= whilestmt38
stmt ::= whileTruestmt38
stmt ::= call
stmt ::= ifstmtl
break ::= POP_BLOCK BREAK_LOOP
break ::= POP_BLOCK POP_TOP BREAK_LOOP
break ::= POP_TOP BREAK_LOOP
break ::= POP_EXCEPT BREAK_LOOP
# FIXME: this should be restricted to being inside a try block
stmt ::= except_ret38
@@ -89,27 +101,39 @@ class Python38Parser(Python37Parser):
return ::= ret_expr ROT_TWO POP_TOP RETURN_VALUE
# 3.8 can push a looping JUMP_BACK into into a JUMP_ from a statement that jumps to it
lastl_stmt ::= ifpoplaststmtl
ifpoplaststmtl ::= testexpr POP_TOP c_stmts_opt JUMP_BACK
ifelsestmtl ::= testexpr c_stmts_opt jb_cfs else_suitel JUMP_BACK come_froms
_ifstmts_jumpl ::= c_stmts JUMP_BACK
_ifstmts_jumpl ::= _ifstmts_jump
ifstmtl ::= testexpr _ifstmts_jumpl
for38 ::= expr get_iter store for_block JUMP_BACK
for38 ::= expr for_iter store for_block JUMP_BACK
for38 ::= expr for_iter store for_block JUMP_BACK POP_BLOCK
for38 ::= expr for_iter store for_block
for38 ::= expr get_for_iter store for_block JUMP_BACK
for38 ::= expr get_for_iter store for_block JUMP_BACK POP_BLOCK
for38 ::= expr get_for_iter store for_block
forelsestmt38 ::= expr for_iter store for_block POP_BLOCK else_suite
forelselaststmt38 ::= expr for_iter store for_block POP_BLOCK else_suitec
forelselaststmtl38 ::= expr for_iter store for_block POP_BLOCK else_suitel
forelsestmt38 ::= expr get_for_iter store for_block POP_BLOCK else_suite
forelselaststmt38 ::= expr get_for_iter store for_block POP_BLOCK else_suitec
forelselaststmtl38 ::= expr get_for_iter store for_block POP_BLOCK else_suitel
whilestmt38 ::= testexpr l_stmts_opt COME_FROM JUMP_BACK POP_BLOCK
whilestmt38 ::= testexpr l_stmts_opt JUMP_BACK POP_BLOCK
whilestmt38 ::= testexpr returns POP_BLOCK
whilestmt38 ::= testexpr l_stmts JUMP_BACK
whilestmt38 ::= _come_froms testexpr l_stmts_opt COME_FROM JUMP_BACK POP_BLOCK
whilestmt38 ::= _come_froms testexpr l_stmts_opt JUMP_BACK POP_BLOCK
whilestmt38 ::= _come_froms testexpr l_stmts_opt JUMP_BACK come_froms
whilestmt38 ::= _come_froms testexpr returns POP_BLOCK
whilestmt38 ::= _come_froms testexpr l_stmts JUMP_BACK
whilestmt38 ::= _come_froms testexpr l_stmts come_froms
# while1elsestmt ::= l_stmts JUMP_BACK
whileTruestmt ::= l_stmts JUMP_BACK POP_BLOCK
while1stmt ::= l_stmts COME_FROM_LOOP
while1stmt ::= l_stmts COME_FROM JUMP_BACK COME_FROM_LOOP
whileTruestmt38 ::= l_stmts JUMP_BACK
whileTruestmt ::= _come_froms l_stmts JUMP_BACK POP_BLOCK
while1stmt ::= _come_froms l_stmts COME_FROM_LOOP
while1stmt ::= _come_froms l_stmts COME_FROM JUMP_BACK COME_FROM_LOOP
whileTruestmt38 ::= _come_froms l_stmts JUMP_BACK
whileTruestmt38 ::= _come_froms l_stmts JUMP_BACK COME_FROM_EXCEPT_CLAUSE
for_block ::= l_stmts_opt _come_from_loops JUMP_BACK
for_block ::= _come_froms l_stmts_opt _come_from_loops JUMP_BACK
except_cond1 ::= DUP_TOP expr COMPARE_OP jmp_false
POP_TOP POP_TOP POP_TOP
@@ -134,7 +158,8 @@ class Python38Parser(Python37Parser):
except_ret38a ::= COME_FROM_FINALLY POP_TOP POP_TOP POP_TOP
expr ROT_FOUR
POP_EXCEPT RETURN_VALUE END_FINALLY
except_handler38 ::= JUMP_FORWARD COME_FROM_FINALLY
except_handler38 ::= _jump COME_FROM_FINALLY
except_stmts END_FINALLY opt_come_from_except
except_handler38a ::= COME_FROM_FINALLY POP_TOP POP_TOP POP_TOP
POP_EXCEPT POP_TOP stmts END_FINALLY
@@ -160,13 +185,15 @@ class Python38Parser(Python37Parser):
self.customized = {}
def remove_rules_38(self):
self.remove_rules("""
self.remove_rules(
"""
stmt ::= async_for_stmt37
stmt ::= for
stmt ::= forelsestmt
stmt ::= try_except36
stmt ::= async_forelse_stmt
async_for_stmt ::= SETUP_LOOP expr
async_for_stmt ::= setup_loop expr
GET_AITER
SETUP_EXCEPT GET_ANEXT LOAD_CONST
YIELD_FROM
@@ -178,7 +205,7 @@ class Python38Parser(Python37Parser):
COME_FROM
POP_TOP POP_TOP POP_TOP POP_EXCEPT POP_TOP POP_BLOCK
COME_FROM_LOOP
async_for_stmt37 ::= SETUP_LOOP expr
async_for_stmt37 ::= setup_loop expr
GET_AITER
SETUP_EXCEPT GET_ANEXT
LOAD_CONST YIELD_FROM
@@ -190,7 +217,7 @@ class Python38Parser(Python37Parser):
POP_TOP POP_BLOCK
COME_FROM_LOOP
async_forelse_stmt ::= SETUP_LOOP expr
async_forelse_stmt ::= setup_loop expr
GET_AITER
SETUP_EXCEPT GET_ANEXT LOAD_CONST
YIELD_FROM
@@ -203,13 +230,13 @@ class Python38Parser(Python37Parser):
POP_TOP POP_TOP POP_TOP POP_EXCEPT POP_TOP POP_BLOCK
else_suite COME_FROM_LOOP
for ::= SETUP_LOOP expr for_iter store for_block POP_BLOCK
for ::= SETUP_LOOP expr for_iter store for_block POP_BLOCK NOP
for ::= setup_loop expr get_for_iter store for_block POP_BLOCK
for ::= setup_loop expr get_for_iter store for_block POP_BLOCK NOP
for_block ::= l_stmts_opt COME_FROM_LOOP JUMP_BACK
forelsestmt ::= SETUP_LOOP expr for_iter store for_block POP_BLOCK else_suite
forelselaststmt ::= SETUP_LOOP expr for_iter store for_block POP_BLOCK else_suitec
forelselaststmtl ::= SETUP_LOOP expr for_iter store for_block POP_BLOCK else_suitel
forelsestmt ::= setup_loop expr get_for_iter store for_block POP_BLOCK else_suite
forelselaststmt ::= setup_loop expr get_for_iter store for_block POP_BLOCK else_suitec
forelselaststmtl ::= setup_loop expr get_for_iter store for_block POP_BLOCK else_suitel
tryelsestmtl3 ::= SETUP_EXCEPT suite_stmts_opt POP_BLOCK
except_handler COME_FROM else_suitel
@@ -223,15 +250,16 @@ class Python38Parser(Python37Parser):
COME_FROM_FINALLY suite_stmts_opt END_FINALLY
tryfinally_return_stmt ::= SETUP_FINALLY suite_stmts_opt POP_BLOCK
LOAD_CONST COME_FROM_FINALLY
""")
"""
)
def customize_grammar_rules(self, tokens, customize):
super(Python37Parser, self).customize_grammar_rules(tokens, customize)
self.remove_rules_38()
self.check_reduce['ifstmt'] = 'tokens'
self.check_reduce['whileTruestmt38'] = 'tokens'
self.check_reduce["ifstmt"] = "tokens"
self.check_reduce["whileTruestmt38"] = "tokens"
self.check_reduce["whilestmt38"] = "tokens"
self.check_reduce["ifstmtl"] = "tokens"
def reduce_is_invalid(self, rule, ast, tokens, first, last):
invalid = super(Python38Parser,
@@ -240,34 +268,47 @@ class Python38Parser(Python37Parser):
self.remove_rules_38()
if invalid:
return invalid
if rule[0] == 'ifstmt':
lhs = rule[0]
if lhs == "ifstmt":
# Make sure jumps don't extend beyond the end of the if statement.
l = last
if l == len(tokens):
l -= 1
if isinstance(tokens[l].offset, str):
last_offset = int(tokens[l].offset.split('_')[0], 10)
last_offset = int(tokens[l].offset.split("_")[0], 10)
else:
last_offset = tokens[l].offset
for i in range(first, l):
t = tokens[i]
if t.kind == 'POP_JUMP_IF_FALSE':
if t.kind == "POP_JUMP_IF_FALSE":
if t.attr > last_offset:
return True
pass
pass
pass
elif rule[0] == 'whileTruestmt38':
t = tokens[last-1]
if t.kind == 'JUMP_BACK':
return t.attr != tokens[first].offset
elif lhs == "ifstmtl":
if last == len(tokens):
last -= 1
if (tokens[last].attr and isinstance(tokens[last].attr, int)):
return tokens[first].offset < tokens[last].attr
pass
elif lhs in ("whileTruestmt38", "whilestmt38"):
jb_index = last - 1
while jb_index > 0 and tokens[jb_index].kind.startswith("COME_FROM"):
jb_index -= 1
t = tokens[jb_index]
if t.kind != "JUMP_BACK":
return True
return t.attr != tokens[first].off2int()
pass
return False
class Python38ParserSingle(Python38Parser, PythonParserSingle):
pass
if __name__ == "__main__":
# Check grammar
# FIXME: DRY this with other parseXX.py routines
@@ -286,7 +327,9 @@ if __name__ == "__main__":
"""JUMP_BACK CONTINUE RETURN_END_IF COME_FROM
LOAD_GENEXPR LOAD_ASSERT LOAD_SETCOMP LOAD_DICTCOMP LOAD_CLASSNAME
LAMBDA_MARKER RETURN_LAST
""".split()))
""".split()
)
)
remain_tokens = set(tokens) - opcode_set
import re

View File

@@ -22,24 +22,50 @@ This sets up opcodes Python's 3.7 and calls a generalized
scanner routine for Python 3.
"""
from uncompyle6.scanners.scanner36 import Scanner36
from uncompyle6.scanners.scanner3 import Scanner3
from uncompyle6.scanners.scanner37base import Scanner37Base
# bytecode verification, verify(), uses JUMP_OPs from here
from xdis.opcodes import opcode_37 as opc
# bytecode verification, verify(), uses JUMP_OPS from here
JUMP_OPs = opc.JUMP_OPS
class Scanner37(Scanner36):
class Scanner37(Scanner37Base):
def __init__(self, show_asm=None):
Scanner3.__init__(self, 3.7, show_asm)
Scanner37Base.__init__(self, 3.7, show_asm)
return
pass
def ingest(self, co, classname=None, code_objects={}, show_asm=None):
tokens, customize = Scanner37Base.ingest(self, co, classname, code_objects, show_asm)
for t in tokens:
# The lowest bit of flags indicates whether the
# var-keyword argument is placed at the top of the stack
if t.op == self.opc.CALL_FUNCTION_EX and t.attr & 1:
t.kind = "CALL_FUNCTION_EX_KW"
pass
elif t.op == self.opc.BUILD_STRING:
t.kind = "BUILD_STRING_%s" % t.attr
elif t.op == self.opc.CALL_FUNCTION_KW:
t.kind = "CALL_FUNCTION_KW_%s" % t.attr
elif t.op == self.opc.FORMAT_VALUE:
if t.attr & 0x4:
t.kind = "FORMAT_VALUE_ATTR"
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:
t.kind = "BUILD_TUPLE_UNPACK_WITH_CALL_%d" % t.attr
pass
return tokens, customize
if __name__ == "__main__":
from uncompyle6 import PYTHON_VERSION
if PYTHON_VERSION == 3.7:
import inspect
co = inspect.currentframe().f_code
tokens, customize = Scanner37().ingest(co)
for t in tokens:

File diff suppressed because it is too large Load Diff

View File

@@ -23,23 +23,63 @@ scanner routine for Python 3.
"""
from uncompyle6.scanners.scanner37 import Scanner37
from uncompyle6.scanners.scanner3 import Scanner3
from uncompyle6.scanners.scanner37base import Scanner37Base
# bytecode verification, verify(), uses JUMP_OPs from here
from xdis.opcodes import opcode_38 as opc
# bytecode verification, verify(), uses JUMP_OPS from here
JUMP_OPs = opc.JUMP_OPS
class Scanner38(Scanner37):
class Scanner38(Scanner37):
def __init__(self, show_asm=None):
Scanner3.__init__(self, 3.8, show_asm)
Scanner37Base.__init__(self, 3.8, show_asm)
return
pass
def ingest(self, co, classname=None, code_objects={}, show_asm=None):
tokens, customize = super(Scanner38, self).ingest(
co, classname, code_objects, show_asm
)
for i, token in enumerate(tokens):
opname = token.kind
if opname in ("JUMP_FORWARD", "JUMP_ABSOLUTE"):
# Turn JUMPs into BREAK_LOOP
jump_target = token.attr
if opname == "JUMP_ABSOLUTE" and token.offset >= jump_target:
# Not a forward jump, so continue
# FIXME: Do we need "continue" detection?
continue
if i + 1 < len(tokens) and tokens[i + 1] == "JUMP_BACK":
# Sometimes the jump back is *after* the break...
jump_back_index = i + 1
else:
# and sometimes it is *before* where we jumped to.
jump_back_index = self.offset2tok_index[jump_target] - 1
while tokens[jump_back_index].kind.startswith("COME_FROM_"):
jump_back_index -= 1
pass
pass
jump_back_token = tokens[jump_back_index]
if (
jump_back_token == "JUMP_BACK"
and jump_back_token.attr < token.offset
):
token.kind = "BREAK_LOOP"
pass
pass
return tokens, customize
if __name__ == "__main__":
from uncompyle6 import PYTHON_VERSION
from decompyle3 import PYTHON_VERSION
if PYTHON_VERSION == 3.8:
import inspect
co = inspect.currentframe().f_code
tokens, customize = Scanner38().ingest(co)
for t in tokens:

View File

@@ -159,5 +159,12 @@ class Token:
def __getitem__(self, i):
raise IndexError
def off2int(self):
if isinstance(self.offset, int):
return self.offset
else:
assert isinstance(self.offset, str)
return(int(self.offset.split("_")[0]))
NoneToken = Token("LOAD_CONST", offset=-1, attr=None, pattr=None)

View File

@@ -344,7 +344,11 @@ TABLE_DIRECT = {
# 'return': ( '%|return %c\n', 0),
'return_if_stmt': ( 'return %c\n', 0),
'ifstmt': ( '%|if %c:\n%+%c%-', 0, 1 ),
'ifstmt': ( '%|if %c:\n%+%c%-',
0, # "testexpr" or "testexpr_then"
1, # "_ifstmts_jump" or "return_stmts"
),
'iflaststmt': ( '%|if %c:\n%+%c%-', 0, 1 ),
'iflaststmtl': ( '%|if %c:\n%+%c%-', 0, 1 ),
'testtrue': ( 'not %p',
@@ -359,18 +363,18 @@ TABLE_DIRECT = {
'ifelsestmt': ( '%|if %c:\n%+%c%-%|else:\n%+%c%-', 0, 1, 3 ),
'ifelsestmtc': ( '%|if %c:\n%+%c%-%|else:\n%+%c%-', 0, 1, 3 ),
'ifelsestmtl': ( '%|if %c:\n%+%c%-%|else:\n%+%c%-', 0, 1, 3 ),
'ifelsestmtr': ( '%|if %c:\n%+%c%-%|else:\n%+%c%-', 0, 1, 2 ),
'ifelsestmtr2': ( '%|if %c:\n%+%c%-%|else:\n%+%c%-\n\n', 0, 1, 3 ), # has COME_FROM in position 2
# "elif" forms are not generated by the parser but are created through tree
# transformations. See "n_ifelsestmt".
'ifelifstmt': ( '%|if %c:\n%+%c%-%c', 0, 1, 3 ),
# These are created only via transformation
'ifelifstmt': ( '%|if %c:\n%+%c%-%c',
0, # "testexpr" or "testexpr_then"
1, 3 ),
'elifelifstmt': ( '%|elif %c:\n%+%c%-%c', 0, 1, 3 ),
'elifstmt': ( '%|elif %c:\n%+%c%-', 0, 1 ),
'elifelsestmt': ( '%|elif %c:\n%+%c%-%|else:\n%+%c%-', 0, 1, 3 ),
'ifelsestmtr': ( '%|if %c:\n%+%c%-%|else:\n%+%c%-', 0, 1, 2 ),
'ifelsestmtr2': ( '%|if %c:\n%+%c%-%|else:\n%+%c%-\n\n', 0, 1, 3 ), # has COME_FROM
'elifelsestmtr': ( '%|elif %c:\n%+%c%-%|else:\n%+%c%-\n\n', 0, 1, 2 ),
'elifelsestmtr2': ( '%|elif %c:\n%+%c%-%|else:\n%+%c%-\n\n', 0, 1, 3 ), # has COME_FROM in position 2
'elifelsestmtr2': ( '%|elif %c:\n%+%c%-%|else:\n%+%c%-\n\n', 0, 1, 3 ), # has COME_FROM
'whileTruestmt': ( '%|while True:\n%+%c%-\n\n', 1 ),
'whilestmt': ( '%|while %c:\n%+%c%-\n\n', 1, 2 ),
@@ -414,10 +418,11 @@ TABLE_DIRECT = {
(1, 'expr'), (5, 'store') ),
'except_suite': ( '%+%c%-%C', 0, (1, maxint, '') ),
# In Python 3.6, this is more complicated in the presence of "returns"
# In Python 3.6+, this is more complicated in the presence of "returns"
'except_suite_finalize': ( '%+%c%-%C', 1, (3, maxint, '') ),
'pass': ( '%|pass\n', ),
'STORE_FAST': ( '%{pattr}', ),
'kv': ( '%c: %c', 3, 1 ),
'kv2': ( '%c: %c', 1, 2 ),
'import': ( '%|import %c\n', 2),

View File

@@ -37,6 +37,7 @@ def customize_for_version3(self, version):
(0, "expr"),
(4, "expr"),
),
"except_cond2": ("%|except %c as %c:\n", (1, "expr"), (5, "store")),
"function_def_annotate": ("\n\n%|def %c%c\n", -1, 0),
# When a generator is a single parameter of a function,
# it doesn't need the surrounding parenethesis.
@@ -331,7 +332,8 @@ def customize_for_version3(self, version):
(1, "suite_stmts_opt"),
(3, "except_handler"),
(5, "else_suitel"),
)
),
"LOAD_CLASSDEREF": ("%{pattr}",),
}
)
if version >= 3.4:

View File

@@ -12,7 +12,7 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Isolate Python 3.6 version-specific semantic actions here.
"""Isolate Python 3.8 version-specific semantic actions here.
"""
########################
@@ -80,10 +80,18 @@ def customize_for_version38(self, version):
(0, 'expr'),
(3, 'for_block'), -2 ),
'ifpoplaststmtl': ( '%|if %c:\n%+%c%-',
(0, "testexpr"),
(2, "c_stmts" ) ),
'ifstmtl': ( '%|if %c:\n%+%c%-',
(0, "testexpr"),
(1, "_ifstmts_jumpl") ),
'whilestmt38': ( '%|while %c:\n%+%c%-\n\n',
(0, 'testexpr'), (1, 'l_stmts') ),
(1, 'testexpr'), (2, 'l_stmts') ),
'whileTruestmt38': ( '%|while True:\n%+%c%-\n\n',
(0, 'l_stmts') ),
(1, 'l_stmts') ),
'try_elsestmtl38': (
'%|try:\n%+%c%-%c%|else:\n%+%c%-',
(1, 'suite_stmts_opt'),
@@ -98,4 +106,7 @@ def customize_for_version38(self, version):
'tryfinally38': (
'%|try:\n%+%c%-%|finally:\n%+%c%-\n\n',
(3, 'returns'), 6 ),
"named_expr": ( # AKA "walrus operatotr"
"%c := %c", (2, "store"), (0, "expr")
)
})

View File

@@ -1130,11 +1130,15 @@ class SourceWalker(GenericASTTraversal, object):
def n_generator_exp(self, node):
self.write("(")
iter_index = 3
if self.version > 3.2:
code_index = -6
if self.version > 3.6:
# Python 3.7+ adds optional "come_froms" at node[0]
iter_index = 4
else:
code_index = -5
self.comprehension_walk(node, iter_index=3, code_index=code_index)
self.comprehension_walk(node, iter_index=iter_index, code_index=code_index)
self.write(")")
self.prune()
@@ -2015,8 +2019,13 @@ class SourceWalker(GenericASTTraversal, object):
self.default(node)
def n_except_cond2(self, node):
if node[-2][0] == "unpack":
node[-2][0].kind = "unpack_w_parens"
if node[-1] == "come_from_opt":
unpack_node = -3
else:
unpack_node = -2
if node[unpack_node][0] == "unpack":
node[unpack_node][0].kind = "unpack_w_parens"
self.default(node)
def template_engine(self, entry, startnode):

View File

@@ -181,6 +181,7 @@ class TreeTransform(GenericASTTraversal, object):
n = else_suite[0]
old_stmts = None
else_suite_index = 1
if len(n) == 1 == len(n[0]) and n[0] == "stmt":
n = n[0][0]
@@ -192,9 +193,12 @@ class TreeTransform(GenericASTTraversal, object):
"iflaststmtl",
"ifelsestmtl",
"ifelsestmtc",
"ifpoplaststmtl",
):
# This seems needed for Python 2.5-2.7
n = n[0]
if n.kind == "ifpoplaststmtl":
old_stmts = n[2]
else_suite_index = 2
pass
pass
elif len(n) > 1 and 1 == len(n[0]) and n[0] == "stmt" and n[1].kind == "stmt":
@@ -206,7 +210,7 @@ class TreeTransform(GenericASTTraversal, object):
else:
return node
if n.kind in ("ifstmt", "iflaststmt", "iflaststmtl"):
if n.kind in ("ifstmt", "iflaststmt", "iflaststmtl", "ifpoplaststmtl"):
node.kind = "ifelifstmt"
n.kind = "elifstmt"
elif n.kind in ("ifelsestmtr",):
@@ -223,17 +227,24 @@ class TreeTransform(GenericASTTraversal, object):
if old_stmts:
if n.kind == "elifstmt":
trailing_else = SyntaxTree("stmts", old_stmts[1:])
if len(trailing_else):
# We use elifelsestmtr because it has 3 nodes
elifelse_stmt = SyntaxTree(
"elifelsestmtr", [n[0], n[1], trailing_else]
"elifelsestmtr", [n[0], n[else_suite_index], trailing_else]
)
node[3] = elifelse_stmt
else:
elif_stmt = SyntaxTree(
"elifstmt", [n[0], n[else_suite_index]]
)
node[3] = elif_stmt
node.transformed_by = "n_ifelsestmt"
pass
else:
# Other cases for n.kind may happen here
pass
pass
node.transformed_by = "n_ifelsestmt"
return node
n_ifelsestmtc = n_ifelsestmtl = n_ifelsestmt