Compare commits

...

20 Commits

Author SHA1 Message Date
rocky
af10f99776 Get ready for release 2.11.0 2017-06-18 15:31:44 -04:00
rocky
0cbafa6e3a Adjust nodeInfo if it is a Token 2017-06-13 04:41:32 -04:00
rocky
4afaee2a36 Add nonterminal node in extractInfo 2017-06-13 04:17:23 -04:00
rocky
daea3c348c Fragment tag more expressions
Revise make_function3 comment wrt args and kwargs
2017-06-10 16:31:56 -04:00
rocky
bf45260588 Fragment tag array subscripts 2017-06-10 08:05:18 -04:00
R. Bernstein
34a356d237 Create README.rst 2017-06-10 06:21:36 -04:00
R. Bernstein
d9c1374a59 Create README.rst 2017-06-10 06:14:06 -04:00
rocky
2e05137f2b Set YIELD_VALUE offset in a <yield> expr 2017-06-10 02:09:58 -04:00
rocky
267ecda070 Python 3.2 MAKE_FUNCTION again..
Was handling bug32/01_named_and_kwargs.py wrong again
2017-06-10 01:42:50 -04:00
R. Bernstein
7e89839777 Merge pull request #119 from rocky/scan-longconstant
Simplify access to L65536 ...
2017-06-09 18:57:28 -04:00
rocky
c7f8edd5ef Simplify access to L65536 ...
and fix use in scanner26.py. Thanks to AnythingTechPro
2017-06-09 18:22:02 -04:00
rocky
6a991833a3 Attempt to document the MAKE_FUNCTION/MAKE_LAMBDA mess...
in Python 3.0+
2017-06-09 06:52:14 -04:00
rocky
28ee3f1257 Correct make_function3 for Pytohn 3.2 2017-06-08 21:49:13 -04:00
rocky
e9588e56e2 Disable "continue" removal in pysource.py
"continue" could be the only statement and then removing it
might lead to a dangling "else".
2017-06-08 04:35:06 -04:00
rocky
7b2217fda4 Mark "pass" offsets.
Start routine to find previous node.
2017-06-07 22:14:38 -04:00
rocky
5ca219f3d3 Remove hacky fragments try fixup...
hacky call_function code is also not needed or will be reinstated
properly. Better grammar structure for Python 3.6 call_function.
2017-06-06 21:58:47 -04:00
rocky
b733a1b036 BUILD_{MAP,TUPLE}_UNPACK & CALL_FUNCTION_EX_KW...
Bang on these in 3.6. Not totally succesfull right now.
In fact a regression on one of the test cases
2017-06-05 23:51:51 -04:00
rocky
4615cda03f Important fragments bug fix...
start, finish that had been adjusted wasn't getting reflected in final
returned deparsed.offsets dictionary. Redo keeping API compatibility,
i.e we still use namedtuple NodeInfo.
2017-06-05 21:17:17 -04:00
rocky
eb92418224 Python 3.5 *args with kwargs handling.
3.5 is a snowflake here. Thank you, Python.

Fully fixes Issue 95.

3.6 is broken on this source, but for a *different* reason. Sigh.
2017-06-04 17:53:51 -04:00
rocky
844221cd43 Small changes.
fragment tag EXEC_STMT
2017-06-03 23:29:46 -04:00
16 changed files with 353 additions and 128 deletions

111
ChangeLog
View File

@@ -1,6 +1,115 @@
2017-06-18 rocky <rb@dustyfeet.com>
* uncompyle6/version.py: Get ready for release 2.11.0
2017-06-13 rocky <rb@dustyfeet.com>
* uncompyle6/semantics/fragments.py: Adjust nodeInfo if it is a
Token
2017-06-13 rocky <rb@dustyfeet.com>
* uncompyle6/semantics/fragments.py: Add nonterminal node in
extractInfo
2017-06-10 rocky <rb@dustyfeet.com>
* uncompyle6/semantics/fragments.py,
uncompyle6/semantics/make_function.py: Fragment tag more expressions Revise make_function3 comment wrt args and kwargs
2017-06-10 rocky <rb@dustyfeet.com>
* uncompyle6/semantics/fragments.py: Fragment tag array subscripts
2017-06-10 R. Bernstein <rocky@users.noreply.github.com>
* README.rst: Create README.rst
2017-06-10 R. Bernstein <rocky@users.noreply.github.com>
* README.rst: Create README.rst
2017-06-10 rocky <rb@dustyfeet.com>
* uncompyle6/semantics/fragments.py: Set YIELD_VALUE offset in a
<yield> expr
2017-06-10 rocky <rb@dustyfeet.com>
* uncompyle6/semantics/make_function.py: Python 3.2 MAKE_FUNCTION
again.. Was handling bug32/01_named_and_kwargs.py wrong again
2017-06-09 R. Bernstein <rocky@users.noreply.github.com>
* : Merge pull request #119 from rocky/scan-longconstant Simplify access to L65536 ...
2017-06-09 rocky <rb@dustyfeet.com>
* uncompyle6/semantics/make_function.py: Attempt to document the
MAKE_FUNCTION/MAKE_LAMBDA mess... in Python 3.0+
2017-06-08 rocky <rb@dustyfeet.com>
* uncompyle6/semantics/make_function.py: Correct make_function3 for
Pytohn 3.2
2017-06-08 rocky <rb@dustyfeet.com>
* uncompyle6/semantics/pysource.py: Disable "continue" removal in
pysource.py "continue" could be the only statement and then removing it might
lead to a dangling "else".
2017-06-07 rocky <rb@dustyfeet.com>
* uncompyle6/semantics/fragments.py: Mark "pass" offsets. Start routine to find previous node.
2017-06-06 rocky <rb@dustyfeet.com>
* uncompyle6/parsers/parse3.py, uncompyle6/semantics/fragments.py:
Remove hacky fragments try fixup... hacky call_function code is also not needed or will be reinstated
properly. Better grammar structure for Python 3.6 call_function.
2017-06-05 rocky <rb@dustyfeet.com>
* uncompyle6/parsers/parse3.py, uncompyle6/parsers/parse36.py,
uncompyle6/scanners/scanner36.py: BUILD_{MAP,TUPLE}_UNPACK &
CALL_FUNCTION_EX_KW... Bang on these in 3.6. Not totally succesfull right now. In fact a
regression on one of the test cases
2017-06-05 rocky <rb@dustyfeet.com>
* uncompyle6/semantics/fragments.py: Important fragments bug fix... start, finish that had been adjusted wasn't getting reflected in
final returned deparsed.offsets dictionary. Redo keeping API
compatibility, i.e we still use namedtuple NodeInfo.
2017-06-04 rocky <rb@dustyfeet.com>
* uncompyle6/parsers/parse3.py, uncompyle6/semantics/pysource.py:
Python 3.5 *args with kwargs handling. 3.5 is a snowflake here. Thank you, Python. Fully fixes Issue 95. 3.6 is broken on this source, but for a *different* reason. Sigh.
2017-06-03 rocky <rb@dustyfeet.com> 2017-06-03 rocky <rb@dustyfeet.com>
* uncompyle6/version.py: Get ready for release 2.10.1 * README.rst, __pkginfo__.py,
test/simple_source/bug35/04_CALL_FUNCTION_VAR_KW.py,
uncompyle6/semantics/fragments.py: Small changes. fragment tag EXEC_STMT
2017-06-03 rocky <rb@dustyfeet.com>
* .travis.yml: Streamline .travis.yml a little bit
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> 2017-06-03 rocky <rb@dustyfeet.com>

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

@@ -496,8 +496,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 '
@@ -507,11 +507,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:
@@ -610,9 +616,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 "
@@ -633,6 +639,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) +
@@ -642,7 +660,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

@@ -25,12 +25,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

@@ -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>
""" """
@@ -25,14 +25,14 @@ from __future__ import print_function
from collections import namedtuple from collections import namedtuple
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
@@ -186,7 +186,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
@@ -178,7 +179,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
@@ -28,8 +28,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".
""" """
@@ -77,11 +77,12 @@ 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 collections import namedtuple from collections import namedtuple
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', ),
@@ -159,8 +160,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
@@ -176,6 +178,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']:
@@ -229,6 +259,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
@@ -366,6 +397,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
@@ -1089,7 +1121,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
@@ -1184,16 +1215,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
@@ -1557,25 +1610,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
@@ -1657,6 +1699,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
@@ -1691,6 +1740,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

@@ -428,10 +428,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:
@@ -451,21 +475,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)]
@@ -475,12 +509,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'
@@ -496,7 +524,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:
@@ -510,58 +538,27 @@ def make_function3(self, node, isLambda, nested=1, codeNode=None):
return return
kw_pairs = args_node.attr[1] if self.version >= 3.0 else 0 kw_pairs = args_node.attr[1] if self.version >= 3.0 else 0
indent = self.indent
# build parameters # build parameters
if self.version != 3.2: params = [build_param(ast, name, d) for
params = [build_param(ast, name, default) for name, d in zip_longest(paramnames, defparams, fillvalue=None)]
name, default in zip_longest(paramnames, defparams, fillvalue=None)]
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]))
suffix = ', ' if i > 0 else ''
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

@@ -358,9 +358,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:
@@ -547,25 +571,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'