Start rolling in LOAD_ARG for 3.7+

This commit is contained in:
rocky
2022-05-05 21:21:59 -04:00
parent 8576117d00
commit f5043408ec
7 changed files with 323 additions and 62 deletions

View File

@@ -347,7 +347,7 @@ class Python36Parser(Python35Parser):
func_async_middle ::= POP_BLOCK JUMP_FORWARD COME_FROM_EXCEPT func_async_middle ::= POP_BLOCK JUMP_FORWARD COME_FROM_EXCEPT
DUP_TOP LOAD_GLOBAL COMPARE_OP POP_JUMP_IF_TRUE DUP_TOP LOAD_GLOBAL COMPARE_OP POP_JUMP_IF_TRUE
END_FINALLY COME_FROM END_FINALLY COME_FROM
genexpr_func_async ::= LOAD_FAST func_async_prefix genexpr_func_async ::= LOAD_ARG func_async_prefix
store func_async_middle comp_iter store func_async_middle comp_iter
JUMP_BACK COME_FROM JUMP_BACK COME_FROM
POP_TOP POP_TOP POP_TOP POP_EXCEPT POP_TOP POP_TOP POP_TOP POP_TOP POP_EXCEPT POP_TOP
@@ -363,7 +363,7 @@ class Python36Parser(Python35Parser):
store func_async_middle list_iter store func_async_middle list_iter
JUMP_BACK JUMP_BACK
POP_TOP POP_TOP POP_TOP POP_EXCEPT POP_TOP POP_TOP POP_TOP POP_TOP POP_EXCEPT POP_TOP
list_comp_async ::= BUILD_LIST_0 LOAD_FAST list_afor2 list_comp_async ::= BUILD_LIST_0 LOAD_ARG list_afor2
get_aiter ::= expr GET_AITER get_aiter ::= expr GET_AITER
list_afor ::= get_aiter list_afor2 list_afor ::= get_aiter list_afor2
list_iter ::= list_afor list_iter ::= list_afor

View File

@@ -138,7 +138,7 @@ class Python37Parser(Python37BaseParser):
returns ::= _stmts return returns ::= _stmts return
stmt ::= genexpr_func stmt ::= genexpr_func
genexpr_func ::= LOAD_FAST _come_froms FOR_ITER store comp_iter JUMP_BACK genexpr_func ::= LOAD_ARG _come_froms FOR_ITER store comp_iter JUMP_BACK
""" """
pass pass
@@ -714,8 +714,18 @@ class Python37Parser(Python37BaseParser):
list_comp ::= BUILD_LIST_0 list_iter list_comp ::= BUILD_LIST_0 list_iter
lc_body ::= expr LIST_APPEND lc_body ::= expr LIST_APPEND
list_for ::= expr for_iter store list_iter jb_or_c
list_for ::= expr_or_arg
for_iter
store list_iter
jb_or_c _come_froms
set_for ::= expr_or_arg
for_iter
store set_iter
jb_or_c _come_froms
list_if_not_end ::= pjump_ift _come_froms
# This is seen in PyPy, but possibly it appears on other Python 3? # This is seen in PyPy, but possibly it appears on other Python 3?
list_if ::= expr jmp_false list_iter COME_FROM list_if ::= expr jmp_false list_iter COME_FROM
list_if_not ::= expr jmp_true list_iter COME_FROM list_if_not ::= expr jmp_true list_iter COME_FROM
@@ -725,10 +735,10 @@ class Python37Parser(Python37BaseParser):
stmt ::= set_comp_func stmt ::= set_comp_func
set_comp_func ::= BUILD_SET_0 LOAD_FAST for_iter store comp_iter set_comp_func ::= BUILD_SET_0 LOAD_ARG for_iter store comp_iter
JUMP_BACK RETURN_VALUE RETURN_LAST JUMP_BACK RETURN_VALUE RETURN_LAST
set_comp_func ::= BUILD_SET_0 LOAD_FAST for_iter store comp_iter set_comp_func ::= BUILD_SET_0 LOAD_ARG for_iter store comp_iter
COME_FROM JUMP_BACK RETURN_VALUE RETURN_LAST COME_FROM JUMP_BACK RETURN_VALUE RETURN_LAST
comp_body ::= dict_comp_body comp_body ::= dict_comp_body
@@ -743,13 +753,16 @@ class Python37Parser(Python37BaseParser):
"""" """"
expr ::= dict_comp expr ::= dict_comp
stmt ::= dict_comp_func stmt ::= dict_comp_func
dict_comp_func ::= BUILD_MAP_0 LOAD_FAST for_iter store dict_comp_func ::= BUILD_MAP_0 LOAD_ARG for_iter store
comp_iter JUMP_BACK RETURN_VALUE RETURN_LAST comp_iter JUMP_BACK RETURN_VALUE RETURN_LAST
comp_iter ::= comp_if comp_iter ::= comp_if
comp_iter ::= comp_if_not comp_iter ::= comp_if_not
comp_if_not ::= expr jmp_true comp_iter comp_if_not ::= expr jmp_true comp_iter
comp_iter ::= comp_body comp_iter ::= comp_body
expr_or_arg ::= LOAD_ARG
expr_or_arg ::= expr
""" """
def p_expr3(self, args): def p_expr3(self, args):
@@ -1212,6 +1225,16 @@ class Python37Parser(Python37BaseParser):
super(Python37Parser, self).customize_grammar_rules(tokens, customize) super(Python37Parser, self).customize_grammar_rules(tokens, customize)
self.check_reduce["call_kw"] = "AST" self.check_reduce["call_kw"] = "AST"
# Opcode names in the custom_ops_processed set have rules that get added
# unconditionally and the rules are constant. So they need to be done
# only once and if we see the opcode a second we don't have to consider
# adding more rules.
#
# Note: BUILD_TUPLE_UNPACK_WITH_CALL gets considered by
# default because it starts with BUILD. So we'll set to ignore it from
# the start.
custom_ops_processed = set()
for i, token in enumerate(tokens): for i, token in enumerate(tokens):
opname = token.kind opname = token.kind
@@ -1312,6 +1335,211 @@ class Python37Parser(Python37BaseParser):
self.addRule(rule, nop_func) self.addRule(rule, nop_func)
rule = "starred ::= %s %s" % ("expr " * v, opname) rule = "starred ::= %s %s" % ("expr " * v, opname)
self.addRule(rule, nop_func) self.addRule(rule, nop_func)
elif opname == "GET_AITER":
self.add_unique_doc_rules("get_aiter ::= expr GET_AITER", customize)
if not {"MAKE_FUNCTION_0", "MAKE_FUNCTION_CLOSURE"} in self.seen_ops:
self.addRule(
"""
expr ::= dict_comp_async
expr ::= generator_exp_async
expr ::= list_comp_async
dict_comp_async ::= LOAD_DICTCOMP
LOAD_STR
MAKE_FUNCTION_0
get_aiter
CALL_FUNCTION_1
dict_comp_async ::= BUILD_MAP_0 LOAD_ARG
dict_comp_async
func_async_middle ::= POP_BLOCK JUMP_FORWARD COME_FROM_EXCEPT
DUP_TOP LOAD_GLOBAL COMPARE_OP POP_JUMP_IF_TRUE
END_FINALLY COME_FROM
func_async_prefix ::= _come_froms SETUP_EXCEPT GET_ANEXT LOAD_CONST YIELD_FROM
generator_exp_async ::= load_genexpr LOAD_STR MAKE_FUNCTION_0
get_aiter CALL_FUNCTION_1
genexpr_func_async ::= LOAD_ARG func_async_prefix
store func_async_middle comp_iter
JUMP_LOOP COME_FROM
POP_TOP POP_TOP POP_TOP POP_EXCEPT POP_TOP
# FIXME this is a workaround for probalby some bug in the Earley parser
# if we use get_aiter, then list_comp_async doesn't match, and I don't
# understand why.
expr_get_aiter ::= expr GET_AITER
list_afor ::= get_aiter list_afor2
list_afor2 ::= func_async_prefix
store func_async_middle list_iter
JUMP_LOOP COME_FROM
POP_TOP POP_TOP POP_TOP POP_EXCEPT POP_TOP
list_comp_async ::= BUILD_LIST_0 LOAD_ARG list_afor2
list_comp_async ::= LOAD_LISTCOMP LOAD_STR MAKE_FUNCTION_0
expr_get_aiter CALL_FUNCTION_1
GET_AWAITABLE LOAD_CONST
YIELD_FROM
list_iter ::= list_afor
set_comp_async ::= LOAD_SETCOMP
LOAD_STR
MAKE_FUNCTION_0
get_aiter
CALL_FUNCTION_1
set_comp_async ::= LOAD_CLOSURE
BUILD_TUPLE_1
LOAD_SETCOMP
LOAD_STR MAKE_FUNCTION_CLOSURE
get_aiter CALL_FUNCTION_1
await
""",
nop_func,
)
custom_ops_processed.add(opname)
self.addRule(
"""
dict_comp_async ::= BUILD_MAP_0 LOAD_ARG
dict_comp_async
expr ::= dict_comp_async
expr ::= generator_exp_async
expr ::= list_comp_async
expr ::= set_comp_async
func_async_middle ::= POP_BLOCK JUMP_FORWARD COME_FROM_EXCEPT
DUP_TOP LOAD_GLOBAL COMPARE_OP POP_JUMP_IF_TRUE
END_FINALLY _come_froms
# async_iter ::= block_break SETUP_EXCEPT GET_ANEXT LOAD_CONST YIELD_FROM
get_aiter ::= expr GET_AITER
list_afor ::= get_aiter list_afor2
list_comp_async ::= BUILD_LIST_0 LOAD_ARG list_afor2
list_iter ::= list_afor
set_afor ::= get_aiter set_afor2
set_iter ::= set_afor
set_iter ::= set_for
set_comp_async ::= BUILD_SET_0 LOAD_ARG
set_comp_async
""",
nop_func,
)
custom_ops_processed.add(opname)
elif opname == "GET_ANEXT":
self.addRule(
"""
expr ::= genexpr_func_async
expr ::= BUILD_MAP_0 genexpr_func_async
expr ::= list_comp_async
dict_comp_async ::= BUILD_MAP_0 genexpr_func_async
async_iter ::= _come_froms
SETUP_EXCEPT GET_ANEXT LOAD_CONST YIELD_FROM
store_async_iter_end ::= store
POP_BLOCK JUMP_FORWARD COME_FROM_EXCEPT
DUP_TOP LOAD_GLOBAL COMPARE_OP POP_JUMP_IF_TRUE
END_FINALLY COME_FROM
# We use store_async_iter_end to make comp_iter come out in the right position,
# (after the logical "store")
genexpr_func_async ::= LOAD_ARG async_iter
store_async_iter_end
comp_iter
JUMP_LOOP COME_FROM
POP_TOP POP_TOP POP_TOP POP_EXCEPT POP_TOP
list_afor2 ::= async_iter
store
list_iter
JUMP_LOOP
COME_FROM_FINALLY
END_ASYNC_FOR
list_comp_async ::= BUILD_LIST_0 LOAD_ARG list_afor2
set_afor2 ::= async_iter
store
func_async_middle
set_iter
JUMP_LOOP COME_FROM
POP_TOP POP_TOP POP_TOP POP_EXCEPT POP_TOP
set_afor2 ::= expr_or_arg
set_iter_async
set_comp_async ::= BUILD_SET_0 set_afor2
set_iter_async ::= async_iter
store
set_iter
JUMP_LOOP
_come_froms
END_ASYNC_FOR
return_expr_lambda ::= genexpr_func_async
LOAD_CONST RETURN_VALUE
RETURN_VALUE_LAMBDA
return_expr_lambda ::= BUILD_SET_0 genexpr_func_async
RETURN_VALUE_LAMBDA
""",
nop_func,
)
custom_ops_processed.add(opname)
elif opname == "GET_AWAITABLE":
rule_str = """
await_expr ::= expr GET_AWAITABLE LOAD_CONST YIELD_FROM
expr ::= await_expr
"""
self.add_unique_doc_rules(rule_str, customize)
elif opname == "GET_ITER":
self.addRule(
"""
expr ::= get_iter
get_iter ::= expr GET_ITER
""",
nop_func,
)
custom_ops_processed.add(opname)
elif opname == "LOAD_ASSERT":
if "PyPy" in customize:
rules_str = """
stmt ::= JUMP_IF_NOT_DEBUG stmts COME_FROM
"""
self.add_unique_doc_rules(rules_str, customize)
elif opname == "LOAD_ATTR":
self.addRule(
"""
expr ::= attribute
attribute ::= expr LOAD_ATTR
""",
nop_func,
)
custom_ops_processed.add(opname)
elif opname == "SETUP_WITH": elif opname == "SETUP_WITH":
rules_str = """ rules_str = """
with ::= expr SETUP_WITH POP_TOP suite_stmts_opt COME_FROM_WITH with ::= expr SETUP_WITH POP_TOP suite_stmts_opt COME_FROM_WITH

View File

@@ -367,7 +367,7 @@ class Python37BaseParser(PythonParser):
if opname == "BUILD_MAP_n": if opname == "BUILD_MAP_n":
# PyPy sometimes has no count. Sigh. # PyPy sometimes has no count. Sigh.
rule = ( rule = (
"dict_comp_func ::= BUILD_MAP_n LOAD_FAST for_iter store " "dict_comp_func ::= BUILD_MAP_n LOAD_ARG for_iter store "
"comp_iter JUMP_BACK RETURN_VALUE RETURN_LAST" "comp_iter JUMP_BACK RETURN_VALUE RETURN_LAST"
) )
self.add_unique_rule(rule, "dict_comp_func", 1, customize) self.add_unique_rule(rule, "dict_comp_func", 1, customize)
@@ -644,7 +644,7 @@ class Python37BaseParser(PythonParser):
func_async_middle ::= POP_BLOCK JUMP_FORWARD COME_FROM_EXCEPT func_async_middle ::= POP_BLOCK JUMP_FORWARD COME_FROM_EXCEPT
DUP_TOP LOAD_GLOBAL COMPARE_OP POP_JUMP_IF_TRUE DUP_TOP LOAD_GLOBAL COMPARE_OP POP_JUMP_IF_TRUE
END_FINALLY COME_FROM END_FINALLY COME_FROM
genexpr_func_async ::= LOAD_FAST func_async_prefix genexpr_func_async ::= LOAD_ARG func_async_prefix
store func_async_middle comp_iter store func_async_middle comp_iter
JUMP_BACK COME_FROM JUMP_BACK COME_FROM
POP_TOP POP_TOP POP_TOP POP_EXCEPT POP_TOP POP_TOP POP_TOP POP_TOP POP_EXCEPT POP_TOP

View File

@@ -75,7 +75,7 @@ class Python38Parser(Python37Parser):
COME_FROM_FINALLY COME_FROM_FINALLY
END_ASYNC_FOR END_ASYNC_FOR
genexpr_func_async ::= LOAD_FAST func_async_prefix genexpr_func_async ::= LOAD_ARG func_async_prefix
store comp_iter store comp_iter
JUMP_BACK COME_FROM_FINALLY JUMP_BACK COME_FROM_FINALLY
END_ASYNC_FOR END_ASYNC_FOR

View File

@@ -387,6 +387,11 @@ class Scanner37Base(Scanner):
if "." in inst.argval: if "." in inst.argval:
opname = "IMPORT_NAME_ATTR" opname = "IMPORT_NAME_ATTR"
pass pass
elif opname == "LOAD_FAST" and argval == ".0":
# Used as the parameter of a list expression
opname = "LOAD_ARG"
elif opname in ("MAKE_FUNCTION", "MAKE_CLOSURE"): elif opname in ("MAKE_FUNCTION", "MAKE_CLOSURE"):
flags = argval flags = argval
opname = "MAKE_FUNCTION_%d" % (flags) opname = "MAKE_FUNCTION_%d" % (flags)

View File

@@ -185,60 +185,92 @@ TABLE_R = {
# } # }
TABLE_DIRECT = { TABLE_DIRECT = {
"BINARY_ADD": ("+",), "BINARY_ADD": ( "+" ,),
"BINARY_SUBTRACT": ("-",), "BINARY_AND": ( "&" ,),
"BINARY_MULTIPLY": ("*",), "BINARY_DIVIDE": ( "/" ,),
"BINARY_DIVIDE": ("/",), "BINARY_FLOOR_DIVIDE": ( "//" ,),
"BINARY_MATRIX_MULTIPLY": ("@",), "BINARY_LSHIFT": ( "<<",),
"BINARY_TRUE_DIVIDE": ("/",), # Not in <= 2.1 "BINARY_MATRIX_MULTIPLY": ( "@" ,),
"BINARY_FLOOR_DIVIDE": ("//",), "BINARY_MODULO": ( "%%",),
"BINARY_MODULO": ("%%",), "BINARY_MULTIPLY": ( "*" ,),
"BINARY_POWER": ("**",), "BINARY_OR": ( "|" ,),
"BINARY_LSHIFT": ("<<",), "BINARY_POWER": ( "**",),
"BINARY_RSHIFT": (">>",), "BINARY_RSHIFT": ( ">>",),
"BINARY_AND": ("&",), "BINARY_SUBTRACT": ( "-" ,),
"BINARY_OR": ("|",), "BINARY_TRUE_DIVIDE": ( "/" ,), # Not in <= 2.1; 2.6 generates INPLACE_DIVIDE only?
"BINARY_XOR": ("^",), "BINARY_XOR": ( "^" ,),
"DELETE_FAST": ("%|del %{pattr}\n",), "DELETE_FAST": ( "%|del %{pattr}\n", ),
"DELETE_NAME": ("%|del %{pattr}\n",), "DELETE_GLOBAL": ( "%|del %{pattr}\n", ),
"DELETE_GLOBAL": ("%|del %{pattr}\n",), "DELETE_NAME": ( "%|del %{pattr}\n", ),
"INPLACE_ADD": ("+=",), "IMPORT_FROM": ( "%{pattr}", ),
"INPLACE_SUBTRACT": ("-=",), "IMPORT_NAME_ATTR": ( "%{pattr}", ),
"INPLACE_MULTIPLY": ("*=",), "INPLACE_ADD": ( "+=" ,),
"INPLACE_MATRIX_MULTIPLY": ("@=",), "INPLACE_AND": ( "&=" ,),
"INPLACE_DIVIDE": ("/=",), "INPLACE_DIVIDE": ( "/=" ,),
"INPLACE_TRUE_DIVIDE": ("/=",), # Not in <= 2.1; 2.6 generates INPLACE_DIVIDE only? "INPLACE_FLOOR_DIVIDE": ( "//=" ,),
"INPLACE_FLOOR_DIVIDE": ("//=",), "INPLACE_LSHIFT": ( "<<=",),
"INPLACE_MODULO": ("%%=",), "INPLACE_MATRIX_MULTIPLY": ( "@=" ,),
"INPLACE_POWER": ("**=",), "INPLACE_MODULO": ( "%%=",),
"INPLACE_LSHIFT": ("<<=",), "INPLACE_MULTIPLY": ( "*=" ,),
"INPLACE_RSHIFT": (">>=",), "INPLACE_OR": ( "|=" ,),
"INPLACE_AND": ("&=",), "INPLACE_POWER": ( "**=",),
"INPLACE_OR": ("|=",), "INPLACE_RSHIFT": ( ">>=",),
"INPLACE_XOR": ("^=",), "INPLACE_SUBTRACT": ( "-=" ,),
"INPLACE_TRUE_DIVIDE": ( "/=" ,),
"INPLACE_XOR": ( "^=" ,),
"LOAD_ARG": ( "%{pattr}", ),
"LOAD_ASSERT": ( "%{pattr}", ),
"LOAD_CLASSNAME": ( "%{pattr}", ),
"LOAD_DEREF": ( "%{pattr}", ),
"LOAD_FAST": ( "%{pattr}", ),
"LOAD_GLOBAL": ( "%{pattr}", ),
"LOAD_LOCALS": ( "locals()", ),
"LOAD_NAME": ( "%{pattr}", ),
"LOAD_STR": ( "%{pattr}", ),
"STORE_DEREF": ( "%{pattr}", ),
"STORE_FAST": ( "%{pattr}", ),
"STORE_GLOBAL": ( "%{pattr}", ),
"STORE_NAME": ( "%{pattr}", ),
"UNARY_INVERT": ( "~"),
"UNARY_NEGATIVE": ( "-",),
"UNARY_NOT": ( "not ", ),
"UNARY_POSITIVE": ( "+",),
# bin_op (formerly "binary_expr") is the Python AST BinOp # bin_op (formerly "binary_expr") is the Python AST BinOp
"bin_op": ("%c %c %c", 0, (-1, "binary_operator"), (1, "expr")), "bin_op": ("%c %c %c", 0, (-1, "binary_operator"), (1, "expr")),
"UNARY_INVERT": ("~"),
# unary_op (formerly "unary_expr") is the Python AST UnaryOp # unary_op (formerly "unary_expr") is the Python AST UnaryOp
"unary_op": ("%c%c", (1, "unary_operator"), (0, "expr")), "unary_op": ("%c%c", (1, "unary_operator"), (0, "expr")),
"unary_not": ("not %c", (0, "expr")), "unary_not": ("not %c", (0, "expr")),
"unary_convert": ("`%c`", (0, "expr"),), "unary_convert": ("`%c`", (0, "expr"),),
"get_iter": ("iter(%c)", (0, "expr"),), "get_iter": ("iter(%c)", (0, "expr"),),
"slice0": ("%c[:]", (0, "expr"),),
"slice1": ("%c[%p:]", (0, "expr"), (1, 100)), "set_iter": ( "%c", 0 ),
"slice2": ("%c[:%p]", (0, "expr"), (1, 100)),
"slice3": ("%c[%p:%p]", (0, "expr"), (1, 100), (2, 100)), "slice0": (
"%c[:]",
(0, "expr"),
),
"slice1": (
"%c[%p:]",
(0, "expr"),
(1, NO_PARENTHESIS_EVER)
),
"slice2": ( "[%c:%p]",
(0, "expr"),
(1, NO_PARENTHESIS_EVER)
),
"slice3": (
"%c[%p:%p]",
(0, "expr"),
(1, NO_PARENTHESIS_EVER),
(2, NO_PARENTHESIS_EVER)
),
"IMPORT_FROM": ("%{pattr}",), "IMPORT_FROM": ("%{pattr}",),
"IMPORT_NAME_ATTR": ("%{pattr}",), "IMPORT_NAME_ATTR": ("%{pattr}",),
"attribute": ("%c.%[1]{pattr}", (0, "expr")), "attribute": ("%c.%[1]{pattr}", (0, "expr")),
"LOAD_STR": ("%{pattr}",),
"LOAD_FAST": ("%{pattr}",),
"LOAD_NAME": ("%{pattr}",),
"LOAD_CLASSNAME": ("%{pattr}",),
"LOAD_GLOBAL": ("%{pattr}",),
"LOAD_DEREF": ("%{pattr}",),
"LOAD_LOCALS": ("locals()",),
"LOAD_ASSERT": ("%{pattr}",),
"delete_subscript": ( "delete_subscript": (
"%|del %p[%c]\n", "%|del %p[%c]\n",
(0, "expr", PRECEDENCE["subscript"]), (0, "expr", PRECEDENCE["subscript"]),
@@ -258,12 +290,6 @@ TABLE_DIRECT = {
), ),
"store_subscript": ("%p[%c]", (0, "expr", PRECEDENCE["subscript"]), (1, "expr")), "store_subscript": ("%p[%c]", (0, "expr", PRECEDENCE["subscript"]), (1, "expr")),
"STORE_FAST": ("%{pattr}",),
"STORE_NAME": ("%{pattr}",),
"STORE_GLOBAL": ("%{pattr}",),
"STORE_DEREF": ("%{pattr}",),
"UNARY_POSITIVE": ("+",),
"UNARY_NEGATIVE": ("-",),
"unpack": ("%C%,", (1, maxint, ", ")), "unpack": ("%C%,", (1, maxint, ", ")),
# This nonterminal we create on the fly in semantic routines # This nonterminal we create on the fly in semantic routines
"unpack_w_parens": ("(%C%,)", (1, maxint, ", ")), "unpack_w_parens": ("(%C%,)", (1, maxint, ", ")),
@@ -285,7 +311,7 @@ TABLE_DIRECT = {
"set_comp_body": ("%c", 0), "set_comp_body": ("%c", 0),
"gen_comp_body": ("%c", 0), "gen_comp_body": ("%c", 0),
"dict_comp_body": ("%c:%c", 1, 0), "dict_comp_body": ("%c: %c", 1, 0),
"assign": ("%|%c = %p\n", -1, (0, 200)), "assign": ("%|%c = %p\n", -1, (0, 200)),
# The 2nd parameter should have a = suffix. # The 2nd parameter should have a = suffix.
# There is a rule with a 4th parameter "store" # There is a rule with a 4th parameter "store"

View File

@@ -182,9 +182,11 @@ class ComprehensionMixin:
self.write(" in ") self.write(" in ")
if node[2] == "expr": if node[2] == "expr":
iter_expr = node[2] iter_expr = node[2]
elif node[3] == "get_aiter":
iter_expr = node[3]
else: else:
iter_expr = node[-3] iter_expr = node[-3]
assert iter_expr == "expr" assert iter_expr in ("expr", "get_aiter"), iter_expr
self.preorder(iter_expr) self.preorder(iter_expr)
self.preorder(tree[iter_index]) self.preorder(tree[iter_index])
self.prec = p self.prec = p