Files
python-uncompyle6/uncompyle6/semantics/customize35.py
rocky af209dc142 Bug in 3.5+ generator detection...
Also bug in 3.5 code detection for async attribute
2019-06-05 19:08:21 -04:00

233 lines
8.3 KiB
Python

# Copyright (c) 2019 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# 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.5 version-specific semantic actions here.
"""
from xdis.code import iscode
from xdis.util import COMPILER_FLAG_BIT
from uncompyle6.semantics.consts import (
INDENT_PER_LEVEL, TABLE_DIRECT)
from uncompyle6.semantics.helper import flatten_list
#######################
# Python 3.5+ Changes #
#######################
def customize_for_version35(self, version):
TABLE_DIRECT.update({
'await_expr': ( 'await %c', 0),
'await_stmt': ( '%|%c\n', 0),
'async_for_stmt': (
'%|async for %c in %c:\n%+%|%c%-\n\n', 9, 1, 25 ),
'async_forelse_stmt': (
'%|async for %c in %c:\n%+%c%-%|else:\n%+%c%-\n\n',
9, 1, 25, (27, 'else_suite') ),
'async_with_stmt': (
'%|async with %c:\n%+%|%c%-',
(0, 'expr'), 7 ),
'async_with_as_stmt': (
'%|async with %c as %c:\n%+%|%c%-',
(0, 'expr'), (6, 'store'), 7),
'unmap_dict': ( '{**%C}', (0, -1, ', **') ),
# 'unmapexpr': ( '{**%c}', 0), # done by n_unmapexpr
})
def 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 = async_call
self.n_build_list_unpack = self.n_list
def n_call(node):
mapping = self._get_mapping(node)
table = mapping[0]
key = node
for i in mapping[1:]:
key = key[i]
pass
if key.kind.startswith('CALL_FUNCTION_VAR_KW'):
# Python 3.5 changes the stack position of
# *args: kwargs come after *args whereas
# in earlier Pythons, *args is at the end
# which simplifies things from our
# perspective. Python 3.6+ replaces
# CALL_FUNCTION_VAR_KW with
# CALL_FUNCTION_EX We will just swap the
# order to make it look like earlier
# Python 3.
entry = table[key.kind]
kwarg_pos = entry[2][1]
args_pos = kwarg_pos - 1
# Put last node[args_pos] after subsequent kwargs
while node[kwarg_pos] == 'kwarg' and kwarg_pos < len(node):
# swap node[args_pos] with node[kwargs_pos]
node[kwarg_pos], node[args_pos] = node[args_pos], node[kwarg_pos]
args_pos = kwarg_pos
kwarg_pos += 1
elif key.kind.startswith('CALL_FUNCTION_VAR'):
# CALL_FUNCTION_VAR's top element of the stack contains
# the variable argument list, then comes
# annotation args, then keyword args.
# In the most least-top-most stack entry, but position 1
# in node order, the positional args.
argc = node[-1].attr
nargs = argc & 0xFF
kwargs = (argc >> 8) & 0xFF
# FIXME: handle annotation args
if nargs > 0:
template = ('%c(%C, ', 0, (1, nargs+1, ', '))
else:
template = ('%c(', 0)
self.template_engine(template, node)
args_node = node[-2]
if args_node in ('pos_arg', 'expr'):
args_node = args_node[0]
if args_node == 'build_list_unpack':
template = ('*%P)', (0, len(args_node)-1, ', *', 100))
self.template_engine(template, args_node)
else:
if len(node) - nargs > 3:
template = ('*%c, %C)', nargs+1, (nargs+kwargs+1, -1, ', '))
else:
template = ('*%c)', nargs+1)
self.template_engine(template, node)
self.prune()
self.default(node)
self.n_call = n_call
def n_function_def(node):
n0 = node[0]
is_code = False
for i in list(range(len(n0)-2, -1, -1)):
code_node = n0[i]
if hasattr(code_node, 'attr') and iscode(code_node.attr):
is_code = True
break
if (is_code and
(code_node.attr.co_flags & COMPILER_FLAG_BIT['COROUTINE'])):
self.template_engine(('\n\n%|async def %c\n',
-2), node)
else:
self.template_engine(('\n\n%|def %c\n', -2),
node)
self.prune()
self.n_function_def = n_function_def
def unmapexpr(node):
last_n = node[0][-1]
for n in node[0]:
self.preorder(n)
if n != last_n:
self.f.write(', **')
pass
pass
self.prune()
pass
self.n_unmapexpr = unmapexpr
# FIXME: start here
def n_list_unpack(node):
"""
prettyprint an unpacked 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 = ']'
elif lastnodetype.startswith('BUILD_TUPLE'):
# Tuples can appear places that can NOT
# have parenthesis around them, like array
# subscripts. We check for that by seeing
# if a tuple item is some sort of slice.
no_parens = False
for n in node:
if n == 'expr' and n[0].kind.startswith('build_slice'):
no_parens = True
break
pass
if no_parens:
endchar = ''
else:
self.write('('); endchar = ')'
pass
elif lastnodetype.startswith('BUILD_SET'):
self.write('{'); endchar = '}'
elif lastnodetype.startswith('BUILD_MAP_UNPACK'):
self.write('{*'); endchar = '}'
elif lastnodetype.startswith('ROT_TWO'):
self.write('('); endchar = ')'
else:
raise TypeError('Internal Error: n_build_list expects list, tuple, set, or unpack')
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
value = self.traverse(elem)
if elem[0] == 'tuple':
assert value[0] == '('
assert value[-1] == ')'
value = value[1:-1]
if value[-1] == ',':
# singleton tuple
value = value[:-1]
else:
value = '*' + value
if line_number != self.line_number:
sep += '\n' + self.indent + INDENT_PER_LEVEL[:-1]
else:
if sep != '': sep += ' '
if not last_was_star:
pass
else:
last_was_star = False
self.write(sep, value)
sep = ','
if lastnode.attr == 1 and lastnodetype.startswith('BUILD_TUPLE'):
self.write(',')
self.write(endchar)
self.indent_less(INDENT_PER_LEVEL)
self.prec = p
self.prune()
return
self.n_tuple_unpack = n_list_unpack