# 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 . """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() elif key.kind == 'CALL_FUNCTION_1': args_node = node[-2] if args_node == 'pos_arg': assert args_node[0] == 'expr' n = args_node[0][0] if n == 'generator_exp': template = ('%c%P', 0, (1, -1, ', ', 100)) 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