You've already forked python-uncompyle6
mirror of
https://github.com/rocky/python-uncompyle6.git
synced 2025-08-03 08:49:51 +08:00
Merge branch 'master' into python-2.4
This commit is contained in:
13
NEWS
13
NEWS
@@ -1,4 +1,11 @@
|
||||
uncompyle6 2.15.0 2017-01-27
|
||||
uncompyle6 2.15.0 2018-02-05 pycon2018.co
|
||||
|
||||
- Bug fixes
|
||||
- Code fragment improvements
|
||||
- Code cleanups
|
||||
- Expand testing
|
||||
|
||||
uncompyle6 2.15.1 2018-01-27
|
||||
|
||||
- Add --linemap option to give line correspondences
|
||||
between original source lines and reconstructed line sources.
|
||||
@@ -10,7 +17,7 @@ uncompyle6 2.15.0 2017-01-27
|
||||
- Correct 3.6+ calls with kwargs
|
||||
- Describe the difficulty of 3.6 in README
|
||||
|
||||
uncompyle6 2.14.3 2017-01-19
|
||||
uncompyle6 2.14.3 2018-01-19
|
||||
|
||||
- Fix bug in 3.5+ await stmt
|
||||
- Better version to magic handling; handle 3.5.2 .. 3.5.4 versions
|
||||
@@ -22,7 +29,7 @@ uncompyle6 2.14.3 2017-01-19
|
||||
- better tests in setup.py for running the right version of Python
|
||||
- Fix 2.6- parsing of "for .. try/else" ... with "continue" inside
|
||||
|
||||
uncompyle6 2.14.2 2017-01-09 Samish
|
||||
uncompyle6 2.14.2 2018-01-09 Samish
|
||||
|
||||
Decompilation bug fixes, mostly 3.6 and pre 2.7
|
||||
|
||||
|
@@ -74,8 +74,8 @@ def check_expect(expect, parsed, fn_name):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.skip(reason='needs reworking')
|
||||
def test_stuff():
|
||||
return
|
||||
parsed = get_parsed_for_fn(map_stmts)
|
||||
expect = """
|
||||
-1
|
||||
|
@@ -29,6 +29,9 @@ def test_if_in_for():
|
||||
bytecode = Bytecode(code, scan.opc)
|
||||
scan.build_lines_data(code, n)
|
||||
scan.insts = list(bytecode)
|
||||
scan.offset2inst_index = {}
|
||||
for i, inst in enumerate(scan.insts):
|
||||
scan.offset2inst_index[inst.offset] = i
|
||||
scan.build_prev_op(n)
|
||||
fjt = scan.find_jump_targets(False)
|
||||
|
||||
@@ -45,8 +48,13 @@ def test_if_in_for():
|
||||
|
||||
code = bug_loop.__code__
|
||||
n = scan.setup_code(code)
|
||||
bytecode = Bytecode(code, scan.opc)
|
||||
scan.build_lines_data(code, n)
|
||||
scan.insts = list(bytecode)
|
||||
scan.build_prev_op(n)
|
||||
scan.offset2inst_index = {}
|
||||
for i, inst in enumerate(scan.insts):
|
||||
scan.offset2inst_index[inst.offset] = i
|
||||
fjt = scan.find_jump_targets(False)
|
||||
assert{64: [42], 67: [42, 42], 42: [16, 41], 19: [6]} == fjt
|
||||
assert scan.structs == [
|
||||
|
BIN
test/bytecode_2.7/05_while_if_return.pyc-notyet
Normal file
BIN
test/bytecode_2.7/05_while_if_return.pyc-notyet
Normal file
Binary file not shown.
10
test/simple_source/bug27+/05_while_if_return.py
Normal file
10
test/simple_source/bug27+/05_while_if_return.py
Normal file
@@ -0,0 +1,10 @@
|
||||
# Issue #149. Bug in Python 2.7 was handling a return stmt at the end
|
||||
# of a while with so no jump back, confusing which block the
|
||||
# return should be part of
|
||||
def test(a):
|
||||
while True:
|
||||
if a:
|
||||
pass
|
||||
else:
|
||||
continue
|
||||
return
|
@@ -170,8 +170,9 @@ def main_bin():
|
||||
try:
|
||||
result = main(src_base, out_base, files, codes, outfile,
|
||||
**options)
|
||||
result = list(result) + [options.get('do_verify', None)]
|
||||
if len(files) > 1:
|
||||
mess = status_msg(do_verify, result, do_verify)
|
||||
mess = status_msg(do_verify, *result)
|
||||
print('# ' + mess)
|
||||
pass
|
||||
except (KeyboardInterrupt):
|
||||
|
@@ -207,7 +207,7 @@ def main(in_base, out_base, files, codes, outfile=None,
|
||||
for d in deparsed:
|
||||
last_mod = None
|
||||
offsets = d.offsets
|
||||
for e in sorted(offsets.keys()):
|
||||
for e in sorted([k for k in offsets.keys() if isinstance(k[1], int)]):
|
||||
if e[0] != last_mod:
|
||||
line = '=' * len(e[0])
|
||||
outstream.write("%s\n%s\n%s\n" % (line, e[0], line))
|
||||
|
@@ -119,7 +119,8 @@ class Python27Parser(Python2Parser):
|
||||
POP_BLOCK LOAD_CONST COME_FROM_WITH
|
||||
WITH_CLEANUP END_FINALLY
|
||||
|
||||
whilestmt ::= SETUP_LOOP testexpr returns POP_BLOCK COME_FROM
|
||||
whilestmt ::= SETUP_LOOP testexpr returns
|
||||
_come_froms POP_BLOCK COME_FROM
|
||||
|
||||
while1stmt ::= SETUP_LOOP returns bp_come_from
|
||||
while1stmt ::= SETUP_LOOP l_stmts_opt JUMP_BACK POP_BLOCK COME_FROM
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2015-2017 by Rocky Bernstein
|
||||
# Copyright (c) 2015-2018 by Rocky Bernstein
|
||||
# Copyright (c) 2005 by Dan Pascu <dan@windowmaker.org>
|
||||
# Copyright (c) 2000-2002 by hartmut Goebel <h.goebel@crazy-compilers.com>
|
||||
"""
|
||||
@@ -113,7 +113,11 @@ class Scanner2(Scanner):
|
||||
|
||||
self.build_lines_data(co, codelen)
|
||||
self.build_prev_op(codelen)
|
||||
|
||||
self.insts = list(bytecode)
|
||||
self.offset2inst_index = {}
|
||||
for i, inst in enumerate(self.insts):
|
||||
self.offset2inst_index[inst.offset] = i
|
||||
|
||||
free, names, varnames = self.unmangle_code_names(co, classname)
|
||||
self.names = names
|
||||
@@ -510,50 +514,55 @@ class Scanner2(Scanner):
|
||||
# Try to find the jump_back instruction of the loop.
|
||||
# It could be a return instruction.
|
||||
|
||||
inst = self.insts[self.offset2inst_index[offset]]
|
||||
start += instruction_size(op, self.opc)
|
||||
target = self.get_target(offset) + extended_arg
|
||||
end = self.restrict_to_parent(target, parent)
|
||||
self.setup_loop_targets[offset] = target
|
||||
self.setup_loops[target] = offset
|
||||
setup_target = inst.argval
|
||||
loop_end_offset = self.restrict_to_parent(setup_target, parent)
|
||||
self.setup_loop_targets[offset] = setup_target
|
||||
self.setup_loops[setup_target] = offset
|
||||
|
||||
if target != end:
|
||||
self.fixed_jumps[offset] = end
|
||||
if setup_target != loop_end_offset:
|
||||
self.fixed_jumps[offset] = loop_end_offset
|
||||
|
||||
(line_no, next_line_byte) = self.lines[offset]
|
||||
jump_back = self.last_instr(start, end, self.opc.JUMP_ABSOLUTE,
|
||||
|
||||
# jump_back_offset is the instruction after the SETUP_LOOP
|
||||
# where we iterate back to.
|
||||
jump_back_offset = self.last_instr(start, loop_end_offset, self.opc.JUMP_ABSOLUTE,
|
||||
next_line_byte, False)
|
||||
|
||||
if jump_back:
|
||||
if jump_back_offset:
|
||||
# Account for the fact that < 2.7 has an explicit
|
||||
# POP_TOP instruction in the equivalate POP_JUMP_IF
|
||||
# construct
|
||||
if self.version < 2.7:
|
||||
jump_forward_offset = jump_back+4
|
||||
return_val_offset1 = self.prev[self.prev[self.prev[end]]]
|
||||
jump_forward_offset = jump_back_offset+4
|
||||
return_val_offset1 = self.prev[self.prev[self.prev[loop_end_offset]]]
|
||||
# Is jump back really "back"?
|
||||
jump_target = self.get_target(jump_back, code[jump_back])
|
||||
if (jump_target > jump_back or
|
||||
code[jump_back+3] in [self.opc.JUMP_FORWARD, self.opc.JUMP_ABSOLUTE]):
|
||||
jump_back = None
|
||||
jump_target = self.get_target(jump_back_offset, code[jump_back_offset])
|
||||
if (jump_target > jump_back_offset or
|
||||
code[jump_back_offset+3] in [self.opc.JUMP_FORWARD, self.opc.JUMP_ABSOLUTE]):
|
||||
jump_back_offset = None
|
||||
pass
|
||||
else:
|
||||
jump_forward_offset = jump_back+3
|
||||
return_val_offset1 = self.prev[self.prev[end]]
|
||||
jump_forward_offset = jump_back_offset+3
|
||||
return_val_offset1 = self.prev[self.prev[loop_end_offset]]
|
||||
|
||||
if (jump_back and jump_back != self.prev[end]
|
||||
if (jump_back_offset and jump_back_offset != self.prev[loop_end_offset]
|
||||
and code[jump_forward_offset] in self.jump_forward):
|
||||
if (code[self.prev[end]] == self.opc.RETURN_VALUE or
|
||||
(code[self.prev[end]] == self.opc.POP_BLOCK
|
||||
if (code[self.prev[loop_end_offset]] == self.opc.RETURN_VALUE or
|
||||
(code[self.prev[loop_end_offset]] == self.opc.POP_BLOCK
|
||||
and code[return_val_offset1] == self.opc.RETURN_VALUE)):
|
||||
jump_back = None
|
||||
if not jump_back:
|
||||
jump_back_offset = None
|
||||
|
||||
if not jump_back_offset:
|
||||
# loop suite ends in return
|
||||
# scanner26 of wbiti had:
|
||||
# jump_back = self.last_instr(start, end, self.opc.JUMP_ABSOLUTE, start, False)
|
||||
jump_back = self.last_instr(start, end, self.opc.RETURN_VALUE)
|
||||
if not jump_back:
|
||||
# jump_back_offset = self.last_instr(start, loop_end_offset, self.opc.JUMP_ABSOLUTE, start, False)
|
||||
jump_back_offset = self.last_instr(start, loop_end_offset, self.opc.RETURN_VALUE)
|
||||
if not jump_back_offset:
|
||||
return
|
||||
jump_back += 1
|
||||
jump_back_offset += 1
|
||||
|
||||
if_offset = None
|
||||
if self.version < 2.7:
|
||||
@@ -569,71 +578,76 @@ class Scanner2(Scanner):
|
||||
loop_type = 'while'
|
||||
self.ignore_if.add(if_offset)
|
||||
if self.version < 2.7 and (
|
||||
code[self.prev[jump_back]] == self.opc.RETURN_VALUE):
|
||||
self.ignore_if.add(self.prev[jump_back])
|
||||
code[self.prev[jump_back_offset]] == self.opc.RETURN_VALUE):
|
||||
self.ignore_if.add(self.prev[jump_back_offset])
|
||||
pass
|
||||
pass
|
||||
else:
|
||||
loop_type = 'for'
|
||||
target = next_line_byte
|
||||
end = jump_back + 3
|
||||
setup_target = next_line_byte
|
||||
loop_end_offset = jump_back_offset + 3
|
||||
else:
|
||||
if self.get_target(jump_back) >= next_line_byte:
|
||||
jump_back = self.last_instr(start, end, self.opc.JUMP_ABSOLUTE, start, False)
|
||||
if end > jump_back+4 and code[end] in self.jump_forward:
|
||||
if code[jump_back+4] in self.jump_forward:
|
||||
if self.get_target(jump_back+4) == self.get_target(end):
|
||||
self.fixed_jumps[offset] = jump_back+4
|
||||
end = jump_back+4
|
||||
elif target < offset:
|
||||
self.fixed_jumps[offset] = jump_back+4
|
||||
end = jump_back+4
|
||||
# We have a loop with a jump-back instruction
|
||||
if self.get_target(jump_back_offset) >= next_line_byte:
|
||||
jump_back_offset = self.last_instr(start, loop_end_offset, self.opc.JUMP_ABSOLUTE, start, False)
|
||||
if loop_end_offset > jump_back_offset+4 and code[loop_end_offset] in self.jump_forward:
|
||||
if code[jump_back_offset+4] in self.jump_forward:
|
||||
if self.get_target(jump_back_offset+4) == self.get_target(loop_end_offset):
|
||||
self.fixed_jumps[offset] = jump_back_offset+4
|
||||
loop_end_offset = jump_back_offset+4
|
||||
elif setup_target < offset:
|
||||
self.fixed_jumps[offset] = jump_back_offset+4
|
||||
loop_end_offset = jump_back_offset+4
|
||||
|
||||
target = self.get_target(jump_back, self.opc.JUMP_ABSOLUTE)
|
||||
setup_target = self.get_target(jump_back_offset, self.opc.JUMP_ABSOLUTE)
|
||||
|
||||
if (self.version > 2.1 and
|
||||
code[target] in (self.opc.FOR_ITER, self.opc.GET_ITER)):
|
||||
code[setup_target] in (self.opc.FOR_ITER, self.opc.GET_ITER)):
|
||||
loop_type = 'for'
|
||||
else:
|
||||
loop_type = 'while'
|
||||
# Look for a test condition immediately after the
|
||||
# SETUP_LOOP while
|
||||
if (self.version < 2.7
|
||||
and self.code[self.prev[next_line_byte]] == self.opc.POP_TOP):
|
||||
test = self.prev[self.prev[next_line_byte]]
|
||||
test_op_offset = self.prev[self.prev[next_line_byte]]
|
||||
else:
|
||||
test = self.prev[next_line_byte]
|
||||
test_op_offset = self.prev[next_line_byte]
|
||||
|
||||
if test == offset:
|
||||
if test_op_offset == offset:
|
||||
loop_type = 'while 1'
|
||||
elif self.code[test] in self.opc.JUMP_OPs:
|
||||
self.ignore_if.add(test)
|
||||
test_target = self.get_target(test)
|
||||
if test_target > (jump_back+3):
|
||||
jump_back = test_target
|
||||
self.not_continue.add(jump_back)
|
||||
self.loops.append(target)
|
||||
elif self.code[test_op_offset] in self.opc.JUMP_OPs:
|
||||
test_target = self.get_target(test_op_offset)
|
||||
|
||||
self.ignore_if.add(test_op_offset)
|
||||
|
||||
if test_target > (jump_back_offset+3):
|
||||
jump_back_offset = test_target
|
||||
self.not_continue.add(jump_back_offset)
|
||||
self.loops.append(setup_target)
|
||||
self.structs.append({'type': loop_type + '-loop',
|
||||
'start': target,
|
||||
'end': jump_back})
|
||||
if jump_back+3 != end:
|
||||
'start': setup_target,
|
||||
'end': jump_back_offset})
|
||||
if jump_back_offset+3 != loop_end_offset:
|
||||
self.structs.append({'type': loop_type + '-else',
|
||||
'start': jump_back+3,
|
||||
'end': end})
|
||||
'start': jump_back_offset+3,
|
||||
'end': loop_end_offset})
|
||||
elif op == self.opc.SETUP_EXCEPT:
|
||||
start = offset + op_size(op, self.opc)
|
||||
target = self.get_target(offset, op)
|
||||
end = self.restrict_to_parent(target, parent)
|
||||
if target != end:
|
||||
self.fixed_jumps[offset] = end
|
||||
end_offset = self.restrict_to_parent(target, parent)
|
||||
if target != end_offset:
|
||||
self.fixed_jumps[offset] = end_offset
|
||||
# print target, end, parent
|
||||
# Add the try block
|
||||
self.structs.append({'type': 'try',
|
||||
'start': start-3,
|
||||
'end': end-4})
|
||||
'end': end_offset-4})
|
||||
# Now isolate the except and else blocks
|
||||
end_else = start_else = self.get_target(self.prev[end])
|
||||
end_else = start_else = self.get_target(self.prev[end_offset])
|
||||
|
||||
|
||||
end_finally_offset = end
|
||||
end_finally_offset = end_offset
|
||||
setup_except_nest = 0
|
||||
while end_finally_offset < len(self.code):
|
||||
if self.code[end_finally_offset] == self.opc.END_FINALLY:
|
||||
@@ -647,7 +661,7 @@ class Scanner2(Scanner):
|
||||
pass
|
||||
|
||||
# Add the except blocks
|
||||
i = end
|
||||
i = end_offset
|
||||
while i < len(self.code) and i < end_finally_offset:
|
||||
jmp = self.next_except_jump(i)
|
||||
if jmp is None: # check
|
||||
@@ -705,7 +719,7 @@ class Scanner2(Scanner):
|
||||
|
||||
test_target = target
|
||||
if self.version < 2.7:
|
||||
# Before 2.6 we have to deal with the fact that there is an extra
|
||||
# Before 2.7 we have to deal with the fact that there is an extra
|
||||
# POP_TOP that is logically associated with the JUMP_IF's (even though
|
||||
# the instance set is called "self.pop_jump_if")
|
||||
if code[pre[test_target]] == self.opc.POP_TOP:
|
||||
@@ -715,22 +729,29 @@ class Scanner2(Scanner):
|
||||
test_set = self.pop_jump_if_or_pop | self.pop_jump_if
|
||||
|
||||
if ( code[pre[test_target]] in test_set and target > offset ):
|
||||
# We have POP_JUMP_IF... target
|
||||
# ...
|
||||
# pre: POP_JUMP_IF ...
|
||||
# target: ...
|
||||
#
|
||||
# We will take that as either as "and" or "or".
|
||||
self.fixed_jumps[offset] = pre[target]
|
||||
self.structs.append({'type': 'and/or',
|
||||
'start': start,
|
||||
'end': pre[target]})
|
||||
return
|
||||
|
||||
# The op offset just before the target jump offset is important
|
||||
# The instruction offset just before the target jump offset is important
|
||||
# in making a determination of what we have. Save that.
|
||||
pre_rtarget = pre[rtarget]
|
||||
|
||||
# Is it an "and" inside an "if" or "while" block
|
||||
if op == self.opc.PJIF:
|
||||
|
||||
# Search for other POP_JUMP_IF_FALSE targetting the same op,
|
||||
# in current statement, starting from current offset, and filter
|
||||
# everything inside inner 'or' jumps and midline ifs
|
||||
# Search for other POP_JUMP_IF_...'s targetting the
|
||||
# same target, of the current POP_JUMP_... instruction,
|
||||
# starting from current offset, and filter everything inside inner 'or'
|
||||
# jumps and mid-line ifs
|
||||
match = self.rem_or(start, self.next_stmt[offset], self.opc.PJIF, target)
|
||||
|
||||
# If we still have any offsets in set, start working on it
|
||||
@@ -829,7 +850,7 @@ class Scanner2(Scanner):
|
||||
# JUMP_FORWARD
|
||||
# HERE:
|
||||
#
|
||||
# If so, this can be block inside an "if" statement
|
||||
# If so, this can be a block inside an "if" statement
|
||||
# or a conditional assignment like:
|
||||
# x = 1 if x else 2
|
||||
#
|
||||
@@ -860,7 +881,7 @@ class Scanner2(Scanner):
|
||||
self.fixed_jumps[jump_if_offset] = jump_target
|
||||
return
|
||||
|
||||
end = self.restrict_to_parent(if_end, parent)
|
||||
end_offset = self.restrict_to_parent(if_end, parent)
|
||||
|
||||
if_then_maybe = None
|
||||
|
||||
@@ -899,7 +920,7 @@ class Scanner2(Scanner):
|
||||
self.structs.append({'type': 'if-then',
|
||||
'start': start-3,
|
||||
'end': pre_rtarget})
|
||||
self.thens[start] = end
|
||||
self.thens[start] = end_offset
|
||||
elif jump_op == 'JUMP_ABSOLUTE':
|
||||
if_then_maybe = {'type': 'if-then',
|
||||
'start': start-3,
|
||||
@@ -914,7 +935,7 @@ class Scanner2(Scanner):
|
||||
if pre_rtarget not in self.linestartoffsets or self.version < 2.7:
|
||||
self.not_continue.add(pre_rtarget)
|
||||
|
||||
if rtarget < end:
|
||||
if rtarget < end_offset:
|
||||
# We have an "else" block of some kind.
|
||||
# Is it associated with "if_then_maybe" seen above?
|
||||
# These will be linked in this funny way:
|
||||
@@ -930,15 +951,15 @@ class Scanner2(Scanner):
|
||||
# 256
|
||||
if if_then_maybe and jump_op == 'JUMP_ABSOLUTE':
|
||||
jump_target = self.get_target(jump_inst, code[jump_inst])
|
||||
if self.opname_for_offset(end) == 'JUMP_FORWARD':
|
||||
end_target = self.get_target(end, code[end])
|
||||
if self.opname_for_offset(end_offset) == 'JUMP_FORWARD':
|
||||
end_target = self.get_target(end_offset, code[end_offset])
|
||||
if jump_target == end_target:
|
||||
self.structs.append(if_then_maybe)
|
||||
self.thens[start] = end
|
||||
self.thens[start] = end_offset
|
||||
|
||||
self.structs.append({'type': 'else',
|
||||
'start': rtarget,
|
||||
'end': end})
|
||||
'end': end_offset})
|
||||
elif code_pre_rtarget == self.opc.RETURN_VALUE:
|
||||
if self.version == 2.7 or pre_rtarget not in self.ignore_if:
|
||||
# 10 is exception-match. If there is an exception match in the
|
||||
|
@@ -110,7 +110,11 @@ class Scanner26(scan.Scanner2):
|
||||
|
||||
self.build_lines_data(co, codelen)
|
||||
self.build_prev_op(codelen)
|
||||
|
||||
self.insts = list(bytecode)
|
||||
self.offset2inst_index = {}
|
||||
for i, inst in enumerate(self.insts):
|
||||
self.offset2inst_index[inst.offset] = i
|
||||
|
||||
free, names, varnames = self.unmangle_code_names(co, classname)
|
||||
self.names = names
|
||||
|
@@ -258,13 +258,13 @@ TABLE_DIRECT = {
|
||||
'while1elsestmt': ( '%|while 1:\n%+%c%-%|else:\n%+%c%-\n\n', 1, -2 ),
|
||||
'whileelsestmt': ( '%|while %c:\n%+%c%-%|else:\n%+%c%-\n\n', 1, 2, -2 ),
|
||||
'whileelselaststmt': ( '%|while %c:\n%+%c%-%|else:\n%+%c%-', 1, 2, -2 ),
|
||||
'for': ( '%|for %c in %c:\n%+%c%-\n\n', 3, 1, 4 ),
|
||||
'for': ( '%|for %c in %c:\n%+%c%-\n\n', (3, 'store'), 1, 4 ),
|
||||
'forelsestmt': (
|
||||
'%|for %c in %c:\n%+%c%-%|else:\n%+%c%-\n\n', 3, 1, 4, -2 ),
|
||||
'%|for %c in %c:\n%+%c%-%|else:\n%+%c%-\n\n', (3, 'store'), 1, 4, -2 ),
|
||||
'forelselaststmt': (
|
||||
'%|for %c in %c:\n%+%c%-%|else:\n%+%c%-', 3, 1, 4, -2 ),
|
||||
'%|for %c in %c:\n%+%c%-%|else:\n%+%c%-', (3, 'store'), 1, 4, -2 ),
|
||||
'forelselaststmtl': (
|
||||
'%|for %c in %c:\n%+%c%-%|else:\n%+%c%-\n\n', 3, 1, 4, -2 ),
|
||||
'%|for %c in %c:\n%+%c%-%|else:\n%+%c%-\n\n', (3, 'store'), 1, 4, -2 ),
|
||||
'try_except': ( '%|try:\n%+%c%-%c\n\n', 1, 3 ),
|
||||
'tryelsestmt': ( '%|try:\n%+%c%-%c%|else:\n%+%c%-\n\n', 1, 3, 4 ),
|
||||
'tryelsestmtc': ( '%|try:\n%+%c%-%c%|else:\n%+%c%-', 1, 3, 4 ),
|
||||
|
@@ -1,5 +1,4 @@
|
||||
# Copyright (c) 2015-2018 by Rocky Bernstein
|
||||
# Copyright (c) 1999 John Aycock
|
||||
|
||||
"""
|
||||
Creates Python source code from an uncompyle6 abstract syntax tree,
|
||||
@@ -15,12 +14,13 @@ We add some format specifiers here not used in pysource
|
||||
-----
|
||||
|
||||
%x takes an argument (src, (dest...)) and copies all of the range attributes
|
||||
from src to dest.
|
||||
from src to all nodes under 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.
|
||||
node 2 range information, it in %c, is copied to nodes 0 and 1. If
|
||||
1. is a nonterminal, all the nodes under it get node2 range information.
|
||||
|
||||
2. %r
|
||||
-----
|
||||
@@ -43,7 +43,7 @@ do it recursively which is where offsets are probably located.
|
||||
|
||||
For example in:
|
||||
'importmultiple': ( '%|import%b %c%c\n', 0, 2, 3 ),
|
||||
n
|
||||
|
||||
The node position 0 will be associated with "import".
|
||||
|
||||
"""
|
||||
@@ -99,17 +99,22 @@ TABLE_DIRECT_FRAGMENT = {
|
||||
'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),
|
||||
'for':
|
||||
('%|for%b %c%x in %c:\n%+%c%-\n\n',
|
||||
0, (3, 'store'), (3, (2,)), 1, 4 ),
|
||||
'forelsestmt':
|
||||
('%|for%b %c%x in %c:\n%+%c%-%|else:\n%+%c%-\n\n',
|
||||
0, (3, 'store'), (3, (2,)), 1, 4, -2),
|
||||
'forelselaststmt':
|
||||
('%|for%b %c%x in %c:\n%+%c%-%|else:\n%+%c%-',
|
||||
0, (3, 'store'), (3, (2,)), 1, 4, -2),
|
||||
'forelselaststmtl':
|
||||
('%|for%b %c%x in %c:\n%+%c%-%|else:\n%+%c%-\n\n',
|
||||
0, (3, 'store'), (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 ),
|
||||
'for': ( '%|for%b %c in %c:\n%+%c%-\n\n', 0, 3, 1, 4 ),
|
||||
}
|
||||
|
||||
|
||||
@@ -207,12 +212,55 @@ class FragmentsWalker(pysource.SourceWalker, object):
|
||||
self.default(node)
|
||||
|
||||
def n_try_except(self, node):
|
||||
# Note: we could also do this via modifying the
|
||||
# 5 or so template rules. That is change:
|
||||
# 'try_except': ( '%|try%:\n%+%c%-%c\n\n', 1, 3 ),
|
||||
# to:
|
||||
# 'try_except': ( '%|try%b:\n%+%c%-%c\n\n', 0, 1, 3 ),
|
||||
|
||||
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_try_except
|
||||
|
||||
def n_raise_stmt0(self, node):
|
||||
assert node[0] == 'RAISE_VARARGS_0'
|
||||
start = len(self.f.getvalue()) + len(self.indent)
|
||||
try:
|
||||
self.default(node)
|
||||
except GenericASTTraversalPruningException:
|
||||
self.set_pos_info(node[0], start, len(self.f.getvalue()))
|
||||
self.prune()
|
||||
|
||||
def n_raise_stmt1(self, node):
|
||||
assert node[1] == 'RAISE_VARARGS_1'
|
||||
start = len(self.f.getvalue()) + len(self.indent)
|
||||
try:
|
||||
self.default(node)
|
||||
except GenericASTTraversalPruningException:
|
||||
self.set_pos_info(node[1], start, len(self.f.getvalue()))
|
||||
self.prune()
|
||||
|
||||
def n_raise_stmt2(self, node):
|
||||
assert node[2] == 'RAISE_VARARGS_2'
|
||||
start = len(self.f.getvalue()) + len(self.indent)
|
||||
try:
|
||||
self.default(node)
|
||||
except GenericASTTraversalPruningException:
|
||||
self.set_pos_info(node[2], start, len(self.f.getvalue()))
|
||||
self.prune()
|
||||
|
||||
# FIXME: Isolate: only in Python 2.x.
|
||||
def n_raise_stmt3(self, node):
|
||||
assert node[3] == 'RAISE_VARARGS_3'
|
||||
start = len(self.f.getvalue()) + len(self.indent)
|
||||
try:
|
||||
self.default(node)
|
||||
except GenericASTTraversalPruningException:
|
||||
self.set_pos_info(node[3], start, len(self.f.getvalue()))
|
||||
self.prune()
|
||||
|
||||
def n_return(self, node):
|
||||
start = len(self.f.getvalue()) + len(self.indent)
|
||||
if self.params['is_lambda']:
|
||||
@@ -224,7 +272,8 @@ class FragmentsWalker(pysource.SourceWalker, object):
|
||||
else:
|
||||
start = len(self.f.getvalue()) + len(self.indent)
|
||||
self.write(self.indent, 'return')
|
||||
if self.return_none or node != AST('return', [AST('ret_expr', [NONE]), Token('RETURN_VALUE')]):
|
||||
if self.return_none or node != AST('return', [AST('ret_expr', [NONE]),
|
||||
Token('RETURN_VALUE')]):
|
||||
self.write(' ')
|
||||
self.last_finish = len(self.f.getvalue())
|
||||
self.preorder(node[0])
|
||||
@@ -339,7 +388,8 @@ class FragmentsWalker(pysource.SourceWalker, object):
|
||||
|
||||
def n_binary_expr(self, node):
|
||||
start = len(self.f.getvalue())
|
||||
node[0].parent = node
|
||||
for n in node:
|
||||
n.parent = node
|
||||
self.last_finish = len(self.f.getvalue())
|
||||
try:
|
||||
super(FragmentsWalker, self).n_binary_expr(node)
|
||||
@@ -602,13 +652,17 @@ class FragmentsWalker(pysource.SourceWalker, object):
|
||||
self.prec = 27
|
||||
code = node[code_index].attr
|
||||
|
||||
assert iscode(code)
|
||||
assert iscode(code), node[code_index]
|
||||
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
|
||||
if ast[0] == 'sstmt':
|
||||
ast = ast[0]
|
||||
|
||||
# skip over stmt return ret_expr
|
||||
ast = ast[0][0][0]
|
||||
store = None
|
||||
if ast in ['setcomp_func', 'dictcomp_func']:
|
||||
@@ -626,11 +680,12 @@ class FragmentsWalker(pysource.SourceWalker, object):
|
||||
else:
|
||||
ast = ast[0][0]
|
||||
n = ast[iter_index]
|
||||
assert n == 'list_iter'
|
||||
assert n == 'list_iter', n
|
||||
|
||||
# FIXME: I'm not totally sure this is right.
|
||||
|
||||
# find innermost node
|
||||
# Find the list comprehension body. It is the inner-most
|
||||
# node that is not list_.. .
|
||||
if_node = None
|
||||
comp_for = None
|
||||
comp_store = None
|
||||
@@ -640,12 +695,12 @@ class FragmentsWalker(pysource.SourceWalker, object):
|
||||
|
||||
have_not = False
|
||||
while n in ('list_iter', 'comp_iter'):
|
||||
n = n[0] # recurse one step
|
||||
if n == 'list_for':
|
||||
n = n[0] # iterate one nesting deeper
|
||||
if n in ('list_for', 'comp_for'):
|
||||
if n[2] == 'store':
|
||||
store = n[2]
|
||||
n = n[3]
|
||||
elif n in ['list_if', 'list_if_not', 'comp_if']:
|
||||
elif n in ('list_if', 'list_if_not', 'comp_if', 'comp_ifnot'):
|
||||
have_not = n in ('list_if_not', 'comp_ifnot')
|
||||
if_node = n[0]
|
||||
if n[1] == 'store':
|
||||
@@ -661,12 +716,29 @@ class FragmentsWalker(pysource.SourceWalker, object):
|
||||
|
||||
old_name = self.name
|
||||
self.name = code_name
|
||||
|
||||
# Issue created with later Python code generation is that there
|
||||
# is a lamda set up with a dummy argument name that is then called
|
||||
# So we can't just translate that as is but need to replace the
|
||||
# dummy name. Below we are picking out the variable name as seen
|
||||
# in the code. And trying to generate code for the other parts
|
||||
# that don't have the dummy argument name in it.
|
||||
# Another approach might be to be able to pass in the source name
|
||||
# for the dummy argument.
|
||||
|
||||
self.preorder(n[0])
|
||||
gen_start = len(self.f.getvalue()) + 1
|
||||
self.write(' for ')
|
||||
start = len(self.f.getvalue())
|
||||
self.preorder(store)
|
||||
if comp_store:
|
||||
self.preorder(comp_store)
|
||||
else:
|
||||
self.preorder(store)
|
||||
|
||||
self.set_pos_info(store, start, len(self.f.getvalue()))
|
||||
|
||||
# FIXME this is all merely approximate
|
||||
# from trepan.api import debug; debug()
|
||||
self.write(' in ')
|
||||
start = len(self.f.getvalue())
|
||||
node[-3].parent = node
|
||||
@@ -674,6 +746,15 @@ class FragmentsWalker(pysource.SourceWalker, object):
|
||||
fin = len(self.f.getvalue())
|
||||
self.set_pos_info(node[-3], start, fin, old_name)
|
||||
|
||||
if ast == 'list_comp':
|
||||
list_iter = ast[1]
|
||||
assert list_iter == 'list_iter'
|
||||
if list_iter == 'list_for':
|
||||
self.preorder(list_iter[3])
|
||||
self.prec = p
|
||||
return
|
||||
pass
|
||||
|
||||
if comp_store:
|
||||
self.preorder(comp_for)
|
||||
elif if_node:
|
||||
@@ -681,6 +762,7 @@ class FragmentsWalker(pysource.SourceWalker, object):
|
||||
if have_not:
|
||||
self.write('not ')
|
||||
self.preorder(if_node)
|
||||
pass
|
||||
self.prec = p
|
||||
self.name = old_name
|
||||
if node[-1].kind.startswith('CALL_FUNCTION'):
|
||||
@@ -1530,17 +1612,6 @@ class FragmentsWalker(pysource.SourceWalker, object):
|
||||
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':
|
||||
@@ -1578,18 +1649,9 @@ class FragmentsWalker(pysource.SourceWalker, object):
|
||||
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
|
||||
self.set_pos_info_recurse(node[d], node[src].start, node[src].finish)
|
||||
pass
|
||||
arg += 1
|
||||
elif typ == 'P':
|
||||
@@ -1762,91 +1824,93 @@ def deparse_code_around_offset(name, offset, version, co, out=StringIO(),
|
||||
return deparsed
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 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
|
||||
# from uncompyle6 import IS_PYPY
|
||||
# def deparse_test(co, is_pypy=IS_PYPY):
|
||||
# from xdis.magics import sysinfo2float
|
||||
# float_version = sysinfo2float()
|
||||
# walk = deparse_code(float_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)
|
||||
|
||||
def deparse_test_around(offset, name, co, is_pypy=IS_PYPY):
|
||||
sys_version = sys.version_info[0] + (sys.version_info[1] / 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
|
||||
# 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):
|
||||
if hasattr(fn, 'func_code'):
|
||||
return fn.func_code
|
||||
return fn.__code__
|
||||
# def deparse_test_around(offset, name, co, is_pypy=IS_PYPY):
|
||||
# sys_version = sys.version_info[0] + (sys.version_info[1] / 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 test():
|
||||
import os, sys
|
||||
# def get_code_for_fn(fn):
|
||||
# if hasattr(fn, 'func_code'):
|
||||
# return fn.func_code
|
||||
# return fn.__code__
|
||||
|
||||
def gcd(a, b):
|
||||
if a > b:
|
||||
(a, b) = (b, a)
|
||||
pass
|
||||
# def test():
|
||||
# import os, sys
|
||||
|
||||
if a <= 0:
|
||||
return None
|
||||
if a == 1 or a == b:
|
||||
return a
|
||||
return gcd(b-a, a)
|
||||
# def gcd(a, b):
|
||||
# if a > b:
|
||||
# (a, b) = (b, a)
|
||||
# pass
|
||||
|
||||
# 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_list))
|
||||
# deparse_test(inspect.currentframe().f_code)
|
||||
# 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_list))
|
||||
# # deparse_test(inspect.currentframe().f_code)
|
||||
|
@@ -1529,7 +1529,8 @@ class SourceWalker(GenericASTTraversal, object):
|
||||
|
||||
ast = self.build_ast(code._tokens, code._customize)
|
||||
self.customize(code._customize)
|
||||
# skip over stmts sstmt smt
|
||||
|
||||
# skip over stmt return ret_expr
|
||||
ast = ast[0][0][0]
|
||||
store = None
|
||||
if ast in ['setcomp_func', 'dictcomp_func']:
|
||||
|
@@ -1,3 +1,3 @@
|
||||
# This file is suitable for sourcing inside bash as
|
||||
# well as importing into Python
|
||||
VERSION='2.15.0'
|
||||
VERSION='2.15.1'
|
||||
|
Reference in New Issue
Block a user