You've already forked python-uncompyle6
mirror of
https://github.com/rocky/python-uncompyle6.git
synced 2025-08-04 01:09:52 +08:00
Merge branch 'master' into python-2.4
This commit is contained in:
@@ -57,6 +57,19 @@ disassembler called `pydisasm`.
|
||||
|
||||
### Semantic equivalence vs. exact source code
|
||||
|
||||
Consider how Python compiles something like "(x*y) + 5". Early on
|
||||
Python creates an "abstract syntax tree" (AST) for this. And this is
|
||||
"abstract" in the sense that unimportant, redundant or unnecceary
|
||||
items have been removed. Here, this means that any notion that you
|
||||
wrote "x+y" in parenthesis is lost, since in this context they are
|
||||
unneeded. Also lost is the fact that the multiplication didn't have
|
||||
spaces around it while the addition did. It should not come as a
|
||||
surprise then that the bytecode which is derived from the AST also has
|
||||
no notion of such possible variation. Generally this kind of thing
|
||||
isn't noticed since the Python community has laid out a very rigid set
|
||||
of formatting guidelines; and it has largely beaten the community into
|
||||
compliance.
|
||||
|
||||
Almost all versions of Python can perform some sort of code
|
||||
improvement that can't be undone. In earlier versions of Python it is
|
||||
rare; in later Python versions, it is more common.
|
||||
@@ -66,7 +79,7 @@ If the code emitted is semantically equivalent, then this isn't a bug.
|
||||
|
||||
For example the code might be
|
||||
|
||||
```
|
||||
```python
|
||||
if a:
|
||||
if b:
|
||||
x = 1
|
||||
@@ -74,7 +87,7 @@ if a:
|
||||
|
||||
and we might produce:
|
||||
|
||||
```
|
||||
```python
|
||||
if a and b:
|
||||
x = 1
|
||||
```
|
||||
@@ -87,24 +100,35 @@ else:
|
||||
|
||||
```
|
||||
|
||||
may come out as `elif`.
|
||||
may come out as `elif` or vice versa.
|
||||
|
||||
|
||||
As mentioned in the README, It is possible that Python changes what
|
||||
you write to be more efficient. For example, for:
|
||||
|
||||
|
||||
```
|
||||
```python
|
||||
if True:
|
||||
x = 5
|
||||
```
|
||||
|
||||
Python will generate code like:
|
||||
|
||||
```
|
||||
```python
|
||||
x = 5
|
||||
```
|
||||
|
||||
Even more extreme, if your code is:
|
||||
|
||||
```python
|
||||
if False:
|
||||
x = 1
|
||||
y = 2
|
||||
# ...
|
||||
```
|
||||
|
||||
Python will eliminate the entire "if" statement.
|
||||
|
||||
So just because the text isn't the same, does not
|
||||
necessarily mean there's a bug.
|
||||
|
||||
|
@@ -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.5.3 3.6.3 2.6.9 3.3.6 2.7.14 3.4.2'
|
||||
export PYVERSIONS='3.5.5 3.6.4 2.6.9 3.3.7 2.7.14 3.4.8'
|
||||
|
BIN
test/bytecode_2.5/08_if_while_else.pyc
Normal file
BIN
test/bytecode_2.5/08_if_while_else.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
13
test/simple_source/bug25/08_if_while_else.py
Normal file
13
test/simple_source/bug25/08_if_while_else.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# From 2.3 Queue.py
|
||||
# Bug was adding COME_FROM from while
|
||||
# confusing the else
|
||||
def put(item, block=True, timeout=None):
|
||||
if block:
|
||||
if timeout:
|
||||
while True:
|
||||
if item:
|
||||
block = 1
|
||||
else:
|
||||
block = 5
|
||||
elif item:
|
||||
block = False
|
@@ -4,3 +4,14 @@ def __new__(cls, encode, decode, streamreader=None, streamwriter=None,
|
||||
incrementalencoder=None, incrementaldecoder=None, name=None,
|
||||
*, _is_text_encoding=None):
|
||||
return
|
||||
|
||||
# From 3.3 _pyio.py. A closure is created here.
|
||||
# This changes how the default params are found
|
||||
class StringIO(object):
|
||||
def __init__(self, initial_value="", newline="\n"):
|
||||
super(StringIO, self).__init__()
|
||||
|
||||
# No closure created here
|
||||
class StringIO2(object):
|
||||
def __init__(self, initial_value="", newline="\n"):
|
||||
return 5
|
||||
|
@@ -1,49 +0,0 @@
|
||||
# Bug was found in 3.6 _osx_support.py in if/elif needing
|
||||
# EXTENDED_ARGS which are the targets of jumps.
|
||||
def get_platform_osx(_config_vars, osname, release, machine, sys, re):
|
||||
"""Filter values for get_platform()"""
|
||||
|
||||
macver = _config_vars.get('MACOSX_DEPLOYMENT_TARGET', '')
|
||||
macrelease = release or 10
|
||||
macver = macver or macrelease
|
||||
|
||||
if macver:
|
||||
release = macver
|
||||
osname = "macosx"
|
||||
|
||||
cflags = _config_vars.get('CFLAGS', _config_vars.get('CFLAGS', ''))
|
||||
if macrelease:
|
||||
try:
|
||||
macrelease = tuple(int(i) for i in macrelease.split('.')[0:2])
|
||||
except ValueError:
|
||||
macrelease = (10, 0)
|
||||
else:
|
||||
macrelease = (10, 0)
|
||||
|
||||
if (macrelease >= (10, 4)) and '-arch' in cflags.strip():
|
||||
machine = 'fat'
|
||||
|
||||
archs = re.findall(r'-arch\s+(\S+)', cflags)
|
||||
archs = tuple(sorted(set(archs)))
|
||||
|
||||
if len(archs) == 1:
|
||||
machine = archs[0]
|
||||
elif archs == ('i386', 'ppc'):
|
||||
machine = 'fat'
|
||||
elif archs == ('i386', 'x86_64'):
|
||||
machine = 'intel'
|
||||
elif archs == ('i386', 'ppc', 'x86_64'):
|
||||
machine = 'fat3'
|
||||
elif archs == ('ppc64', 'x86_64'):
|
||||
machine = 'fat64'
|
||||
elif archs == ('i386', 'ppc', 'ppc64', 'x86_64'):
|
||||
machine = 'universal'
|
||||
else:
|
||||
raise ValueError(
|
||||
"Don't know machine value for archs=%r" % (archs,))
|
||||
|
||||
elif machine == 'i386':
|
||||
if sys.maxsize >= 2**32:
|
||||
machine = 'x86_64'
|
||||
|
||||
return (osname, release, machine)
|
@@ -1,5 +1,30 @@
|
||||
# Python 3.6's changes for calling functions.
|
||||
# See https://github.com/rocky/python-uncompyle6/issues/58
|
||||
# CALL_FUNCTION_EX takes 2 to 3 arguments on the stack: the function, the tuple of positional arguments,
|
||||
# and optionally the dict of keyword arguments if bit 0 of oparg is 1.
|
||||
a(*[])
|
||||
|
||||
# CALL_FUNCTION_EX takes 2 to 3 arguments on the stack:
|
||||
# * the function,
|
||||
# * the tuple of positional arguments, and optionally
|
||||
# * the dict of keyword arguments if bit 0 of oparg is 1.
|
||||
from foo import f, dialect, args, kwds, reader
|
||||
|
||||
f(*[])
|
||||
|
||||
# From Python 3.6 csv.py
|
||||
# (f, dialect) are positional arg tuples, *args, is by itself, i.e.
|
||||
# no tuple.
|
||||
x = reader(f, dialect, *args, **kwds)
|
||||
|
||||
# From 3.6 functools.py
|
||||
# Below there is a load_closure instruction added
|
||||
def cmp_to_key(mycmp):
|
||||
class K(object):
|
||||
def __ge__():
|
||||
return mycmp()
|
||||
return
|
||||
|
||||
# In this situation though, there is no load_closure
|
||||
def cmp2_to_key(mycmp):
|
||||
class K2(object):
|
||||
def __ge__():
|
||||
return 5
|
||||
return
|
||||
|
@@ -6,3 +6,12 @@ def a85decode(b, *, foldspaces=False, adobe=False, ignorechars=b' \t\n\r\v'):
|
||||
_UNSET = object()
|
||||
def get(self, section, option, *, raw=False, vars=None, fallback=_UNSET):
|
||||
return
|
||||
|
||||
# From 3.6 compileall.py Bug is making default values are in quotes
|
||||
def compile_command(source, filename="<input>", symbol="single"):
|
||||
return
|
||||
|
||||
# From 3.6 _pyio.py. Bug was in getting order of metaclass=abc.ABCMeta right
|
||||
import abc
|
||||
class IOBase(metaclass=abc.ABCMeta):
|
||||
pass
|
||||
|
@@ -115,7 +115,6 @@ class Python26Parser(Python2Parser):
|
||||
|
||||
# Semantic actions want else_suitel to be at index 3
|
||||
ifelsestmtl ::= testexpr c_stmts_opt cf_jb_cf_pop else_suitel
|
||||
|
||||
ifelsestmtc ::= testexpr c_stmts_opt ja_cf_pop else_suitec
|
||||
|
||||
# Semantic actions want suite_stmts_opt to be at index 3
|
||||
|
@@ -19,6 +19,7 @@ spark grammar differences over Python 3.5 for Python 3.6.
|
||||
from uncompyle6.parser import PythonParserSingle, nop_func
|
||||
from spark_parser import DEFAULT_DEBUG as PARSER_DEFAULT_DEBUG
|
||||
from uncompyle6.parsers.parse35 import Python35Parser
|
||||
from uncompyle6.scanners.tok import Token
|
||||
|
||||
class Python36Parser(Python35Parser):
|
||||
|
||||
@@ -77,6 +78,8 @@ class Python36Parser(Python35Parser):
|
||||
|
||||
def customize_grammar_rules(self, tokens, customize):
|
||||
super(Python36Parser, self).customize_grammar_rules(tokens, customize)
|
||||
self.check_reduce['call_kw'] = 'AST'
|
||||
|
||||
for i, token in enumerate(tokens):
|
||||
opname = token.kind
|
||||
|
||||
@@ -186,6 +189,8 @@ class Python36Parser(Python35Parser):
|
||||
self.addRule("""expr ::= call_ex_kw
|
||||
expr ::= call_ex_kw2
|
||||
expr ::= call_ex_kw3
|
||||
expr ::= call_ex_kw4
|
||||
|
||||
call_ex_kw ::= expr expr build_map_unpack_with_call
|
||||
CALL_FUNCTION_EX_KW
|
||||
call_ex_kw2 ::= expr
|
||||
@@ -193,6 +198,10 @@ class Python36Parser(Python35Parser):
|
||||
build_map_unpack_with_call
|
||||
CALL_FUNCTION_EX_KW
|
||||
call_ex_kw3 ::= expr
|
||||
build_tuple_unpack_with_call
|
||||
expr
|
||||
CALL_FUNCTION_EX_KW
|
||||
call_ex_kw4 ::= expr
|
||||
expr
|
||||
expr
|
||||
CALL_FUNCTION_EX_KW
|
||||
@@ -212,6 +221,21 @@ class Python36Parser(Python35Parser):
|
||||
seen_GET_AWAITABLE_YIELD_FROM,
|
||||
next_token)
|
||||
|
||||
def reduce_is_invalid(self, rule, ast, tokens, first, last):
|
||||
invalid = super(Python36Parser,
|
||||
self).reduce_is_invalid(rule, ast,
|
||||
tokens, first, last)
|
||||
if invalid:
|
||||
return invalid
|
||||
if rule[0] == 'call_kw':
|
||||
# Make sure we don't derive call_kw
|
||||
nt = ast[0]
|
||||
while not isinstance(nt, Token):
|
||||
if nt[0] == 'call_kw':
|
||||
return True
|
||||
nt = nt[0]
|
||||
|
||||
return False
|
||||
class Python36ParserSingle(Python36Parser, PythonParserSingle):
|
||||
pass
|
||||
|
||||
|
@@ -1131,6 +1131,12 @@ class Scanner2(Scanner):
|
||||
source = self.setup_loops[label]
|
||||
else:
|
||||
source = offset
|
||||
# FIXME: The grammar for 2.6 and before doesn't
|
||||
# handle COME_FROM's from a loop inside if's
|
||||
# It probably should.
|
||||
if (self.version > 2.6 or
|
||||
self.code[source] != self.opc.SETUP_LOOP or
|
||||
self.code[label] != self.opc.JUMP_FORWARD):
|
||||
targets[label] = targets.get(label, []) + [source]
|
||||
pass
|
||||
pass
|
||||
|
@@ -455,6 +455,33 @@ def customize_for_version(self, is_pypy, version):
|
||||
self.n_call_ex_kw2 = call_ex_kw2
|
||||
|
||||
def call_ex_kw3(node):
|
||||
"""Handle CALL_FUNCTION_EX 1 (have KW) but without
|
||||
BUILD_MAP_UNPACK_WITH_CALL"""
|
||||
self.preorder(node[0])
|
||||
self.write('(')
|
||||
args = node[1][0]
|
||||
if args == 'expr':
|
||||
args = args[0]
|
||||
if args == 'tuple':
|
||||
if self.call36_tuple(args) > 0:
|
||||
self.write(', ')
|
||||
pass
|
||||
pass
|
||||
|
||||
self.write('*')
|
||||
self.preorder(node[1][1])
|
||||
self.write(', ')
|
||||
|
||||
kwargs = node[2]
|
||||
if kwargs == 'expr':
|
||||
kwargs = kwargs[0]
|
||||
self.write('**')
|
||||
self.preorder(kwargs)
|
||||
self.write(')')
|
||||
self.prune()
|
||||
self.n_call_ex_kw3 = call_ex_kw3
|
||||
|
||||
def call_ex_kw4(node):
|
||||
"""Handle CALL_FUNCTION_EX 2 (have KW) but without
|
||||
BUILD_{MAP,TUPLE}_UNPACK_WITH_CALL"""
|
||||
self.preorder(node[0])
|
||||
@@ -478,8 +505,7 @@ def customize_for_version(self, is_pypy, version):
|
||||
self.preorder(kwargs)
|
||||
self.write(')')
|
||||
self.prune()
|
||||
self.n_call_ex_kw3 = call_ex_kw3
|
||||
|
||||
self.n_call_ex_kw4 = call_ex_kw4
|
||||
|
||||
def call36_tuple(node):
|
||||
"""
|
||||
@@ -665,7 +691,7 @@ def customize_for_version(self, is_pypy, version):
|
||||
self.prune()
|
||||
return
|
||||
self.n_return_closure = return_closure
|
||||
pass # version > 3.6
|
||||
pass # version > 3.4
|
||||
pass # version > 3.0
|
||||
pass # version >= 3.6
|
||||
pass # version >= 3.4
|
||||
pass # version >= 3.0
|
||||
return
|
||||
|
@@ -456,8 +456,11 @@ def make_function3(self, node, is_lambda, nested=1, codeNode=None):
|
||||
|
||||
# MAKE_CLOSURE adds an additional closure slot
|
||||
|
||||
# Thank you, Python: such a well-thought out system that has
|
||||
# changed and continues to change many times.
|
||||
# In Python 3.6 stack entries change again. I understand
|
||||
# 3.7 changes some of those changes. Yes, it is hard to follow
|
||||
# and I am sure I haven't been able to keep up.
|
||||
|
||||
# Thank you, Python.
|
||||
|
||||
def build_param(ast, name, default):
|
||||
"""build parameters:
|
||||
@@ -482,10 +485,23 @@ def make_function3(self, node, is_lambda, nested=1, codeNode=None):
|
||||
# MAKE_FUNCTION_... or MAKE_CLOSURE_...
|
||||
assert node[-1].kind.startswith('MAKE_')
|
||||
|
||||
# Python 3.3+ adds a qualified name at TOS (-1)
|
||||
# moving down the LOAD_LAMBDA instruction
|
||||
if 3.0 <= self.version <= 3.2:
|
||||
lambda_index = -2
|
||||
elif 3.03 <= self.version:
|
||||
lambda_index = -3
|
||||
else:
|
||||
lambda_index = None
|
||||
|
||||
args_node = node[-1]
|
||||
if isinstance(args_node.attr, tuple):
|
||||
if self.version <= 3.3 and len(node) > 2 and node[-3] != 'LOAD_LAMBDA':
|
||||
# positional args are after kwargs
|
||||
pos_args, kw_args, annotate_argc = args_node.attr
|
||||
# FIXME: there is probably a better way to classify this.
|
||||
if (self.version <= 3.3 and len(node) > 2 and
|
||||
node[lambda_index] != 'LOAD_LAMBDA' and
|
||||
(node[0].kind.startswith('kwarg') or node[-4].kind != 'load_closure')):
|
||||
# args are after kwargs; kwargs are bundled as one node
|
||||
defparams = node[1:args_node.attr[0]+1]
|
||||
else:
|
||||
# args are before kwargs; kwags as bundled as one node
|
||||
@@ -502,7 +518,7 @@ def make_function3(self, node, is_lambda, nested=1, codeNode=None):
|
||||
expr_node = node[0]
|
||||
if (expr_node[0] == 'LOAD_CONST' and
|
||||
isinstance(expr_node[0].attr, tuple)):
|
||||
defparams = list(expr_node[0].attr)
|
||||
defparams = [repr(a) for a in expr_node[0].attr]
|
||||
elif expr_node[0] in frozenset(('list', 'tuple', 'dict', 'set')):
|
||||
defparams = [self.traverse(n, indent='') for n in expr_node[0][:-1]]
|
||||
else:
|
||||
|
@@ -536,278 +536,8 @@ class SourceWalker(GenericASTTraversal, object):
|
||||
pass
|
||||
self.n_unmapexpr = unmapexpr
|
||||
|
||||
if version >= 3.6:
|
||||
########################
|
||||
# Python 3.6+ Additions
|
||||
#######################
|
||||
|
||||
TABLE_DIRECT.update({
|
||||
'fstring_expr': ( "{%c%{conversion}}", 0),
|
||||
'fstring_single': ( "f'{%c%{conversion}}'", 0),
|
||||
'fstring_multi': ( "f'%c'", 0),
|
||||
'func_args36': ( "%c(**", 0),
|
||||
'try_except36': ( '%|try:\n%+%c%-%c\n\n', 1, 2 ),
|
||||
'unpack_list': ( '*%c', (0, 'list') ),
|
||||
'starred': ( '*%c', (0, 'expr') ),
|
||||
'call_ex' : (
|
||||
'%c(%c)',
|
||||
(0, 'expr'), 1),
|
||||
'call_ex_kw' : (
|
||||
'%c(%c)',
|
||||
(0, 'expr'), 2),
|
||||
|
||||
})
|
||||
|
||||
TABLE_R.update({
|
||||
'CALL_FUNCTION_EX': ('%c(*%P)', 0, (1, 2, ', ', 100)),
|
||||
# Not quite right
|
||||
'CALL_FUNCTION_EX_KW': ('%c(**%C)', 0, (2, 3, ',')),
|
||||
})
|
||||
|
||||
def build_unpack_tuple_with_call(node):
|
||||
|
||||
if node[0] == 'expr':
|
||||
tup = node[0][0]
|
||||
else:
|
||||
tup = node[0]
|
||||
pass
|
||||
assert tup == 'tuple'
|
||||
self.call36_tuple(tup)
|
||||
|
||||
buwc = node[-1]
|
||||
assert buwc.kind.startswith('BUILD_TUPLE_UNPACK_WITH_CALL')
|
||||
for n in node[1:-1]:
|
||||
self.f.write(', *')
|
||||
self.preorder(n)
|
||||
pass
|
||||
self.prune()
|
||||
return
|
||||
self.n_build_tuple_unpack_with_call = build_unpack_tuple_with_call
|
||||
|
||||
def build_unpack_map_with_call(node):
|
||||
n = node[0]
|
||||
if n == 'expr':
|
||||
n = n[0]
|
||||
if n == 'dict':
|
||||
self.call36_dict(n)
|
||||
first = 1
|
||||
sep = ', **'
|
||||
else:
|
||||
first = 0
|
||||
sep = '**'
|
||||
for n in node[first:-1]:
|
||||
self.f.write(sep)
|
||||
self.preorder(n)
|
||||
sep = ', **'
|
||||
pass
|
||||
self.prune()
|
||||
return
|
||||
self.n_build_map_unpack_with_call = build_unpack_map_with_call
|
||||
|
||||
def call_ex_kw2(node):
|
||||
"""Handle CALL_FUNCTION_EX 2 (have KW) but with
|
||||
BUILD_{MAP,TUPLE}_UNPACK_WITH_CALL"""
|
||||
|
||||
# This is weird shit. Thanks Python!
|
||||
self.preorder(node[0])
|
||||
self.write('(')
|
||||
|
||||
assert node[1] == 'build_tuple_unpack_with_call'
|
||||
btuwc = node[1]
|
||||
tup = btuwc[0]
|
||||
if tup == 'expr':
|
||||
tup = tup[0]
|
||||
assert tup == 'tuple'
|
||||
self.call36_tuple(tup)
|
||||
assert node[2] == 'build_map_unpack_with_call'
|
||||
|
||||
self.write(', ')
|
||||
d = node[2][0]
|
||||
if d == 'expr':
|
||||
d = d[0]
|
||||
assert d == 'dict'
|
||||
self.call36_dict(d)
|
||||
|
||||
args = btuwc[1]
|
||||
self.write(', *')
|
||||
self.preorder(args)
|
||||
|
||||
self.write(', **')
|
||||
star_star_args = node[2][1]
|
||||
if star_star_args == 'expr':
|
||||
star_star_args = star_star_args[0]
|
||||
self.preorder(star_star_args)
|
||||
self.write(')')
|
||||
self.prune()
|
||||
self.n_call_ex_kw2 = call_ex_kw2
|
||||
|
||||
def call_ex_kw3(node):
|
||||
"""Handle CALL_FUNCTION_EX 2 (have KW) but without
|
||||
BUILD_{MAP,TUPLE}_UNPACK_WITH_CALL"""
|
||||
self.preorder(node[0])
|
||||
self.write('(')
|
||||
args = node[1][0]
|
||||
if args == 'tuple':
|
||||
if self.call36_tuple(args) > 0:
|
||||
self.write(', ')
|
||||
pass
|
||||
pass
|
||||
else:
|
||||
self.write('*')
|
||||
self.preorder(args)
|
||||
self.write(', ')
|
||||
pass
|
||||
|
||||
kwargs = node[2]
|
||||
if kwargs == 'expr':
|
||||
kwargs = kwargs[0]
|
||||
self.write('**')
|
||||
self.preorder(kwargs)
|
||||
self.write(')')
|
||||
self.prune()
|
||||
self.n_call_ex_kw3 = call_ex_kw3
|
||||
|
||||
|
||||
def call36_tuple(node):
|
||||
"""
|
||||
A tuple used in a call, these are like normal tuples but they
|
||||
don't have the enclosing parenthesis.
|
||||
"""
|
||||
assert node == 'tuple'
|
||||
# Note: don't iterate over last element which is a
|
||||
# BUILD_TUPLE...
|
||||
flat_elems = flatten_list(node[:-1])
|
||||
|
||||
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 line_number != self.line_number:
|
||||
sep += '\n' + self.indent + INDENT_PER_LEVEL[:-1]
|
||||
self.write(sep, value)
|
||||
sep = ', '
|
||||
|
||||
self.indent_less(INDENT_PER_LEVEL)
|
||||
return len(flat_elems)
|
||||
self.call36_tuple = call36_tuple
|
||||
|
||||
def call36_dict(node):
|
||||
"""
|
||||
A dict used in a call_ex_kw2, which are a dictionary items expressed
|
||||
in a call. This should format to:
|
||||
a=1, b=2
|
||||
In other words, no braces, no quotes around keys and ":" becomes
|
||||
"=".
|
||||
|
||||
We will source-code use line breaks to guide us when to break.
|
||||
"""
|
||||
p = self.prec
|
||||
self.prec = 100
|
||||
|
||||
self.indent_more(INDENT_PER_LEVEL)
|
||||
sep = INDENT_PER_LEVEL[:-1]
|
||||
line_number = self.line_number
|
||||
|
||||
assert node[0].kind.startswith('kvlist')
|
||||
# Python 3.5+ style key/value list in dict
|
||||
kv_node = node[0]
|
||||
l = list(kv_node)
|
||||
i = 0
|
||||
# Respect line breaks from source
|
||||
while i < len(l):
|
||||
self.write(sep)
|
||||
name = self.traverse(l[i], indent='')
|
||||
# Strip off beginning and trailing quotes in name
|
||||
name = name[1:-1]
|
||||
if i > 0:
|
||||
line_number = self.indent_if_source_nl(line_number,
|
||||
self.indent + INDENT_PER_LEVEL[:-1])
|
||||
line_number = self.line_number
|
||||
self.write(name, '=')
|
||||
value = self.traverse(l[i+1], indent=self.indent+(len(name)+2)*' ')
|
||||
self.write(value)
|
||||
sep = ","
|
||||
if line_number != self.line_number:
|
||||
sep += "\n" + self.indent + INDENT_PER_LEVEL[:-1]
|
||||
line_number = self.line_number
|
||||
i += 2
|
||||
pass
|
||||
self.prec = p
|
||||
self.indent_less(INDENT_PER_LEVEL)
|
||||
return
|
||||
self.call36_dict = call36_dict
|
||||
|
||||
|
||||
FSTRING_CONVERSION_MAP = {1: '!s', 2: '!r', 3: '!a'}
|
||||
|
||||
def f_conversion(node):
|
||||
node.conversion = FSTRING_CONVERSION_MAP.get(node.data[1].attr, '')
|
||||
|
||||
def fstring_expr(node):
|
||||
f_conversion(node)
|
||||
self.default(node)
|
||||
self.n_fstring_expr = fstring_expr
|
||||
|
||||
def fstring_single(node):
|
||||
f_conversion(node)
|
||||
self.default(node)
|
||||
self.n_fstring_single = fstring_single
|
||||
|
||||
# def kwargs_only_36(node):
|
||||
# keys = node[-1].attr
|
||||
# num_kwargs = len(keys)
|
||||
# values = node[:num_kwargs]
|
||||
# for i, (key, value) in enumerate(zip(keys, values)):
|
||||
# self.write(key + '=')
|
||||
# self.preorder(value)
|
||||
# if i < num_kwargs:
|
||||
# self.write(',')
|
||||
# self.prune()
|
||||
# return
|
||||
# self.n_kwargs_only_36 = kwargs_only_36
|
||||
|
||||
def kwargs_36(node):
|
||||
self.write('(')
|
||||
keys = node[-1].attr
|
||||
num_kwargs = len(keys)
|
||||
num_posargs = len(node) - (num_kwargs + 1)
|
||||
n = len(node)
|
||||
assert n >= len(keys)+2
|
||||
sep = ''
|
||||
# FIXME: adjust output for line breaks?
|
||||
for i in range(num_posargs):
|
||||
self.write(sep)
|
||||
self.preorder(node[i])
|
||||
sep = ', '
|
||||
|
||||
i = num_posargs
|
||||
j = 0
|
||||
# FIXME: adjust output for line breaks?
|
||||
while i < n-1:
|
||||
self.write(sep)
|
||||
self.write(keys[j] + '=')
|
||||
self.preorder(node[i])
|
||||
i += 1
|
||||
j += 1
|
||||
self.write(')')
|
||||
self.prune()
|
||||
return
|
||||
self.n_kwargs_36 = kwargs_36
|
||||
|
||||
|
||||
def return_closure(node):
|
||||
# Nothing should be output here
|
||||
self.prune()
|
||||
return
|
||||
self.n_return_closure = return_closure
|
||||
pass # version > 3.6
|
||||
pass # version > 3.4
|
||||
pass # version > 3.0
|
||||
pass # version >= 3.4
|
||||
pass # version >= 3.0
|
||||
return
|
||||
|
||||
f = property(lambda s: s.params['f'],
|
||||
@@ -1801,17 +1531,21 @@ class SourceWalker(GenericASTTraversal, object):
|
||||
class_name = node[2][0].pattr
|
||||
else:
|
||||
class_name = node[1][2].pattr
|
||||
buildclass = node
|
||||
build_class = node
|
||||
else:
|
||||
build_class = node[0]
|
||||
if self.version >= 3.6:
|
||||
class_name = node[0][1][0].attr.co_name
|
||||
buildclass = node[0]
|
||||
if build_class[1][0] == 'load_closure':
|
||||
code_node = build_class[1][1]
|
||||
else:
|
||||
code_node = build_class[1][0]
|
||||
class_name = code_node.attr.co_name
|
||||
else:
|
||||
class_name = node[1][0].pattr
|
||||
buildclass = node[0]
|
||||
build_class = node[0]
|
||||
|
||||
assert 'mkfunc' == buildclass[1]
|
||||
mkfunc = buildclass[1]
|
||||
assert 'mkfunc' == build_class[1]
|
||||
mkfunc = build_class[1]
|
||||
if mkfunc[0] == 'kwargs':
|
||||
if 3.0 <= self.version <= 3.2:
|
||||
for n in mkfunc:
|
||||
@@ -1833,9 +1567,9 @@ class SourceWalker(GenericASTTraversal, object):
|
||||
subclass_info = node
|
||||
else:
|
||||
subclass_info = node[0]
|
||||
elif buildclass[1][0] == 'load_closure':
|
||||
elif build_class[1][0] == 'load_closure':
|
||||
# Python 3 with closures not functions
|
||||
load_closure = buildclass[1]
|
||||
load_closure = build_class[1]
|
||||
if hasattr(load_closure[-3], 'attr'):
|
||||
# Python 3.3 classes with closures work like this.
|
||||
# Note have to test before 3.2 case because
|
||||
@@ -1846,34 +1580,34 @@ class SourceWalker(GenericASTTraversal, object):
|
||||
subclass_code = load_closure[-2].attr
|
||||
else:
|
||||
raise 'Internal Error n_classdef: cannot find class body'
|
||||
if hasattr(buildclass[3], '__len__'):
|
||||
subclass_info = buildclass[3]
|
||||
elif hasattr(buildclass[2], '__len__'):
|
||||
subclass_info = buildclass[2]
|
||||
if hasattr(build_class[3], '__len__'):
|
||||
subclass_info = build_class[3]
|
||||
elif hasattr(build_class[2], '__len__'):
|
||||
subclass_info = build_class[2]
|
||||
else:
|
||||
raise 'Internal Error n_classdef: cannot superclass name'
|
||||
elif self.version >= 3.6 and node == 'classdefdeco2':
|
||||
subclass_info = node
|
||||
subclass_code = buildclass[1][0].attr
|
||||
subclass_code = build_class[1][0].attr
|
||||
else:
|
||||
subclass_code = buildclass[1][0].attr
|
||||
subclass_code = build_class[1][0].attr
|
||||
subclass_info = node[0]
|
||||
else:
|
||||
if node == 'classdefdeco2':
|
||||
buildclass = node
|
||||
build_class = node
|
||||
else:
|
||||
buildclass = node[0]
|
||||
build_list = buildclass[1][0]
|
||||
if hasattr(buildclass[-3][0], 'attr'):
|
||||
subclass_code = buildclass[-3][0].attr
|
||||
class_name = buildclass[0].pattr
|
||||
elif (buildclass[-3] == 'mkfunc' and
|
||||
build_class = node[0]
|
||||
build_list = build_class[1][0]
|
||||
if hasattr(build_class[-3][0], 'attr'):
|
||||
subclass_code = build_class[-3][0].attr
|
||||
class_name = build_class[0].pattr
|
||||
elif (build_class[-3] == 'mkfunc' and
|
||||
node == 'classdefdeco2' and
|
||||
buildclass[-3][0] == 'load_closure'):
|
||||
subclass_code = buildclass[-3][1].attr
|
||||
class_name = buildclass[-3][0][0].pattr
|
||||
build_class[-3][0] == 'load_closure'):
|
||||
subclass_code = build_class[-3][1].attr
|
||||
class_name = build_class[-3][0][0].pattr
|
||||
elif hasattr(node[0][0], 'pattr'):
|
||||
subclass_code = buildclass[-3][1].attr
|
||||
subclass_code = build_class[-3][1].attr
|
||||
class_name = node[0][0].pattr
|
||||
else:
|
||||
raise 'Internal Error n_classdef: cannot find class name'
|
||||
@@ -1954,13 +1688,13 @@ class SourceWalker(GenericASTTraversal, object):
|
||||
else:
|
||||
l = n
|
||||
while i < l:
|
||||
# 3.6+ may have this
|
||||
if kwargs:
|
||||
self.write("%s=" % kwargs[j])
|
||||
j += 1
|
||||
value = self.traverse(node[i])
|
||||
i += 1
|
||||
self.write(sep, value)
|
||||
# 3.6+ may have this
|
||||
if kwargs:
|
||||
self.write("=%s" % kwargs[j])
|
||||
j += 1
|
||||
sep = line_separator
|
||||
pass
|
||||
pass
|
||||
|
Reference in New Issue
Block a user