Files
python-uncompyle6/uncompyle6/semantics/fragments.py
2017-12-01 03:55:31 -05:00

1835 lines
64 KiB
Python

# Copyright (c) 2015-2017 by Rocky Bernstein
# Copyright (c) 1999 John Aycock
"""
Creates Python source code from an uncompyle6 abstract syntax tree,
and indexes fragments which can be accessed by instruction offset
address.
See https://github.com/rocky/python-uncompyle6/wiki/Table-driven-semantic-actions.
for a more complete explanation, nicely marked up and with examples.
We add some format specifiers here not used in pysource
1. %x
-----
%x takes an argument (src, (dest...)) and copies all of the range attributes
from src to dest.
For example in:
'import': ( '%|import %c%x\n', 2, (2,(0,1)), ),
node 2 range information, it in %c, is copied to nodes 0 and 1.
2. %r
-----
%r associates recursively location information for the string that follows
For example in:
'break_stmt': ( '%|%rbreak\n', ),
The node will be associated with the text break, excluding the trailing newline.
Note we associate the accumulated text with the node normally, but we just don't
do it recursively which is where offsets are probably located.
2. %b
-----
%b associates the text from the specified index to what we have now.
it takes an integer argument.
For example in:
'importmultiple': ( '%|import%b %c%c\n', 0, 2, 3 ),
n
The node position 0 will be associated with "import".
"""
# FIXME: DRY code with pysource
from __future__ import print_function
import re, sys
from xdis.code import iscode
from uncompyle6.semantics import pysource
from uncompyle6 import parser
from uncompyle6.scanner import Token, Code, get_scanner
from uncompyle6.semantics.check_ast import checker
from uncompyle6.show import (
maybe_show_asm,
maybe_show_ast,
)
from uncompyle6.parsers.astnode import AST
from uncompyle6.semantics.pysource import (
ParserError, StringIO)
from uncompyle6.semantics.consts import (
INDENT_PER_LEVEL, NONE, PRECEDENCE,
TABLE_DIRECT, escape, minint, MAP
)
from spark_parser import DEFAULT_DEBUG as PARSER_DEFAULT_DEBUG
from spark_parser.ast import GenericASTTraversalPruningException
from collections import namedtuple
NodeInfo = namedtuple("NodeInfo", "node start finish")
ExtractInfo = namedtuple("ExtractInfo",
"lineNo lineStartOffset markerLine selectedLine selectedText nonterminal")
TABLE_DIRECT_FRAGMENT = {
'break_stmt': ( '%|%rbreak\n', ),
'continue_stmt': ( '%|%rcontinue\n', ),
'passstmt': ( '%|%rpass\n', ),
'raise_stmt0': ( '%|%rraise\n', ),
'import': ( '%|import %c%x\n', 2, (2, (0, 1)), ),
'importfrom': ( '%|from %[2]{pattr}%x import %c\n', (2, (0, 1)), 3),
'importmultiple': ( '%|import%b %c%c\n', 0, 2, 3 ),
'list_for': (' for %c%x in %c%c', 2, (2, (1, )), 0, 3 ),
'forelsestmt': (
'%|for %c%x in %c:\n%+%c%-%|else:\n%+%c%-\n\n', 3, (3, (2,)), 1, 4, -2),
'forelselaststmt': (
'%|for %c%x in %c:\n%+%c%-%|else:\n%+%c%-', 3, (3, (2,)), 1, 4, -2),
'forelselaststmtl': (
'%|for %c%x in %c:\n%+%c%-%|else:\n%+%c%-\n\n', 3, (3, (2, )), 1, 4, -2),
'whilestmt': ( '%|while%b %c:\n%+%c%-\n\n', 0, 1, 2 ),
'whileelsestmt': ( '%|while%b %c:\n%+%c%-%|else:\n%+%c%-\n\n', 0, 1, 2, -2 ),
'whileelselaststmt': ( '%|while%b %c:\n%+%c%-%|else:\n%+%c%-', 0, 1, 2, -2 ),
'forstmt': ( '%|for%b %c in %c:\n%+%c%-\n\n', 0, 3, 1, 4 ),
}
class FragmentsWalker(pysource.SourceWalker, object):
MAP_DIRECT_FRAGMENT = ()
stacked_params = ('f', 'indent', 'is_lambda', '_globals')
def __init__(self, version, scanner, showast=False,
debug_parser=PARSER_DEFAULT_DEBUG,
compile_mode='exec', is_pypy=False, tolerate_errors=True):
pysource.SourceWalker.__init__(self, version=version, out=StringIO(),
scanner=scanner,
showast=showast, debug_parser=debug_parser,
compile_mode=compile_mode, is_pypy=is_pypy,
tolerate_errors=tolerate_errors)
# hide_internal suppresses displaying the additional instructions that sometimes
# exist in code but but were not written in the source code.
# An example is:
# __module__ = __name__
# If showing source code we generally don't want to show this. However in fragment
# deparsing we generally do need to see these instructions since we may be stopped
# at one. So here we do not want to suppress showing such instructions.
self.hide_internal = False
self.offsets = {}
self.last_finish = -1
# FIXME: is there a better way?
global MAP_DIRECT_FRAGMENT
MAP_DIRECT_FRAGMENT = dict(TABLE_DIRECT, **TABLE_DIRECT_FRAGMENT),
return
f = property(lambda s: s.params['f'],
lambda s, x: s.params.__setitem__('f', x),
lambda s: s.params.__delitem__('f'),
None)
indent = property(lambda s: s.params['indent'],
lambda s, x: s.params.__setitem__('indent', x),
lambda s: s.params.__delitem__('indent'),
None)
is_lambda = property(lambda s: s.params['is_lambda'],
lambda s, x: s.params.__setitem__('is_lambda', x),
lambda s: s.params.__delitem__('is_lambda'),
None)
_globals = property(lambda s: s.params['_globals'],
lambda s, x: s.params.__setitem__('_globals', x),
lambda s: s.params.__delitem__('_globals'),
None)
def set_pos_info(self, node, start, finish, name=None):
if name is None: name = self.name
if hasattr(node, 'offset'):
node.start = start
node.finish = finish
self.offsets[name, node.offset] = node
if hasattr(node, 'parent'):
assert node.parent != node
node.start = start
node.finish = finish
self.last_finish = finish
def preorder(self, node=None):
start = len(self.f.getvalue())
super(pysource.SourceWalker, self).preorder(node)
self.set_pos_info(node, start, len(self.f.getvalue()))
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_subscript = table_r_node
n_aug_assign_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):
start = len(self.f.getvalue()) + len(self.indent)
if self.params['is_lambda']:
self.preorder(node[0])
if hasattr(node[-1], 'offset'):
self.set_pos_info(node[-1], start,
len(self.f.getvalue()))
self.prune()
else:
start = len(self.f.getvalue()) + len(self.indent)
self.write(self.indent, 'return')
if self.return_none or node != AST('return_stmt', [AST('ret_expr', [NONE]), Token('RETURN_VALUE')]):
self.write(' ')
self.last_finish = len(self.f.getvalue())
self.preorder(node[0])
if hasattr(node[-1], 'offset'):
self.set_pos_info(node[-1], start, len(self.f.getvalue()))
pass
pass
else:
for n in node:
self.set_pos_info_recurse(n, start, len(self.f.getvalue()))
pass
pass
self.set_pos_info(node, start, len(self.f.getvalue()))
self.println()
self.prune() # stop recursing
def n_return_if_stmt(self, node):
start = len(self.f.getvalue()) + len(self.indent)
if self.params['is_lambda']:
node[0].parent = node
self.preorder(node[0])
else:
start = len(self.f.getvalue()) + len(self.indent)
self.write(self.indent, 'return')
if self.return_none or node != AST('return_stmt', [AST('ret_expr', [NONE]), Token('RETURN_END_IF')]):
self.write(' ')
self.preorder(node[0])
if hasattr(node[-1], 'offset'):
self.set_pos_info(node[-1], start, len(self.f.getvalue()))
self.println()
self.set_pos_info(node, start, len(self.f.getvalue()))
self.prune() # stop recursing
def n_yield(self, node):
start = len(self.f.getvalue())
self.write('yield')
if node != AST('yield', [NONE, Token('YIELD_VALUE')]):
self.write(' ')
node[0].parent = node
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.prune() # stop recursing
# In Python 3.3+ only
def n_yield_from(self, node):
start = len(self.f.getvalue())
self.write('yield from')
self.write(' ')
node[0].parent = node
self.preorder(node[0])
self.set_pos_info(node, start, len(self.f.getvalue()))
self.prune() # stop recursing
def n_buildslice3(self, node):
start = len(self.f.getvalue())
p = self.prec
self.prec = 100
if node[0] != NONE:
self.preorder(node[0])
self.write(':')
if node[1] != NONE:
self.preorder(node[1])
self.write(':')
if node[2] != NONE:
self.preorder(node[2])
self.prec = p
self.set_pos_info(node, start, len(self.f.getvalue()))
self.prune() # stop recursing
def n_buildslice2(self, node):
start = len(self.f.getvalue())
p = self.prec
self.prec = 100
if node[0] != NONE:
node[0].parent = node
self.preorder(node[0])
self.write(':')
if node[1] != NONE:
node[1].parent = node
self.preorder(node[1])
self.prec = p
self.set_pos_info(node, start, len(self.f.getvalue()))
self.prune() # stop recursing
def n_expr(self, node):
start = len(self.f.getvalue())
p = self.prec
if node[0].kind.startswith('binary_expr'):
n = node[0][-1][0]
else:
n = node[0]
self.prec = PRECEDENCE.get(n.kind, -2)
if n == 'LOAD_CONST' and repr(n.pattr)[0] == '-':
n.parent = node
self.set_pos_info(n, start, len(self.f.getvalue()))
self.prec = 6
if p < self.prec:
self.write('(')
node[0].parent = node
self.last_finish = len(self.f.getvalue())
self.preorder(node[0])
finish = len(self.f.getvalue())
if hasattr(node[0], 'offset'):
self.set_pos_info(node[0], start, len(self.f.getvalue()))
self.write(')')
self.last_finish = finish + 1
else:
node[0].parent = node
start = len(self.f.getvalue())
self.preorder(node[0])
if hasattr(node[0], 'offset'):
self.set_pos_info(node[0], start, len(self.f.getvalue()))
self.prec = p
self.set_pos_info(node, start, len(self.f.getvalue()))
self.prune()
def n_ret_expr(self, node):
start = len(self.f.getvalue())
if len(node) == 1 and node[0] == 'expr':
node[0].parent = node
self.n_expr(node[0])
else:
self.n_expr(node)
self.set_pos_info(node, start, len(self.f.getvalue()))
def n_binary_expr(self, node):
start = len(self.f.getvalue())
node[0].parent = node
self.last_finish = len(self.f.getvalue())
self.preorder(node[0])
self.write(' ')
node[-1].parent = node
self.preorder(node[-1])
self.write(' ')
self.prec -= 1
node[1].parent = node
self.preorder(node[1])
self.prec += 1
self.set_pos_info(node, start, len(self.f.getvalue()))
self.prune()
def n_LOAD_CONST(self, node):
start = len(self.f.getvalue())
data = node.pattr; datatype = type(data)
if isinstance(datatype, int) and data == minint:
# convert to hex, since decimal representation
# would result in 'LOAD_CONST; UNARY_NEGATIVE'
# change:hG/2002-02-07: this was done for all negative integers
# todo: check whether this is necessary in Python 2.1
self.write( hex(data) )
elif datatype is type(Ellipsis):
self.write('...')
elif data is None:
# LOAD_CONST 'None' only occurs, when None is
# implicit eg. in 'return' w/o params
# pass
self.write('None')
else:
self.write(repr(data))
self.set_pos_info(node, start, len(self.f.getvalue()))
# LOAD_CONST is a terminal, so stop processing/recursing early
self.prune()
def n_exec_stmt(self, node):
"""
exec_stmt ::= expr exprlist DUP_TOP EXEC_STMT
exec_stmt ::= expr exprlist EXEC_STMT
"""
start = len(self.f.getvalue()) + len(self.indent)
self.write(self.indent, 'exec ')
self.preorder(node[0])
if node[1][0] != NONE:
sep = ' in '
for subnode in node[1]:
self.write(sep); sep = ", "
self.preorder(subnode)
self.set_pos_info(node, start, len(self.f.getvalue()))
self.set_pos_info(node[-1], start, len(self.f.getvalue()))
self.println()
self.prune() # stop recursing
def n_ifelsestmtr(self, node):
if node[2] == 'COME_FROM':
return_stmts_node = node[3]
node.kind = 'ifelsestmtr2'
else:
return_stmts_node = node[2]
if len(return_stmts_node) != 2:
self.default(node)
if (not (return_stmts_node[0][0][0] == 'ifstmt'
and return_stmts_node[0][0][0][1][0] == 'return_if_stmts')
and not (return_stmts_node[0][-1][0] == 'ifstmt'
and return_stmts_node[0][-1][0][1][0] == 'return_if_stmts')):
self.default(node)
return
start = len(self.f.getvalue()) + len(self.indent)
self.write(self.indent, 'if ')
self.preorder(node[0])
self.println(':')
self.indent_more()
node[1].parent = node
self.preorder(node[1])
self.indent_less()
if_ret_at_end = False
if len(node[2][0]) >= 3:
node[2][0].parent = node
if node[2][0][-1][0] == 'ifstmt' and node[2][0][-1][0][1][0] == 'return_if_stmts':
if_ret_at_end = True
past_else = False
prev_stmt_is_if_ret = True
for n in return_stmts_node[0]:
if (n[0] == 'ifstmt' and n[0][1][0] == 'return_if_stmts'):
if prev_stmt_is_if_ret:
n[0].kind = 'elifstmt'
prev_stmt_is_if_ret = True
else:
prev_stmt_is_if_ret = False
if not past_else and not if_ret_at_end:
self.println(self.indent, 'else:')
self.indent_more()
past_else = True
n.parent = node
self.preorder(n)
if not past_else or if_ret_at_end:
self.println(self.indent, 'else:')
self.indent_more()
node[2][1].parent = node
self.preorder(node[2][1])
self.set_pos_info(node, start, len(self.f.getvalue()))
self.indent_less()
self.prune()
def n_elifelsestmtr(self, node):
if len(node[2]) != 2:
self.default(node)
for n in node[2][0]:
if not (n[0] == 'ifstmt' and n[0][1][0] == 'return_if_stmts'):
self.default(node)
return
start = len(self.f.getvalue() + self.indent)
self.write(self.indent, 'elif ')
node[0].parent = node
self.preorder(node[0])
self.println(':')
self.indent_more()
node[1].parent = node
self.preorder(node[1])
self.indent_less()
for n in node[2][0]:
n[0].kind = 'elifstmt'
n.parent = node
self.preorder(n)
self.println(self.indent, 'else:')
self.indent_more()
node[2][1].parent = node
self.preorder(node[2][1])
self.indent_less()
self.set_pos_info(node, start, len(self.f.getvalue()))
self.prune()
def n_alias(self, node):
start = len(self.f.getvalue())
iname = node[0].pattr
store_import_node = node[-1][-1]
assert store_import_node.kind.startswith('STORE_')
sname = store_import_node.pattr
self.write(iname)
finish = len(self.f.getvalue())
if iname == sname or iname.startswith(sname + '.'):
self.set_pos_info_recurse(node, start, finish)
else:
self.write(' as ')
sname_start = len(self.f.getvalue())
self.write(sname)
finish = len(self.f.getvalue())
for n in node[-1]:
self.set_pos_info_recurse(n, sname_start, finish)
self.set_pos_info(node, start, finish)
self.prune() # stop recursing
def n_mkfunc(self, node):
start = len(self.f.getvalue())
if self.version >= 3.3 or node[-2] == 'kwargs':
# LOAD_CONST code object ..
# LOAD_CONST 'x0' if >= 3.3
# MAKE_FUNCTION ..
code_node = node[-3]
elif node[-2] == 'expr':
code_node = node[-2][0]
else:
# LOAD_CONST code object ..
# MAKE_FUNCTION ..
code_node = node[-2]
func_name = code_node.attr.co_name
self.write(func_name)
self.set_pos_info(code_node, start, len(self.f.getvalue()))
self.indent_more()
start = len(self.f.getvalue())
self.make_function(node, is_lambda=False, codeNode=code_node)
self.set_pos_info(node, start, len(self.f.getvalue()))
if len(self.param_stack) > 1:
self.write('\n\n')
else:
self.write('\n\n\n')
self.indent_less()
self.prune() # stop recursing
def n_list_comp(self, node):
"""List comprehensions"""
p = self.prec
self.prec = 27
n = node[-1]
assert n == 'list_iter'
# find innermost node
while n == 'list_iter':
n = n[0] # recurse one step
if n == 'list_for': n = n[3]
elif n == 'list_if': n = n[2]
elif n == 'list_if_not': n= n[2]
assert n == 'lc_body'
if node[0].kind.startswith('BUILD_LIST'):
start = len(self.f.getvalue())
self.set_pos_info(node[0], start, start+1)
self.write( '[ ')
self.preorder(n[0]) # lc_body
self.preorder(node[-1]) # for/if parts
self.write( ' ]')
self.prec = p
self.prune() # stop recursing
def comprehension_walk(self, node, iter_index, code_index=-5):
p = self.prec
self.prec = 27
# FIXME: clean this up
if self.version > 3.0 and node == 'dict_comp':
cn = node[1]
elif self.version > 3.0 and node == 'generator_exp':
if node[0] == 'load_genexpr':
load_genexpr = node[0]
elif node[1] == 'load_genexpr':
load_genexpr = node[1]
cn = load_genexpr[0]
elif hasattr(node[code_index], 'attr'):
# Python 2.5+ (and earlier?) does this
cn = node[code_index]
else:
if len(node[1]) > 1 and hasattr(node[1][1], 'attr'):
# Python 3.3+ does this
cn = node[1][1]
elif hasattr(node[1][0], 'attr'):
# Python 3.2 does this
cn = node[1][0]
else:
assert False, "Can't find code for comprehension"
assert iscode(cn.attr)
code = Code(cn.attr, self.scanner, self.currentclass)
ast = self.build_ast(code._tokens, code._customize)
self.customize(code._customize)
ast = ast[0][0][0]
n = ast[iter_index]
assert n == 'comp_iter'
# find innermost node
while n == 'comp_iter':
n = n[0] # recurse one step
if n == 'comp_for': n = n[3]
elif n == 'comp_if': n = n[2]
elif n == 'comp_ifnot': n = n[2]
assert n == 'comp_body', ast
self.preorder(n[0])
self.write(' for ')
start = len(self.f.getvalue())
store = ast[iter_index-1]
self.preorder(store)
self.set_pos_info(ast[iter_index-1], start, len(self.f.getvalue()))
self.write(' in ')
start = len(self.f.getvalue())
node[-3].parent = node
self.preorder(node[-3])
self.set_pos_info(node[-3], start, len(self.f.getvalue()))
start = len(self.f.getvalue())
self.preorder(ast[iter_index])
self.set_pos_info(ast[iter_index], start, len(self.f.getvalue()))
self.prec = p
def comprehension_walk3(self, node, iter_index, code_index=-5):
"""
List comprehensions the way they are done in Python3.
They're more other comprehensions, e.g. set comprehensions
See if we can combine code.
"""
p = self.prec
self.prec = 27
code = node[code_index].attr
assert iscode(code)
code_name = code.co_name
code = Code(code, self.scanner, self.currentclass)
ast = self.build_ast(code._tokens, code._customize)
self.customize(code._customize)
# skip over stmts sstmt smt
ast = ast[0][0][0]
store = None
if ast in ['setcomp_func', 'dictcomp_func']:
# Offset 0: BUILD_SET should have the span
# of '{'
self.gen_source(ast, code_name, {})
for k in ast:
if k == 'comp_iter':
n = k
elif k == 'store':
store = k
pass
pass
pass
else:
ast = ast[0][0]
n = ast[iter_index]
assert n == 'list_iter'
# FIXME: I'm not totally sure this is right.
# find innermost node
if_node = None
comp_for = None
comp_store = None
if n == 'comp_iter':
comp_for = n
comp_store = ast[3]
have_not = False
while n in ('list_iter', 'comp_iter'):
n = n[0] # recurse one step
if n == 'list_for':
if n[2] == 'store':
store = n[2]
n = n[3]
elif n in ['list_if', 'list_if_not', 'comp_if']:
have_not = n in ('list_if_not', 'comp_ifnot')
if_node = n[0]
if n[1] == 'store':
store = n[1]
n = n[2]
pass
pass
# Python 2.7+ starts including set_comp_body
# Python 3.5+ starts including setcomp_func
assert n.kind in ('lc_body', 'comp_body', 'setcomp_func', 'set_comp_body'), ast
assert store, "Couldn't find store in list/set comprehension"
old_name = self.name
self.name = code_name
self.preorder(n[0])
gen_start = len(self.f.getvalue()) + 1
self.write(' for ')
start = len(self.f.getvalue())
self.preorder(store)
self.set_pos_info(store, start, len(self.f.getvalue()))
self.write(' in ')
start = len(self.f.getvalue())
node[-3].parent = node
self.preorder(node[-3])
fin = len(self.f.getvalue())
self.set_pos_info(node[-3], start, fin, old_name)
if comp_store:
self.preorder(comp_for)
elif if_node:
self.write(' if ')
if have_not:
self.write('not ')
self.preorder(if_node)
self.prec = p
self.name = old_name
if node[-1].kind.startswith('CALL_FUNCTION'):
self.set_pos_info(node[-1], gen_start, len(self.f.getvalue()))
def listcomprehension_walk2(self, node):
"""List comprehensions the way they are done in Python3.
They're more other comprehensions, e.g. set comprehensions
See if we can combine code.
"""
p = self.prec
self.prec = 27
code = Code(node[1].attr, self.scanner, self.currentclass)
ast = self.build_ast(code._tokens, code._customize)
self.customize(code._customize)
ast = ast[0][0][0][0][0]
n = ast[1]
collection = node[-3]
list_if = None
assert n == 'list_iter'
# find innermost node
while n == 'list_iter':
n = n[0] # recurse one step
if n == 'list_for':
store = n[2]
n = n[3]
elif n in ('list_if', 'list_if_not'):
# FIXME: just a guess
if n[0].kind == 'expr':
list_if = n
else:
list_if = n[1]
n = n[2]
pass
pass
assert n == 'lc_body', ast
self.preorder(n[0])
self.write(' for ')
start = len(self.f.getvalue())
self.preorder(store)
self.set_pos_info(store, start, len(self.f.getvalue()))
self.write(' in ')
start = len(self.f.getvalue())
node[-3].parent = node
self.preorder(collection)
self.set_pos_info(collection, start, len(self.f.getvalue()))
if list_if:
start = len(self.f.getvalue())
self.preorder(list_if)
self.set_pos_info(list_if, start, len(self.f.getvalue()))
self.prec = p
def n_generator_exp(self, node):
start = len(self.f.getvalue())
self.write('(')
code_index = -6 if self.version > 3.2 else -5
self.comprehension_walk(node, iter_index=3, code_index=code_index)
self.write(')')
self.set_pos_info(node, start, len(self.f.getvalue()))
self.prune()
def n_set_comp(self, node):
start = len(self.f.getvalue())
self.write('{')
if node[0] in ['LOAD_SETCOMP', 'LOAD_DICTCOMP']:
start = len(self.f.getvalue())
self.set_pos_info(node[0], start-1, start)
self.comprehension_walk3(node, 1, 0)
elif node[0].kind == 'load_closure':
self.setcomprehension_walk3(node, collection_index=4)
else:
self.comprehension_walk(node, iter_index=4)
self.write('}')
self.set_pos_info(node, start, len(self.f.getvalue()))
self.prune()
# FIXME: Not sure if below is general. Also, add dict_comp_func.
# 'setcomp_func': ("%|lambda %c: {%c for %c in %c%c}\n", 1, 3, 3, 1, 4)
def n_setcomp_func(self, node):
setcomp_start = len(self.f.getvalue())
self.write(self.indent, "lambda ")
param_node = node[1]
start = len(self.f.getvalue())
self.preorder(param_node)
self.set_pos_info(node[0], start, len(self.f.getvalue()))
self.write(': {')
start = len(self.f.getvalue())
assert node[0].kind.startswith('BUILD_SET')
self.set_pos_info(node[0], start-1, start)
store = node[3]
assert store == 'store'
start = len(self.f.getvalue())
self.preorder(store)
fin = len(self.f.getvalue())
self.set_pos_info(store, start, fin)
for_iter_node = node[2]
assert for_iter_node.kind == 'FOR_ITER'
self.set_pos_info(for_iter_node, start, fin)
self.write(" for ")
self.preorder(store)
self.write(" in ")
self.preorder(param_node)
start = len(self.f.getvalue())
self.preorder(node[4])
self.set_pos_info(node[4], start, len(self.f.getvalue()))
self.write("}")
fin = len(self.f.getvalue())
self.set_pos_info(node, setcomp_start, fin)
if node[-2] == 'RETURN_VALUE':
self.set_pos_info(node[-2], setcomp_start, fin)
self.prune()
def n_listcomp(self, node):
self.write('[')
if node[0].kind == 'load_closure':
self.listcomprehension_walk2(node)
else:
if node[0] == 'LOAD_LISTCOMP':
start = len(self.f.getvalue())
self.set_pos_info(node[0], start-1, start)
self.comprehension_walk3(node, 1, 0)
self.write(']')
self.prune()
def n__ifstmts_jump_exit(self, node):
if len(node) > 1:
if (node[0] == 'c_stmts_opt' and
node[0][0] == 'passstmt' and
node[1].kind.startswith('JUMP_FORWARD')):
self.set_pos_info(node[1], node[0][0].start, node[0][0].finish)
def setcomprehension_walk3(self, node, collection_index):
"""List comprehensions the way they are done in Python3.
They're more other comprehensions, e.g. set comprehensions
See if we can combine code.
"""
p = self.prec
self.prec = 27
code = Code(node[1].attr, self.scanner, self.currentclass)
ast = self.build_ast(code._tokens, code._customize)
self.customize(code._customize)
ast = ast[0][0][0]
store = ast[3]
collection = node[collection_index]
n = ast[4]
list_if = None
assert n == 'comp_iter'
# find innermost node
while n == 'comp_iter':
n = n[0] # recurse one step
# FIXME: adjust for set comprehension
if n == 'list_for':
store = n[2]
n = n[3]
elif n in ('list_if', 'list_if_not', 'comp_if', 'comp_if_not'):
# FIXME: just a guess
if n[0].kind == 'expr':
list_if = n
else:
list_if = n[1]
n = n[2]
pass
pass
assert n == 'comp_body', ast
self.preorder(n[0])
self.write(' for ')
start = len(self.f.getvalue())
self.preorder(store)
self.set_pos_info(store, start, len(self.f.getvalue()))
self.write(' in ')
start = len(self.f.getvalue())
self.preorder(collection)
self.set_pos_info(collection, start, len(self.f.getvalue()))
if list_if:
start = len(self.f.getvalue())
self.preorder(list_if)
self.set_pos_info(list_if, start, len(self.f.getvalue()))
self.prec = p
def n_classdef(self, node):
# class definition ('class X(A,B,C):')
cclass = self.currentclass
if self.version > 3.0:
if node == 'classdefdeco2':
currentclass = node[1][2].pattr
buildclass = node
else:
currentclass = node[1][0].pattr
buildclass = node[0]
if buildclass[0] == 'LOAD_BUILD_CLASS':
start = len(self.f.getvalue())
self.set_pos_info(buildclass[0], start, start + len('class')+2)
assert 'mkfunc' == buildclass[1]
mkfunc = buildclass[1]
if mkfunc[0] == 'kwargs':
for n in mkfunc:
if hasattr(n, 'attr') and iscode(n.attr):
subclass = n.attr
break
pass
subclass_info = node if node == 'classdefdeco2' else node[0]
elif buildclass[1][0] == 'load_closure':
# Python 3 with closures not functions
load_closure = buildclass[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
# index -2 also has an attr.
subclass = load_closure[-3].attr
elif hasattr(load_closure[-2], 'attr'):
# Python 3.2 works like this
subclass = 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]
else:
raise 'Internal Error n_classdef: cannot superclass name'
else:
subclass = buildclass[1][0].attr
subclass_info = node[0]
else:
buildclass = node if (node == 'classdefdeco2') else node[0]
build_list = buildclass[1][0]
if hasattr(buildclass[-3][0], 'attr'):
subclass = buildclass[-3][0].attr
currentclass = buildclass[0].pattr
elif hasattr(node[0][0], 'pattr'):
subclass = buildclass[-3][1].attr
currentclass = node[0][0].pattr
else:
raise 'Internal Error n_classdef: cannot find class name'
if (node == 'classdefdeco2'):
self.write('\n')
else:
self.write('\n\n')
self.currentclass = str(currentclass)
start = len(self.f.getvalue())
self.write(self.indent, 'class ', self.currentclass)
if self.version > 3.0:
self.print_super_classes3(subclass_info)
else:
self.print_super_classes(build_list)
self.println(':')
# class body
self.indent_more()
self.build_class(subclass)
self.indent_less()
self.currentclass = cclass
self.set_pos_info(node, start, len(self.f.getvalue()))
if len(self.param_stack) > 1:
self.write('\n\n')
else:
self.write('\n\n\n')
self.prune()
n_classdefdeco2 = n_classdef
def gen_source(self, ast, name, customize, is_lambda=False, returnNone=False):
"""convert AST to Python source code"""
rn = self.return_none
self.return_none = returnNone
old_name = self.name
self.name = name
# if code would be empty, append 'pass'
if len(ast) == 0:
self.println(self.indent, 'pass')
else:
self.customize(customize)
self.text = self.traverse(ast, is_lambda=is_lambda)
self.name = old_name
self.return_none = rn
def build_ast(self, tokens, customize, is_lambda=False, noneInNames=False):
# assert type(tokens) == ListType
# assert isinstance(tokens[0], Token)
if is_lambda:
tokens.append(Token('LAMBDA_MARKER'))
try:
ast = parser.parse(self.p, tokens, customize)
except (parser.ParserError, AssertionError) as e:
raise ParserError(e, tokens)
maybe_show_ast(self.showast, ast)
return ast
# The bytecode for the end of the main routine has a
# "return None". However you can't issue a "return" statement in
# main. In the other build_ast routine we eliminate the
# return statement instructions before parsing.
# But here we want to keep these instructions at the expense of
# a fully runnable Python program because we
# my be queried about the role of one of those instructions.
#
# NOTE: this differs from behavior in pysource.py
if len(tokens) >= 2 and not noneInNames:
if tokens[-1].kind == 'RETURN_VALUE':
if tokens[-2].kind != 'LOAD_CONST':
tokens.append(Token('RETURN_LAST'))
if len(tokens) == 0:
return
# Build AST from disassembly.
try:
ast = parser.parse(self.p, tokens, customize)
except (parser.ParserError, AssertionError) as e:
raise ParserError(e, tokens)
maybe_show_ast(self.showast, ast)
checker(ast, False, self.ast_errors)
return ast
# FIXME: we could provide another customized routine
# that fixes up parents along a particular path to a node that
# we are interested in.
def fixup_parents(self, node, parent):
"""Make sure each node has a parent"""
start, finish = 0, self.last_finish
# We assume anything with a start has a finish.
needs_range = not hasattr(node, 'start')
if not hasattr(node, 'parent'):
node.parent = parent
for n in node:
if needs_range and hasattr(n, 'start'):
if n.start < start: start = n.start
if n.finish > finish: finish = n.finish
if hasattr(n, 'offset') and not hasattr(n, 'parent'):
n.parent = node
else:
self.fixup_parents(n, node)
pass
pass
if needs_range:
node.start, node.finish = start, finish
return
# FIXME: revise to do *once* over the entire tree.
# So here we should just mark that the subtree
# needs offset adjustment.
def fixup_offsets(self, new_start, node):
"""Adjust all offsets under node"""
if hasattr(node, 'start'):
node.start += new_start
node.finish += new_start
for n in node:
if hasattr(n, 'offset'):
if hasattr(n, 'start'):
n.start += new_start
n.finish += new_start
else:
self.fixup_offsets(new_start, n)
return
def set_pos_info_recurse(self, node, start, finish, parent=None):
"""Set positions under node"""
self.set_pos_info(node, start, finish)
if parent is None:
parent = node
for n in node:
n.parent = parent
if hasattr(n, 'offset'):
self.set_pos_info(n, start, finish)
else:
n.start = start
n.finish = finish
self.set_pos_info_recurse(n, start, finish, parent)
return
def node_append(self, before_str, node_text, node):
self.write(before_str)
self.last_finish = len(self.f.getvalue())
self.fixup_offsets(self.last_finish, node)
self.write(node_text)
self.last_finish = len(self.f.getvalue())
# FIXME: duplicated from pysource, since we don't find self.params
def traverse(self, node, indent=None, is_lambda=False):
'''Buulds up fragment which can be used inside a larger
block of code'''
self.param_stack.append(self.params)
if indent is None: indent = self.indent
p = self.pending_newlines
self.pending_newlines = 0
self.params = {
'_globals': {},
'f': StringIO(),
'indent': indent,
'is_lambda': is_lambda,
}
self.preorder(node)
self.f.write('\n'*self.pending_newlines)
text = self.f.getvalue()
self.last_finish = len(text)
self.params = self.param_stack.pop()
self.pending_newlines = p
return text
def extract_node_info(self, nodeInfo):
# XXX debug
# print('-' * 30)
# node = nodeInfo.node
# print(node)
# if hasattr(node, 'parent'):
# print('~' * 30)
# print(node.parent)
# else:
# print("No parent")
# print('-' * 30)
start, finish = (nodeInfo.start, nodeInfo.finish)
text = self.text
# Ignore trailing blanks
match = re.search(r'\n+$', text[start:])
if match:
text = text[:-len(match.group(0))]
# Ignore leading blanks
match = re.search(r'\s*[^ \t\n]', text[start:])
if match:
start += len(match.group(0))-1
at_end = False
if start >= finish:
at_end = True
selectedText = text
else:
selectedText = text[start:finish]
# Compute offsets relative to the beginning of the
# line rather than the beinning of the text
try:
lineStart = text[:start].rindex("\n") + 1
except ValueError:
lineStart = 0
adjustedStart = start - lineStart
# If selected text is greater than a single line
# just show the first line plus elipses.
lines = selectedText.split("\n")
if len(lines) > 1:
adjustedEnd = len(lines[0]) - adjustedStart
selectedText = lines[0] + " ...\n" + lines[-1]
else:
adjustedEnd = len(selectedText)
if at_end:
markerLine = (' ' * len(lines[-1])) + '^'
else:
markerLine = ((' ' * adjustedStart) +
('-' * adjustedEnd))
elided = False
if len(lines) > 1 and not at_end:
elided = True
markerLine += ' ...'
# Get line that the selected text is in and
# get a line count for that.
try:
lineEnd = lineStart + text[lineStart+1:].index("\n") - 1
except ValueError:
lineEnd = len(text)
lines = text[:lineEnd].split("\n")
selectedLine = text[lineStart:lineEnd+2]
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,
markerLine = markerLine,
selectedLine = selectedLine,
selectedText = selectedText,
nonterminal = nonterminal)
def extract_line_info(self, name, offset):
if (name, offset) not in list(self.offsets.keys()):
return None
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):
if not hasattr(node, 'parent'):
return None, None
p = node.parent
orig_parent = p
# If we can get different text, use that as the parent,
# otherwise we'll use the immeditate parent
while (p and (hasattr(p, 'parent') and
p.start == node.start and p.finish == node.finish)):
assert p != node
node = p
p = p.parent
if p is None: p = orig_parent
return self.extract_node_info(p), p
def print_super_classes(self, node):
if not (node == 'build_list'):
return
start = len(self.f.getvalue())
self.write('(')
line_separator = ', '
sep = ''
for elem in node[:-1]:
value = self.traverse(elem)
self.node_append(sep, value, elem)
# self.write(sep, value)
sep = line_separator
self.write(')')
self.set_pos_info(node, start, len(self.f.getvalue()))
def print_super_classes3(self, node):
# FIXME: wrap superclasses onto a node
# as a custom rule
start = len(self.f.getvalue())
n = len(node)-1
assert node[n].kind.startswith('CALL_FUNCTION')
for i in range(n-2, 0, -1):
if not node[i].kind in ['expr', 'LOAD_CLASSNAME']:
break
pass
if i == n-2:
return
self.write('(')
line_separator = ', '
sep = ''
i += 1
while i < n:
value = self.traverse(node[i])
self.node_append(sep, value, node[i])
i += 1
self.write(sep, value)
sep = line_separator
self.write(')')
self.set_pos_info(node, start, len(self.f.getvalue()))
def n_dict(self, node):
"""
prettyprint a dict
'dict' is something like k = {'a': 1, 'b': 42 }"
"""
p = self.prec
self.prec = 100
self.indent_more(INDENT_PER_LEVEL)
line_seperator = ',\n' + self.indent
sep = INDENT_PER_LEVEL[:-1]
start = len(self.f.getvalue())
self.write('{')
if self.version > 3.0:
if node[0].kind.startswith('kvlist'):
# Python 3.5+ style key/value list in dict
kv_node = node[0]
l = list(kv_node)
i = 0
while i < len(l):
l[i].parent = kv_node
l[i+1].parent = kv_node
name = self.traverse(l[i], indent='')
value = self.traverse(l[i+1], indent=self.indent+(len(name)+2)*' ')
self.write(sep, name, ': ', value)
sep = line_seperator
i += 2
pass
pass
elif node[1].kind.startswith('kvlist'):
# Python 3.0..3.4 style key/value list in dict
kv_node = node[1]
l = list(kv_node)
if len(l) > 0 and l[0].kind == 'kv3':
# Python 3.2 does this
kv_node = node[1][0]
l = list(kv_node)
i = 0
while i < len(l):
l[i].parent = kv_node
l[i+1].parent = kv_node
key_start = len(self.f.getvalue()) + len(sep)
name = self.traverse(l[i+1], indent='')
key_finish = key_start + len(name)
val_start = key_finish + 2
value = self.traverse(l[i], indent=self.indent+(len(name)+2)*' ')
self.write(sep, name, ': ', value)
self.set_pos_info_recurse(l[i+1], key_start, key_finish)
self.set_pos_info_recurse(l[i], val_start, val_start + len(value))
sep = line_seperator
i += 3
pass
pass
pass
else:
# Python 2 style kvlist
assert node[-1].kind.startswith('kvlist')
kv_node = node[-1] # goto kvlist
for kv in kv_node:
assert kv in ('kv', 'kv2', 'kv3')
# kv ::= DUP_TOP expr ROT_TWO expr STORE_SUBSCR
# kv2 ::= DUP_TOP expr expr ROT_THREE STORE_SUBSCR
# kv3 ::= expr expr STORE_MAP
if kv == 'kv':
name = self.traverse(kv[-2], indent='')
kv[1].parent = kv_node
value = self.traverse(kv[1], indent=self.indent+(len(name)+2)*' ')
elif kv == 'kv2':
name = self.traverse(kv[1], indent='')
kv[-3].parent = kv_node
value = self.traverse(kv[-3], indent=self.indent+(len(name)+2)*' ')
elif kv == 'kv3':
name = self.traverse(kv[-2], indent='')
kv[0].parent = kv_node
value = self.traverse(kv[0], indent=self.indent+(len(name)+2)*' ')
self.write(sep, name, ': ', value)
sep = line_seperator
self.write('}')
finish = len(self.f.getvalue())
for n in node:
n.parent = node
self.set_pos_info(n, start, finish)
self.set_pos_info(node, start, finish)
self.indent_less(INDENT_PER_LEVEL)
self.prec = p
self.prune()
def n_list(self, node):
"""
prettyprint a list or tuple
"""
p = self.prec
self.prec = 100
n = node.pop()
lastnode = n.kind
start = len(self.f.getvalue())
if lastnode.startswith('BUILD_LIST'):
self.write('['); endchar = ']'
elif lastnode.startswith('BUILD_TUPLE'):
self.write('('); endchar = ')'
elif lastnode.startswith('BUILD_SET'):
self.write('{'); endchar = '}'
elif lastnode.startswith('ROT_TWO'):
self.write('('); endchar = ')'
else:
raise RuntimeError('Internal Error: n_list expects list or tuple')
flat_elems = []
for elem in node:
if elem == 'expr1024':
for subelem in elem:
for subsubelem in subelem:
flat_elems.append(subsubelem)
elif elem == 'expr32':
for subelem in elem:
flat_elems.append(subelem)
else:
flat_elems.append(elem)
self.indent_more(INDENT_PER_LEVEL)
if len(node) > 3:
line_separator = ',\n' + self.indent
else:
line_separator = ', '
sep = INDENT_PER_LEVEL[:-1]
# FIXME:
# if flat_elems > some_number, then group
# do automatic wrapping
for elem in flat_elems:
if (elem == 'ROT_THREE'):
continue
assert elem == 'expr'
value = self.traverse(elem)
self.node_append(sep, value, elem)
sep = line_separator
if len(node) == 1 and lastnode.startswith('BUILD_TUPLE'):
self.write(',')
self.write(endchar)
finish = len(self.f.getvalue())
n.parent = node.parent
self.set_pos_info(n, start, finish)
self.set_pos_info(node, start, finish)
self.indent_less(INDENT_PER_LEVEL)
self.prec = p
self.prune()
def template_engine(self, entry, startnode):
"""The format template interpetation engine. See the comment at the
beginning of this module for the how we interpret format
specifications such as %c, %C, and so on.
"""
# print("-----")
# print(startnode)
# print(entry[0])
# print('======')
startnode_start = len(self.f.getvalue())
start = startnode_start
fmt = entry[0]
arg = 1
i = 0
lastC = -1
recurse_node = False
m = escape.search(fmt)
while m:
i = m.end()
self.write(m.group('prefix'))
typ = m.group('type') or '{'
node = startnode
try:
if m.group('child'):
node = node[int(m.group('child'))]
node.parent = startnode
except:
print(node.__dict__)
raise
if typ == '%':
start = len(self.f.getvalue())
self.write('%')
self.set_pos_info(node, start, len(self.f.getvalue()))
elif typ == '+': self.indent_more()
elif typ == '-': self.indent_less()
elif typ == '|': self.write(self.indent)
# no longer used, since BUILD_TUPLE_n is pretty printed:
elif typ == 'r': recurse_node = True
elif typ == ',':
if lastC == 1:
self.write(',')
elif typ == 'b':
finish = len(self.f.getvalue())
self.set_pos_info(node[entry[arg]], start, finish)
arg += 1
elif typ == 'c':
start = len(self.f.getvalue())
index = entry[arg]
if isinstance(index, tuple):
assert node[index[0]] == index[1], (
"at %s[%d], %s vs %s" % (
node.kind, arg, node[index[0]].kind, index[1])
)
index = index[0]
if isinstance(index, int):
self.preorder(node[index])
finish = len(self.f.getvalue())
# FIXME rocky: figure out how to get this to be table driven
# 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"
match = re.search(r'^for', startnode.kind)
if match and entry[arg] == 3:
self.set_pos_info(node[0], start, finish)
for n in node[2]:
self.set_pos_info(n, start, finish)
self.set_pos_info(node, start, finish)
arg += 1
elif typ == 'p':
p = self.prec
(index, self.prec) = entry[arg]
node[index].parent = node
start = len(self.f.getvalue())
self.preorder(node[index])
self.set_pos_info(node, start, len(self.f.getvalue()))
self.prec = p
arg += 1
elif typ == 'C':
low, high, sep = entry[arg]
lastC = remaining = len(node[low:high])
start = len(self.f.getvalue())
for subnode in node[low:high]:
self.preorder(subnode)
remaining -= 1
if remaining > 0:
self.write(sep)
self.set_pos_info(node, start, len(self.f.getvalue()))
arg += 1
elif typ == 'D':
low, high, sep = entry[arg]
lastC = remaining = len(node[low:high])
for subnode in node[low:high]:
remaining -= 1
if len(subnode) > 0:
self.preorder(subnode)
if remaining > 0:
self.write(sep)
pass
pass
pass
arg += 1
elif typ == 'x':
assert isinstance(entry[arg], tuple)
src, dest = entry[arg]
for d in dest:
if hasattr(node[d], 'offset'):
self.set_pos_info(node[d], node[src].start, node[src].finish)
else:
for n in node[d]:
if hasattr(n, 'offset'):
self.set_pos_info(n, node[src].start, node[src].finish)
pass
pass
pass
pass
arg += 1
elif typ == 'P':
p = self.prec
low, high, sep, self.prec = entry[arg]
lastC = remaining = len(node[low:high])
start = self.last_finish
for subnode in node[low:high]:
self.preorder(subnode)
remaining -= 1
if remaining > 0:
self.write(sep)
self.prec = p
arg += 1
elif typ == '{':
d = node.__dict__
expr = m.group('expr')
try:
start = len(self.f.getvalue())
self.write(eval(expr, d, d))
self.set_pos_info(node, start, len(self.f.getvalue()))
except:
print(node)
raise
m = escape.search(fmt, i)
pass
self.write(fmt[i:])
fin = len(self.f.getvalue())
if recurse_node:
self.set_pos_info_recurse(startnode, startnode_start, fin)
else:
self.set_pos_info(startnode, startnode_start, fin)
# FIXME rocky: figure out how to get these casess to be table driven.
# 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
# entire string of the node fn(...)
if startnode.kind == 'call':
last_node = startnode[-1]
self.set_pos_info(last_node, startnode_start, self.last_finish)
return
@classmethod
def _get_mapping(cls, node):
return MAP.get(node, MAP_DIRECT_FRAGMENT)
pass
def deparse_code(version, co, out=StringIO(), showasm=False, showast=False,
showgrammar=False, is_pypy=False):
"""
Convert the code object co into a python source fragment.
:param version: The python version this code is from as a float, for
example 2.6, 2.7, 3.2, 3.3, 3.4, 3.5 etc.
:param co: The code object to parse.
:param out: File like object to write the output to.
:param showasm: Flag which determines whether the ingestd code
is written to sys.stdout or not. (It is also to
pass a file like object, into which the asm will be
written).
:param showast: Flag which determines whether the constructed
abstract syntax tree is written to sys.stdout or
not. (It is also to pass a file like object, into
which the ast will be written).
:param showgrammar: Flag which determines whether the grammar
is written to sys.stdout or not. (It is also to
pass a file like object, into which the grammer
will be written).
:return: The deparsed source fragment.
"""
assert iscode(co)
# store final output stream for case of error
scanner = get_scanner(version, is_pypy=is_pypy)
tokens, customize = scanner.ingest(co)
tokens, customize = scanner.ingest(co)
maybe_show_asm(showasm, tokens)
debug_parser = dict(PARSER_DEFAULT_DEBUG)
if showgrammar:
debug_parser['reduce'] = showgrammar
debug_parser['errorstack'] = True
# Build AST from disassembly.
# deparsed = pysource.FragmentsWalker(out, scanner, showast=showast)
deparsed = FragmentsWalker(version, scanner, showast=showast, debug_parser=debug_parser)
deparsed.ast = deparsed.build_ast(tokens, customize)
assert deparsed.ast == 'stmts', 'Should have parsed grammar start'
del tokens # save memory
# convert leading '__doc__ = "..." into doc string
assert deparsed.ast == 'stmts'
deparsed.mod_globs = pysource.find_globals(deparsed.ast, set())
# Just when you think we've forgotten about what we
# were supposed to to: Generate source from AST!
deparsed.gen_source(deparsed.ast, co.co_name, customize)
deparsed.set_pos_info(deparsed.ast, 0, len(deparsed.text))
deparsed.fixup_parents(deparsed.ast, None)
for g in deparsed.mod_globs:
deparsed.write('# global %s ## Warning: Unused global' % g)
if deparsed.ast_errors:
deparsed.write("# NOTE: have decompilation errors.\n")
deparsed.write("# Use -t option to show full context.")
for err in deparsed.ast_errors:
deparsed.write(err)
deparsed.ERROR = True
if 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
from bisect import bisect_right
def find_gt(a, x):
'Find leftmost value greater than x'
i = bisect_right(a, x)
if i != len(a):
return a[i]
raise ValueError
def deparse_code_around_offset(name, offset, version, co, out=StringIO(),
showasm=False, showast=False,
showgrammar=False, is_pypy=False):
"""
Like deparse_code(), but given a function/module name and
offset, finds the node closest to offset. If offset is not an instruction boundary,
we raise an IndexError.
"""
deparsed = deparse_code(version, co, out, showasm, showast, showgrammar, is_pypy)
if (name, offset) in deparsed.offsets.keys():
# This is the easy case
return deparsed
valid_offsets = [t for t in deparsed.offsets if isinstance(t[1], int)]
offset_list = sorted([t[1] for t in valid_offsets if t[0] == name])
# FIXME: should check for branching?
found_offset = find_gt(offset_list, offset)
deparsed.offsets[name, offset] = deparsed.offsets[name, found_offset]
return deparsed
if __name__ == '__main__':
from uncompyle6 import IS_PYPY
def deparse_test(co, is_pypy=IS_PYPY):
sys_version = sys.version_info.major + (sys.version_info.minor / 10.0)
walk = deparse_code(sys_version, co, showasm=False, showast=False,
showgrammar=False, is_pypy=IS_PYPY)
print("deparsed source")
print(walk.text, "\n")
print('------------------------')
for name, offset in sorted(walk.offsets.keys(),
key=lambda x: str(x[0])):
print("name %s, offset %s" % (name, offset))
nodeInfo = walk.offsets[name, offset]
node = nodeInfo.node
extractInfo = walk.extract_node_info(node)
print("code: %s" % node.kind)
# print extractInfo
print(extractInfo.selectedText)
print(extractInfo.selectedLine)
print(extractInfo.markerLine)
extractInfo, p = walk.extract_parent_info(node)
if extractInfo:
print("Contained in...")
print(extractInfo.selectedLine)
print(extractInfo.markerLine)
print("code: %s" % p.kind)
print('=' * 40)
pass
pass
return
def deparse_test_around(offset, name, co, is_pypy=IS_PYPY):
sys_version = sys.version_info.major + (sys.version_info.minor / 10.0)
walk = deparse_code_around_offset(name, offset, sys_version, co, showasm=False, showast=False,
showgrammar=False, is_pypy=IS_PYPY)
print("deparsed source")
print(walk.text, "\n")
print('------------------------')
for name, offset in sorted(walk.offsets.keys(),
key=lambda x: str(x[0])):
print("name %s, offset %s" % (name, offset))
nodeInfo = walk.offsets[name, offset]
node = nodeInfo.node
extractInfo = walk.extract_node_info(node)
print("code: %s" % node.kind)
# print extractInfo
print(extractInfo.selectedText)
print(extractInfo.selectedLine)
print(extractInfo.markerLine)
extractInfo, p = walk.extract_parent_info(node)
if extractInfo:
print("Contained in...")
print(extractInfo.selectedLine)
print(extractInfo.markerLine)
print("code: %s" % p.kind)
print('=' * 40)
pass
pass
return
def get_code_for_fn(fn):
return fn.__code__
def test():
import os, sys
def gcd(a, b):
if a > b:
(a, b) = (b, a)
pass
if a <= 0:
return None
if a == 1 or a == b:
return a
return gcd(b-a, a)
# check_args(['3', '5'])
# deparse_test(get_code_for_fn(gcd))
# deparse_test(get_code_for_fn(test))
# deparse_test(get_code_for_fn(FragmentsWalker.fixup_offsets))
# deparse_test(get_code_for_fn(FragmentsWalker.n_list))
print('=' * 30)
deparse_test_around(408, 'n_list', get_code_for_fn(FragmentsWalker.n_build_list))
# deparse_test(inspect.currentframe().f_code)