Merge branch 'master' into python-2.4

This commit is contained in:
rocky
2017-06-18 15:40:40 -04:00
16 changed files with 282 additions and 130 deletions

View File

@@ -1,3 +1,43 @@
2017-06-03 rocky <rb@dustyfeet.com>
* pytest/validate.py: 2.4 doesn't do six
2017-06-03 rocky <rb@dustyfeet.com>
* appveyor.yml: Nope it (appveyor) doesn't.
2017-06-03 rocky <rb@dustyfeet.com>
* __pkginfo__.py, appveyor.yml: Administrivia See if appveyor will handle 2.5
2017-06-03 rocky <rb@dustyfeet.com>
* : commit 7c299fbf3777c452d6a10075964961783f510699 Author: rocky
<rb@dustyfeet.com> Date: Sat Jun 3 05:38:05 2017 -0400
2017-06-03 rocky <rb@dustyfeet.com>
* __pkginfo__.py: We need six
2017-06-03 rocky <rb@dustyfeet.com>
* README.rst, circle.yml, requirements-dev.txt: Go over
administrivia
2017-06-03 rocky <rb@dustyfeet.com>
* ChangeLog, NEWS, uncompyle6/version.py: Get ready for release
2.10.1
2017-06-03 rocky <rb@dustyfeet.com>
* uncompyle6/semantics/fragments.py,
uncompyle6/semantics/pysource.py: Fragment bugs fragment.py: * deparse_code_aorund_offset: was sometimes returning the wrong type * capture function name offset * lint imports pysource.py: use a clearer variable name
2017-06-02 rocky <rb@dustyfeet.com>
* uncompyle6/semantics/fragments.py: Track changes in ifelstmtr.. in fragments from pysource
2017-05-30 rocky <rb@dustyfeet.com> 2017-05-30 rocky <rb@dustyfeet.com>
* pytest/test_function_call.py: No decorators in Python < 2.6 * pytest/test_function_call.py: No decorators in Python < 2.6

10
NEWS
View File

@@ -1,3 +1,13 @@
uncompyle6 2.11.1 2016-06-18 Fleetwood
- Major improvements in fragment tracking
* Add nonterminal node in extractInfo
* tag more offsets in expressions
* tag array subscripts
* set YIELD value offset in a <yield> expr
* fix a long-standing bug in not adjusting final AST when melding other deparse ASTs
- Fixes yet again for make_function node handling; document what's up here
- Fix bug in snowflake Python 3.5 *args kwargs
uncompyle6 2.10.1 2016-06-3 Marylin Frankel uncompyle6 2.10.1 2016-06-3 Marylin Frankel
- fix some fragments parsing bugs - fix some fragments parsing bugs

View File

@@ -56,7 +56,7 @@ This uses setup.py, so it follows the standard Python routine:
:: ::
pip install -e setup.py pip install -e .
pip install -r requirements-dev.txt pip install -r requirements-dev.txt
python setup.py install # may need sudo python setup.py install # may need sudo
# or if you have pyenv: # or if you have pyenv:
@@ -171,7 +171,7 @@ See Also
* https://code.google.com/archive/p/unpyc3/ : supports Python 3.2 only. The above projects use a different decompiling technique than what is used here. * https://code.google.com/archive/p/unpyc3/ : supports Python 3.2 only. The above projects use a different decompiling technique than what is used here.
* https://github.com/figment/unpyc3/ : fork of above, but supports Python 3.3 only. Include some fixes like supporting function annotations * https://github.com/figment/unpyc3/ : fork of above, but supports Python 3.3 only. Include some fixes like supporting function annotations
* The HISTORY_ file. * The HISTORY_ file.
* `How to report a bug <https://github.com/rocky/python-uncompyle6/blob/master/HISTORY.md>`_
.. |downloads| image:: https://img.shields.io/pypi/dd/uncompyle6.svg .. |downloads| image:: https://img.shields.io/pypi/dd/uncompyle6.svg
.. _trepan: https://pypi.python.org/pypi/trepan .. _trepan: https://pypi.python.org/pypi/trepan
.. _HISTORY: https://github.com/rocky/python-uncompyle6/blob/master/HISTORY.md .. _HISTORY: https://github.com/rocky/python-uncompyle6/blob/master/HISTORY.md

View File

@@ -33,7 +33,7 @@ classifiers = ['Development Status :: 5 - Production/Stable',
# The rest in alphabetic order # The rest in alphabetic order
author = "Rocky Bernstein, Hartmut Goebel, John Aycock, and others" author = "Rocky Bernstein, Hartmut Goebel, John Aycock, and others"
author_email = "rb@dustyfeet.com" author_email = "rb@dustyfeet.com"
entry_points={ entry_points = {
'console_scripts': [ 'console_scripts': [
'uncompyle6=uncompyle6.bin.uncompile:main_bin', 'uncompyle6=uncompyle6.bin.uncompile:main_bin',
'pydisassemble=uncompyle6.bin.pydisassemble:main', 'pydisassemble=uncompyle6.bin.pydisassemble:main',

Binary file not shown.

View File

@@ -1,4 +1,5 @@
# sql/schema.py # sql/schema.py
# Note that kwargs comes before "positional" args
def tometadata(self, metadata, schema, Table, args, name=None): def tometadata(self, metadata, schema, Table, args, name=None):
table = Table( table = Table(
name, metadata, schema=schema, name, metadata, schema=schema,

View File

@@ -494,8 +494,8 @@ class Python3Parser(PythonParser):
token.type = self.call_fn_name(token) token.type = self.call_fn_name(token)
uniq_param = args_kw + args_pos uniq_param = args_kw + args_pos
if self.version == 3.5 and opname.startswith('CALL_FUNCTION_VAR'): if self.version == 3.5 and opname.startswith('CALL_FUNCTION_VAR'):
# Python 3.5 changes the stack position of where * args, the # Python 3.5 changes the stack position of *args. KW args come
# first LOAD_FAST, below are located. # after *args.
# Python 3.6+ replaces CALL_FUNCTION_VAR_KW with CALL_FUNCTION_EX # Python 3.6+ replaces CALL_FUNCTION_VAR_KW with CALL_FUNCTION_EX
if opname.endswith('KW'): if opname.endswith('KW'):
kw = 'expr ' kw = 'expr '
@@ -505,11 +505,17 @@ class Python3Parser(PythonParser):
('pos_arg ' * args_pos) + ('pos_arg ' * args_pos) +
('kwarg ' * args_kw) + kw + token.type) ('kwarg ' * args_kw) + kw + token.type)
self.add_unique_rule(rule, token.type, uniq_param, customize) self.add_unique_rule(rule, token.type, uniq_param, customize)
if self.version >= 3.6 and opname == 'CALL_FUNCTION_EX_KW':
rule = ('call_function ::= expr ' + rule = ('call_function36 ::= '
('pos_arg ' * args_pos) + 'expr build_tuple_unpack_with_call build_map_unpack_with_call '
('kwarg ' * args_kw) + 'CALL_FUNCTION_EX_KW_1')
'expr ' * nak + token.type) self.add_unique_rule(rule, token.type, uniq_param, customize)
rule = 'call_function ::= call_function36'
else:
rule = ('call_function ::= expr ' +
('pos_arg ' * args_pos) +
('kwarg ' * args_kw) +
'expr ' * nak + token.type)
self.add_unique_rule(rule, token.type, uniq_param, customize) self.add_unique_rule(rule, token.type, uniq_param, customize)
if self.version >= 3.5: if self.version >= 3.5:
@@ -611,9 +617,9 @@ class Python3Parser(PythonParser):
assign2_pypy ::= expr expr designator designator assign2_pypy ::= expr expr designator designator
""", nop_func) """, nop_func)
continue continue
elif opname in ('CALL_FUNCTION', 'CALL_FUNCTION_VAR', elif (opname in ('CALL_FUNCTION', 'CALL_FUNCTION_VAR',
'CALL_FUNCTION_VAR_KW') \ 'CALL_FUNCTION_VAR_KW', 'CALL_FUNCTION_EX_KW')
or opname.startswith('CALL_FUNCTION_KW'): or opname.startswith('CALL_FUNCTION_KW')):
self.custom_classfunc_rule(opname, token, customize) self.custom_classfunc_rule(opname, token, customize)
elif opname == 'LOAD_DICTCOMP': elif opname == 'LOAD_DICTCOMP':
rule_pat = ("dictcomp ::= LOAD_DICTCOMP %sMAKE_FUNCTION_0 expr " rule_pat = ("dictcomp ::= LOAD_DICTCOMP %sMAKE_FUNCTION_0 expr "
@@ -634,6 +640,18 @@ class Python3Parser(PythonParser):
self.add_unique_rule(rule, opname, token.attr, customize) self.add_unique_rule(rule, opname, token.attr, customize)
rule = 'expr ::= build_list_unpack' rule = 'expr ::= build_list_unpack'
self.add_unique_rule(rule, opname, token.attr, customize) self.add_unique_rule(rule, opname, token.attr, customize)
elif opname.startswith('BUILD_TUPLE_UNPACK_WITH_CALL'):
v = token.attr
rule = ('build_tuple_unpack_with_call ::= ' + 'expr1024 ' * int(v//1024) +
'expr32 ' * int((v//32) % 32) +
'expr ' * (v % 32) + opname)
self.add_unique_rule(rule, opname, token.attr, customize)
elif opname.startswith('BUILD_MAP_UNPACK_WITH_CALL'):
v = token.attr
rule = ('build_map_unpack_with_call ::= ' + 'expr1024 ' * int(v//1024) +
'expr32 ' * int((v//32) % 32) +
'expr ' * (v % 32) + opname)
self.add_unique_rule(rule, opname, token.attr, customize)
elif opname_base in ('BUILD_LIST', 'BUILD_TUPLE', 'BUILD_SET'): elif opname_base in ('BUILD_LIST', 'BUILD_TUPLE', 'BUILD_SET'):
v = token.attr v = token.attr
rule = ('build_list ::= ' + 'expr1024 ' * int(v//1024) + rule = ('build_list ::= ' + 'expr1024 ' * int(v//1024) +
@@ -643,7 +661,10 @@ class Python3Parser(PythonParser):
if opname_base == 'BUILD_TUPLE': if opname_base == 'BUILD_TUPLE':
rule = ('load_closure ::= %s%s' % (('LOAD_CLOSURE ' * v), opname)) rule = ('load_closure ::= %s%s' % (('LOAD_CLOSURE ' * v), opname))
self.add_unique_rule(rule, opname, token.attr, customize) self.add_unique_rule(rule, opname, token.attr, customize)
rule = ('build_tuple ::= ' + 'expr1024 ' * int(v//1024) +
'expr32 ' * int((v//32) % 32) +
'expr ' * (v % 32) + opname)
self.add_unique_rule(rule, opname, token.attr, customize)
elif opname == 'LOOKUP_METHOD': elif opname == 'LOOKUP_METHOD':
# A PyPy speciality - DRY with parse2 # A PyPy speciality - DRY with parse2
self.add_unique_rule("load_attr ::= expr LOOKUP_METHOD", self.add_unique_rule("load_attr ::= expr LOOKUP_METHOD",

View File

@@ -24,12 +24,13 @@ class Python36Parser(Python35Parser):
func_args36 ::= expr BUILD_TUPLE_0 func_args36 ::= expr BUILD_TUPLE_0
call_function ::= func_args36 unmapexpr CALL_FUNCTION_EX call_function ::= func_args36 unmapexpr CALL_FUNCTION_EX
call_function ::= func_args36 build_map_unpack_with_call CALL_FUNCTION_EX_KW_1
withstmt ::= expr SETUP_WITH POP_TOP suite_stmts_opt POP_BLOCK LOAD_CONST withstmt ::= expr SETUP_WITH POP_TOP suite_stmts_opt POP_BLOCK LOAD_CONST
WITH_CLEANUP_START WITH_CLEANUP_FINISH END_FINALLY WITH_CLEANUP_START WITH_CLEANUP_FINISH END_FINALLY
call_function ::= expr expr CALL_FUNCTION_EX call_function ::= expr expr CALL_FUNCTION_EX
call_function ::= expr expr expr CALL_FUNCTION_EX_KW call_function ::= expr expr expr CALL_FUNCTION_EX_KW_1
""" """
def add_custom_rules(self, tokens, customize): def add_custom_rules(self, tokens, customize):

View File

@@ -29,14 +29,14 @@ else:
from array import array from array import array
from uncompyle6.scanner import op_has_argument from uncompyle6.scanner import op_has_argument, L65536
from xdis.code import iscode from xdis.code import iscode
import uncompyle6.scanner as scan from uncompyle6.scanner import Scanner
class Scanner2(scan.Scanner): class Scanner2(Scanner):
def __init__(self, version, show_asm=None, is_pypy=False): def __init__(self, version, show_asm=None, is_pypy=False):
scan.Scanner.__init__(self, version, show_asm, is_pypy) Scanner.__init__(self, version, show_asm, is_pypy)
self.pop_jump_if = frozenset([self.opc.PJIF, self.opc.PJIT]) self.pop_jump_if = frozenset([self.opc.PJIF, self.opc.PJIT])
self.jump_forward = frozenset([self.opc.JUMP_ABSOLUTE, self.opc.JUMP_FORWARD]) self.jump_forward = frozenset([self.opc.JUMP_ABSOLUTE, self.opc.JUMP_FORWARD])
# This is the 2.5+ default # This is the 2.5+ default
@@ -192,7 +192,7 @@ class Scanner2(scan.Scanner):
oparg = self.get_argument(offset) + extended_arg oparg = self.get_argument(offset) + extended_arg
extended_arg = 0 extended_arg = 0
if op == self.opc.EXTENDED_ARG: if op == self.opc.EXTENDED_ARG:
extended_arg = oparg * scan.L65536 extended_arg = oparg * L65536
continue continue
if op in self.opc.hasconst: if op in self.opc.hasconst:
const = co.co_consts[oparg] const = co.co_consts[oparg]

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2015, 2016 by Rocky Bernstein # Copyright (c) 2015-2017 by Rocky Bernstein
# Copyright (c) 2005 by Dan Pascu <dan@windowmaker.org> # Copyright (c) 2005 by Dan Pascu <dan@windowmaker.org>
# Copyright (c) 2000-2002 by hartmut Goebel <h.goebel@crazy-compilers.com> # Copyright (c) 2000-2002 by hartmut Goebel <h.goebel@crazy-compilers.com>
""" """
@@ -15,6 +15,7 @@ if PYTHON3:
intern = sys.intern intern = sys.intern
import uncompyle6.scanners.scanner2 as scan import uncompyle6.scanners.scanner2 as scan
from uncompyle6.scanner import L65536
# bytecode verification, verify(), uses JUMP_OPs from here # bytecode verification, verify(), uses JUMP_OPs from here
from xdis.opcodes import opcode_26 from xdis.opcodes import opcode_26
@@ -180,7 +181,7 @@ class Scanner26(scan.Scanner2):
oparg = self.get_argument(offset) + extended_arg oparg = self.get_argument(offset) + extended_arg
extended_arg = 0 extended_arg = 0
if op == self.opc.EXTENDED_ARG: if op == self.opc.EXTENDED_ARG:
extended_arg = oparg * scan.L65536 extended_arg = oparg * L65536
continue continue
if op in self.opc.hasconst: if op in self.opc.hasconst:
const = co.co_consts[oparg] const = co.co_consts[oparg]

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2016 by Rocky Bernstein # Copyright (c) 2016-2017 by Rocky Bernstein
""" """
Python 3.6 bytecode scanner/deparser Python 3.6 bytecode scanner/deparser
@@ -26,8 +26,12 @@ class Scanner36(Scanner3):
if t.op == self.opc.CALL_FUNCTION_EX and t.attr & 1: if t.op == self.opc.CALL_FUNCTION_EX and t.attr & 1:
t.type = 'CALL_FUNCTION_EX_KW' t.type = 'CALL_FUNCTION_EX_KW'
pass pass
if t.op == self.opc.CALL_FUNCTION_KW: elif t.op == self.opc.CALL_FUNCTION_KW:
t.type = 'CALL_FUNCTION_KW_{t.attr}'.format(**locals()) t.type = 'CALL_FUNCTION_KW_{t.attr}'.format(**locals())
elif t.op == self.opc.BUILD_TUPLE_UNPACK_WITH_CALL:
t.type = 'BUILD_TUPLE_UNPACK_WITH_CALL_%d' % t.attr
elif t.op == self.opc.BUILD_MAP_UNPACK_WITH_CALL:
t.type = 'BUILD_MAP_UNPACK_WITH_CALL_%d' % t.attr
pass pass
return tokens, customize return tokens, customize

View File

@@ -44,7 +44,7 @@ do it recursively which is where offsets are probably located.
For example in: For example in:
'importmultiple': ( '%|import%b %c%c\n', 0, 2, 3 ), 'importmultiple': ( '%|import%b %c%c\n', 0, 2, 3 ),
n
The node position 0 will be associated with "import". The node position 0 will be associated with "import".
""" """
@@ -75,6 +75,7 @@ from uncompyle6.semantics.consts import (
) )
from spark_parser import DEFAULT_DEBUG as PARSER_DEFAULT_DEBUG from spark_parser import DEFAULT_DEBUG as PARSER_DEFAULT_DEBUG
from spark_parser.ast import GenericASTTraversalPruningException
from uncompyle6 import PYTHON_VERSION from uncompyle6 import PYTHON_VERSION
if PYTHON_VERSION < 2.6: if PYTHON_VERSION < 2.6:
@@ -84,7 +85,7 @@ else:
NodeInfo = namedtuple("NodeInfo", "node start finish") NodeInfo = namedtuple("NodeInfo", "node start finish")
ExtractInfo = namedtuple("ExtractInfo", ExtractInfo = namedtuple("ExtractInfo",
"lineNo lineStartOffset markerLine selectedLine selectedText") "lineNo lineStartOffset markerLine selectedLine selectedText nonterminal")
TABLE_DIRECT_FRAGMENT = { TABLE_DIRECT_FRAGMENT = {
'break_stmt': ( '%|%rbreak\n', ), 'break_stmt': ( '%|%rbreak\n', ),
@@ -162,8 +163,9 @@ class FragmentsWalker(pysource.SourceWalker, object):
def set_pos_info(self, node, start, finish, name=None): def set_pos_info(self, node, start, finish, name=None):
if name is None: name = self.name if name is None: name = self.name
if hasattr(node, 'offset'): if hasattr(node, 'offset'):
self.offsets[name, node.offset] = \ node.start = start
NodeInfo(node = node, start = start, finish = finish) node.finish = finish
self.offsets[name, node.offset] = node
if hasattr(node, 'parent'): if hasattr(node, 'parent'):
assert node.parent != node assert node.parent != node
@@ -179,6 +181,34 @@ class FragmentsWalker(pysource.SourceWalker, object):
return return
def table_r_node(self, node):
"""General pattern where the last node should should
get the text span attributes of the entire tree"""
start = len(self.f.getvalue())
try:
self.default(node)
except GenericASTTraversalPruningException:
final = len(self.f.getvalue())
self.set_pos_info(node, start, final)
self.set_pos_info(node[-1], start, final)
raise GenericASTTraversalPruningException
n_slice0 = n_slice1 = n_slice2 = n_slice3 = n_binary_subscr = table_r_node
n_augassign_1 = n_print_item = exec_stmt = print_to_item = del_stmt = table_r_node
n_classdefco1 = n_classdefco2 = except_cond1 = except_cond2 = table_r_node
def n_passtmt(self, node):
start = len(self.f.getvalue()) + len(self.indent)
self.set_pos_info(node, start, start+len("pass"))
self.default(node)
def n_trystmt(self, node):
start = len(self.f.getvalue()) + len(self.indent)
self.set_pos_info(node[0], start, start+len("try:"))
self.default(node)
n_tryelsestmt = n_tryelsestmtc = n_tryelsestmtl = n_tryfinallystmt = n_trystmt
def n_return_stmt(self, node): def n_return_stmt(self, node):
start = len(self.f.getvalue()) + len(self.indent) start = len(self.f.getvalue()) + len(self.indent)
if self.params['isLambda']: if self.params['isLambda']:
@@ -232,6 +262,7 @@ class FragmentsWalker(pysource.SourceWalker, object):
self.write(' ') self.write(' ')
node[0].parent = node node[0].parent = node
self.preorder(node[0]) self.preorder(node[0])
self.set_pos_info(node[-1], start, len(self.f.getvalue()))
self.set_pos_info(node, start, len(self.f.getvalue())) self.set_pos_info(node, start, len(self.f.getvalue()))
self.prune() # stop recursing self.prune() # stop recursing
@@ -369,6 +400,7 @@ class FragmentsWalker(pysource.SourceWalker, object):
self.write(sep); sep = ", " self.write(sep); sep = ", "
self.preorder(subnode) self.preorder(subnode)
self.set_pos_info(node, start, len(self.f.getvalue())) self.set_pos_info(node, start, len(self.f.getvalue()))
self.set_pos_info(node[-1], start, len(self.f.getvalue()))
self.println() self.println()
self.prune() # stop recursing self.prune() # stop recursing
@@ -1105,7 +1137,6 @@ class FragmentsWalker(pysource.SourceWalker, object):
def traverse(self, node, indent=None, isLambda=False): def traverse(self, node, indent=None, isLambda=False):
'''Buulds up fragment which can be used inside a larger '''Buulds up fragment which can be used inside a larger
block of code''' block of code'''
self.param_stack.append(self.params) self.param_stack.append(self.params)
if indent is None: indent = self.indent if indent is None: indent = self.indent
p = self.pending_newlines p = self.pending_newlines
@@ -1200,16 +1231,38 @@ class FragmentsWalker(pysource.SourceWalker, object):
if elided: selectedLine += ' ...' if elided: selectedLine += ' ...'
if isinstance(nodeInfo, Token):
nodeInfo = nodeInfo.parent
else:
nodeInfo = nodeInfo
if isinstance(nodeInfo, AST):
nonterminal = nodeInfo[0]
else:
nonterminal = nodeInfo.node
return ExtractInfo(lineNo = len(lines), lineStartOffset = lineStart, return ExtractInfo(lineNo = len(lines), lineStartOffset = lineStart,
markerLine = markerLine, markerLine = markerLine,
selectedLine = selectedLine, selectedLine = selectedLine,
selectedText = selectedText) selectedText = selectedText,
nonterminal = nonterminal)
def extract_line_info(self, name, offset): def extract_line_info(self, name, offset):
if (name, offset) not in list(self.offsets.keys()): if (name, offset) not in list(self.offsets.keys()):
return None return None
return self.extract_node_info(self.offsets[name, offset]) return self.extract_node_info(self.offsets[name, offset])
def prev_node(self, node):
prev = None
if not hasattr(node, 'parent'):
return prev
p = node.parent
for n in p:
if node == n:
return prev
prev = n
return prev
def extract_parent_info(self, node): def extract_parent_info(self, node):
if not hasattr(node, 'parent'): if not hasattr(node, 'parent'):
return None, None return None, None
@@ -1573,25 +1626,14 @@ class FragmentsWalker(pysource.SourceWalker, object):
self.set_pos_info(startnode, startnode_start, fin) self.set_pos_info(startnode, startnode_start, fin)
# FIXME rocky: figure out how to get these casess to be table driven. # FIXME rocky: figure out how to get these casess to be table driven.
#
# 1. for loops. For loops have two positions that correspond to a single text
# location. In "for i in ..." there is the initialization "i" code as well
# as the iteration code with "i". A "copy" spec like %X3,3 - copy parame
# 3 to param 2 would work
#
# 2. subroutine calls. It the last op is the call and for purposes of printing # 2. subroutine calls. It the last op is the call and for purposes of printing
# we don't need to print anything special there. However it encompases the # we don't need to print anything special there. However it encompases the
# entire string of the node fn(...) # entire string of the node fn(...)
match = re.search(r'^try', startnode.type) match = re.search(r'^call_function', startnode.type)
if match: if match:
self.set_pos_info(node[0], startnode_start, startnode_start+len("try:")) last_node = startnode[-1]
self.set_pos_info(node[2], node[3].finish, node[3].finish) # import traceback; traceback.print_stack()
else: self.set_pos_info(last_node, startnode_start, self.last_finish)
match = re.search(r'^call_function', startnode.type)
if match:
last_node = startnode[-1]
# import traceback; traceback.print_stack()
self.set_pos_info(last_node, startnode_start, self.last_finish)
return return
@classmethod @classmethod
@@ -1673,6 +1715,13 @@ def deparse_code(version, co, out=StringIO(), showasm=False, showast=False,
if deparsed.ERROR: if deparsed.ERROR:
raise deparsed.ERROR raise deparsed.ERROR
# To keep the API consistent with previous releases, convert
# deparse.offset values into NodeInfo items
for tup, node in deparsed.offsets.items():
deparsed.offsets[tup] = NodeInfo(node = node, start = node.start,
finish = node.finish)
deparsed.scanner = scanner
return deparsed return deparsed
from bisect import bisect_right from bisect import bisect_right
@@ -1707,6 +1756,7 @@ def deparse_code_around_offset(name, offset, version, co, out=StringIO(),
if __name__ == '__main__': if __name__ == '__main__':
from uncompyle6 import IS_PYPY
def deparse_test(co, is_pypy=IS_PYPY): def deparse_test(co, is_pypy=IS_PYPY):
sys_version = sys.version_info.major + (sys.version_info.minor / 10.0) sys_version = sys.version_info.major + (sys.version_info.minor / 10.0)
walk = deparse_code(sys_version, co, showasm=False, showast=False, walk = deparse_code(sys_version, co, showasm=False, showast=False,

View File

@@ -430,10 +430,34 @@ def make_function2(self, node, isLambda, nested=1, codeNode=None):
def make_function3(self, node, isLambda, nested=1, codeNode=None): def make_function3(self, node, isLambda, nested=1, codeNode=None):
"""Dump function definition, doc string, and function body.""" """Dump function definition, doc string, and function body in
Python version 3.0 and above
"""
# FIXME: call make_function3 if we are self.version >= 3.0 # For Python 3.3, the evaluation stack in MAKE_FUNCTION is:
# and then simplify the below.
# * default argument objects in positional order
# * pairs of name and default argument, with the name just below
# the object on the stack, for keyword-only parameters
# * parameter annotation objects
# * a tuple listing the parameter names for the annotations
# (only if there are ony annotation objects)
# * the code associated with the function (at TOS1)
# * the qualified name of the function (at TOS)
# For Python 3.0 .. 3.2 the evaluation stack is:
# The function object is defined to have argc default parameters,
# which are found below TOS.
# * first come positional args in the order they are given in the source,
# * next come the keyword args in the order they given in the source,
# * finally is the code associated with the function (at TOS)
#
# Note: There is no qualified name at TOS
# MAKE_CLOSURE adds an additional closure slot
# Thank you, Python, for a such a well-thought out system that has
# changed 4 or so times.
def build_param(ast, name, default): def build_param(ast, name, default):
"""build parameters: """build parameters:
@@ -453,21 +477,31 @@ def make_function3(self, node, isLambda, nested=1, codeNode=None):
# MAKE_FUNCTION_... or MAKE_CLOSURE_... # MAKE_FUNCTION_... or MAKE_CLOSURE_...
assert node[-1].type.startswith('MAKE_') assert node[-1].type.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] args_node = node[-1]
if isinstance(args_node.attr, tuple): if isinstance(args_node.attr, tuple):
if self.version <= 3.3 and len(node) > 2 and node[-3] != 'LOAD_LAMBDA': pos_args, kw_args, annotate_argc = args_node.attr
# positional args are after kwargs if self.version <= 3.3 and len(node) > 2 and node[lambda_index] != 'LOAD_LAMBDA':
# args are after kwargs; kwargs are bundled as one node
defparams = node[1:args_node.attr[0]+1] defparams = node[1:args_node.attr[0]+1]
else: else:
# positional args are before kwargs # args are before kwargs; kwags as bundled as one node
defparams = node[:args_node.attr[0]] defparams = node[:args_node.attr[0]]
pos_args, kw_args, annotate_argc = args_node.attr
else: else:
if self.version < 3.6: if self.version < 3.6:
defparams = node[:args_node.attr] defparams = node[:args_node.attr]
else: else:
default, kw, annotate, closure = args_node.attr default, kw, annotate, closure = args_node.attr
# FIXME: start here. # FIXME: start here for Python 3.6 and above:
defparams = [] defparams = []
# if default: # if default:
# defparams = node[-(2 + kw + annotate + closure)] # defparams = node[-(2 + kw + annotate + closure)]
@@ -477,12 +511,6 @@ def make_function3(self, node, isLambda, nested=1, codeNode=None):
kw_args = 0 kw_args = 0
pass pass
if 3.0 <= self.version <= 3.2:
lambda_index = -2
elif 3.03 <= self.version:
lambda_index = -3
else:
lambda_index = None
if lambda_index and isLambda and iscode(node[lambda_index].attr): if lambda_index and isLambda and iscode(node[lambda_index].attr):
assert node[lambda_index].type == 'LOAD_LAMBDA' assert node[lambda_index].type == 'LOAD_LAMBDA'
@@ -498,7 +526,7 @@ def make_function3(self, node, isLambda, nested=1, codeNode=None):
paramnames = list(code.co_varnames[:argc]) paramnames = list(code.co_varnames[:argc])
# defaults are for last n parameters, thus reverse # defaults are for last n parameters, thus reverse
if not 3.0 <= self.version <= 3.2: if not 3.0 <= self.version <= 3.1:
paramnames.reverse(); defparams.reverse() paramnames.reverse(); defparams.reverse()
try: try:
@@ -515,62 +543,27 @@ def make_function3(self, node, isLambda, nested=1, codeNode=None):
kw_pairs = args_node.attr[1] kw_pairs = args_node.attr[1]
else: else:
kw_pairs = 0 kw_pairs = 0
indent = self.indent
# build parameters # build parameters
if self.version != 3.2: params = [build_param(ast, name, d) for
tup = [paramnames, defparams] name, d in zip_longest(paramnames, defparams, fillvalue=None)]
params = [build_param(ast, name, default) for
name, default in map(lambda *tup:tup, *tup)] if not 3.0 <= self.version <= 3.1:
params.reverse() # back to correct order params.reverse() # back to correct order
if code_has_star_arg(code): if code_has_star_arg(code):
if self.version > 3.0: if self.version > 3.0:
params.append('*%s' % code.co_varnames[argc + kw_pairs]) params.append('*%s' % code.co_varnames[argc + kw_pairs])
else:
params.append('*%s' % code.co_varnames[argc])
argc += 1
# dump parameter list (with default values)
if isLambda:
self.write("lambda ", ", ".join(params))
else: else:
self.write("(", ", ".join(params)) params.append('*%s' % code.co_varnames[argc])
# self.println(indent, '#flags:\t', int(code.co_flags)) argc += 1
# dump parameter list (with default values)
if isLambda:
self.write("lambda ", ", ".join(params))
else: else:
if isLambda: self.write("(", ", ".join(params))
self.write("lambda ") # self.println(indent, '#flags:\t', int(code.co_flags))
else:
self.write("(")
pass
last_line = self.f.getvalue().split("\n")[-1]
l = len(last_line)
indent = ' ' * l
line_number = self.line_number
if code_has_star_arg(code):
self.write('*%s' % code.co_varnames[argc + kw_pairs])
argc += 1
i = len(paramnames) - len(defparams)
self.write(", ".join(paramnames[:i]))
if i > 0:
suffix = ', '
else:
suffix = ''
for n in node:
if n == 'pos_arg':
self.write(suffix)
self.write(paramnames[i] + '=')
i += 1
self.preorder(n)
if (line_number != self.line_number):
suffix = ",\n" + indent
line_number = self.line_number
else:
suffix = ', '
if kw_args > 0: if kw_args > 0:
if not (4 & code.co_flags): if not (4 & code.co_flags):

View File

@@ -356,9 +356,33 @@ class SourceWalker(GenericASTTraversal, object):
self.prec = p self.prec = p
self.prune() self.prune()
self.n_async_call_function = n_async_call_function self.n_async_call_function = n_async_call_function
self.n_build_list_unpack = self.n_build_list self.n_build_list_unpack = self.n_build_list
if version == 3.5:
def n_call_function(node):
mapping = self._get_mapping(node)
table = mapping[0]
key = node
for i in mapping[1:]:
key = key[i]
pass
if key.type.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 simpilfiies 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.type]
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
self.default(node)
self.n_call_function = n_call_function
def n_funcdef(node): def n_funcdef(node):
if self.version == 3.6: if self.version == 3.6:
@@ -545,25 +569,32 @@ class SourceWalker(GenericASTTraversal, object):
node == AST('return_stmt', node == AST('return_stmt',
[AST('ret_expr', [NONE]), Token('RETURN_VALUE')])) [AST('ret_expr', [NONE]), Token('RETURN_VALUE')]))
def n_continue_stmt(self, node): ## The below doesn't work because continue may be the only thing inside an 'else'. For example
if self.version >= 3.0 and node[0] == 'CONTINUE': # for ...
t = node[0] # if ...
if not t.linestart: # else:
# Artificially-added "continue" statements derived from JUMP_ABSOLUTE # continue
# don't have line numbers associated with them. #
# If this is a CONTINUE is to the same target as a JUMP_ABSOLUTE following it, # def n_continue_stmt(self, node):
# then the "continue" can be suppressed. # if self.version >= 3.0 and node[0] == 'CONTINUE':
op, offset = t.op, t.offset # t = node[0]
next_offset = self.scanner.next_offset(op, offset) # if not t.linestart:
scanner = self.scanner # # Artificially-added "continue" statements derived from JUMP_ABSOLUTE
code = scanner.code # # don't have line numbers associated with them.
if next_offset < len(code): # # If this is a CONTINUE is to the same target as a JUMP_ABSOLUTE following it,
next_inst = code[next_offset] # # then the "continue" can be suppressed.
if (scanner.opc.opname[next_inst] == 'JUMP_ABSOLUTE' # op, offset = t.op, t.offset
and t.pattr == code[next_offset+1]): # next_offset = self.scanner.next_offset(op, offset)
# Suppress "continue" # scanner = self.scanner
self.prune() # code = scanner.code
self.default(node) # if next_offset < len(code):
# next_inst = code[next_offset]
# if (scanner.opc.opname[next_inst] == 'JUMP_ABSOLUTE'
# and t.pattr == code[next_offset+1]):
# # Suppress "continue"
# import pdb; pdb.set_trace()
# self.prune()
# self.default(node)
def n_return_stmt(self, node): def n_return_stmt(self, node):
if self.params['isLambda']: if self.params['isLambda']:

View File

@@ -1,3 +1,3 @@
# This file is suitable for sourcing inside bash as # This file is suitable for sourcing inside bash as
# well as importing into Python # well as importing into Python
VERSION='2.10.1' VERSION='2.11.0'