mirror of
https://github.com/rocky/python-uncompyle6.git
synced 2024-11-27 07:00:42 +00:00
286 lines
9.4 KiB
Python
286 lines
9.4 KiB
Python
# Copyright (c) 2022-2023 Rocky Bernstein
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
import sys
|
|
|
|
from xdis import iscode
|
|
from uncompyle6.parsers.treenode import SyntaxTree
|
|
|
|
minint = -sys.maxsize-1
|
|
maxint = sys.maxsize
|
|
|
|
read_write_global_ops = frozenset(('STORE_GLOBAL', 'DELETE_GLOBAL', 'LOAD_GLOBAL'))
|
|
read_global_ops = frozenset(('STORE_GLOBAL', 'DELETE_GLOBAL'))
|
|
|
|
# NOTE: we also need to check that the variable name is a free variable, not a cell variable.
|
|
nonglobal_ops = frozenset(('STORE_DEREF', 'DELETE_DEREF'))
|
|
|
|
def escape_string(s, quotes=('"', "'", '"""', "'''")):
|
|
quote = None
|
|
for q in quotes:
|
|
if s.find(q) == -1:
|
|
quote = q
|
|
break
|
|
pass
|
|
if quote is None:
|
|
quote = '"""'
|
|
s = s.replace('"""', '\\"""')
|
|
|
|
for (orig, replace) in (('\t', '\\t'),
|
|
('\n', '\\n'),
|
|
('\r', '\\r')):
|
|
s = s.replace(orig, replace)
|
|
return "%s%s%s" % (quote, s, quote)
|
|
|
|
# FIXME: this and find_globals could be parameterized with one of the
|
|
# above global ops
|
|
def find_all_globals(node, globs):
|
|
"""Search Syntax Tree node to find variable names that are global."""
|
|
for n in node:
|
|
if isinstance(n, SyntaxTree):
|
|
globs = find_all_globals(n, globs)
|
|
elif n.kind in read_write_global_ops:
|
|
globs.add(n.pattr)
|
|
return globs
|
|
|
|
# def find_globals(node, globs, global_ops=mkfunc_globals):
|
|
# """Find globals in this statement."""
|
|
# for n in node:
|
|
# # print("XXX", n.kind, global_ops)
|
|
# if isinstance(n, SyntaxTree):
|
|
# # FIXME: do I need a caser for n.kind="mkfunc"?
|
|
# if n.kind in ("if_exp_lambda", "return_expr_lambda"):
|
|
# globs = find_globals(n, globs, lambda_body_globals)
|
|
# else:
|
|
# globs = find_globals(n, globs, global_ops)
|
|
# elif n.kind in frozenset(global_ops):
|
|
# globs.add(n.pattr)
|
|
# return globs
|
|
|
|
def find_code_node(node, start):
|
|
for i in range(-start, len(node) + 1):
|
|
if node[-i].kind == "LOAD_CODE":
|
|
code_node = node[-i]
|
|
assert iscode(code_node.attr)
|
|
return code_node
|
|
pass
|
|
assert False, "did not find code node starting at %d in %s" % (start, node)
|
|
|
|
|
|
def find_globals_and_nonlocals(node, globs, nonlocals, code, version):
|
|
"""search a node of parse tree to find variable names that need a
|
|
either 'global' or 'nonlocal' statements added."""
|
|
for n in node:
|
|
if isinstance(n, SyntaxTree):
|
|
globs, nonlocals = find_globals_and_nonlocals(n, globs, nonlocals,
|
|
code, version)
|
|
elif n.kind in read_global_ops:
|
|
globs.add(n.pattr)
|
|
elif (version >= (3, 0)
|
|
and n.kind in nonglobal_ops
|
|
and n.pattr in code.co_freevars
|
|
and n.pattr != code.co_name
|
|
and code.co_name != '<lambda>'):
|
|
nonlocals.add(n.pattr)
|
|
return globs, nonlocals
|
|
|
|
def find_none(node):
|
|
for n in node:
|
|
if isinstance(n, SyntaxTree):
|
|
if n not in ('return_stmt', 'return_if_stmt'):
|
|
if find_none(n):
|
|
return True
|
|
elif n.kind == 'LOAD_CONST' and n.pattr is None:
|
|
return True
|
|
return False
|
|
|
|
def flatten_list(node):
|
|
"""
|
|
List of expressions may be nested in groups of 32 and 1024
|
|
items. flatten that out and return the list
|
|
"""
|
|
flat_elems = []
|
|
for elem in node:
|
|
if elem == 'expr1024':
|
|
for subelem in elem:
|
|
assert subelem == 'expr32'
|
|
for subsubelem in subelem:
|
|
flat_elems.append(subsubelem)
|
|
elif elem == 'expr32':
|
|
for subelem in elem:
|
|
assert subelem == 'expr'
|
|
flat_elems.append(subelem)
|
|
else:
|
|
flat_elems.append(elem)
|
|
pass
|
|
pass
|
|
return flat_elems
|
|
|
|
# Note: this is only used in Python > 3.0
|
|
# Should move this somewhere more specific?
|
|
def gen_function_parens_adjust(mapping_key, node):
|
|
"""If we can avoid the outer parenthesis
|
|
of a generator function, set the node key to
|
|
'call_generator' and the caller will do the default
|
|
action on that. Otherwise we do nothing.
|
|
"""
|
|
if mapping_key.kind != 'CALL_FUNCTION_1':
|
|
return
|
|
|
|
args_node = node[-2]
|
|
if args_node == 'pos_arg':
|
|
assert args_node[0] == 'expr'
|
|
n = args_node[0][0]
|
|
if n == 'generator_exp':
|
|
node.kind = 'call_generator'
|
|
pass
|
|
return
|
|
|
|
def is_lambda_mode(compile_mode: str) -> bool:
|
|
return compile_mode in ("dictcomp", "genexpr", "lambda", "listcomp", "setcomp")
|
|
|
|
|
|
def print_docstring(self, indent, docstring):
|
|
quote = '"""'
|
|
if docstring.find(quote) >= 0:
|
|
if docstring.find("'''") == -1:
|
|
quote = "'''"
|
|
|
|
self.write(indent)
|
|
docstring = repr(docstring.expandtabs())[1:-1]
|
|
|
|
for (orig, replace) in (('\\\\', '\t'),
|
|
('\\r\\n', '\n'),
|
|
('\\n', '\n'),
|
|
('\\r', '\n'),
|
|
('\\"', '"'),
|
|
("\\'", "'")):
|
|
docstring = docstring.replace(orig, replace)
|
|
|
|
# Do a raw string if there are backslashes but no other escaped characters:
|
|
# also check some edge cases
|
|
if ('\t' in docstring
|
|
and '\\' not in docstring
|
|
and len(docstring) >= 2
|
|
and docstring[-1] != '\t'
|
|
and (docstring[-1] != '"'
|
|
or docstring[-2] == '\t')):
|
|
self.write('r') # raw string
|
|
# Restore backslashes unescaped since raw
|
|
docstring = docstring.replace('\t', '\\')
|
|
else:
|
|
# Escape the last character if it is the same as the
|
|
# triple quote character.
|
|
quote1 = quote[-1]
|
|
if len(docstring) and docstring[-1] == quote1:
|
|
docstring = docstring[:-1] + '\\' + quote1
|
|
|
|
# Escape triple quote when needed
|
|
if quote == '"""':
|
|
replace_str = '\\"""'
|
|
else:
|
|
assert quote == "'''"
|
|
replace_str = "\\'''"
|
|
|
|
docstring = docstring.replace(quote, replace_str)
|
|
docstring = docstring.replace('\t', '\\\\')
|
|
|
|
lines = docstring.split('\n')
|
|
|
|
self.write(quote)
|
|
if len(lines) == 0:
|
|
self.println(quote)
|
|
elif len(lines) == 1:
|
|
self.println(lines[0], quote)
|
|
else:
|
|
self.println(lines[0])
|
|
for line in lines[1:-1]:
|
|
if line:
|
|
self.println( line )
|
|
else:
|
|
self.println( "\n\n" )
|
|
pass
|
|
pass
|
|
self.println(lines[-1], quote)
|
|
return True
|
|
|
|
def strip_quotes(s):
|
|
if s.startswith("'''") and s.endswith("'''"):
|
|
s = s[3:-3]
|
|
elif s.startswith('"""') and s.endswith('"""'):
|
|
s = s[3:-3]
|
|
elif s.startswith("'") and s.endswith("'"):
|
|
s = s[1:-1]
|
|
elif s.startswith('"') and s.endswith('"'):
|
|
s = s[1:-1]
|
|
pass
|
|
return s
|
|
|
|
|
|
|
|
# if __name__ == '__main__':
|
|
# from io import StringIO
|
|
# class PrintFake():
|
|
# def __init__(self):
|
|
# self.pending_newlines = 0
|
|
# self.f = StringIO()
|
|
|
|
# def write(self, *data):
|
|
# if (len(data) == 0) or (len(data) == 1 and data[0] == ''):
|
|
# return
|
|
# out = ''.join((str(j) for j in data))
|
|
# n = 0
|
|
# for i in out:
|
|
# if i == '\n':
|
|
# n += 1
|
|
# if n == len(out):
|
|
# self.pending_newlines = max(self.pending_newlines, n)
|
|
# return
|
|
# elif n:
|
|
# self.pending_newlines = max(self.pending_newlines, n)
|
|
# out = out[n:]
|
|
# break
|
|
# else:
|
|
# break
|
|
|
|
# if self.pending_newlines > 0:
|
|
# self.f.write('\n'*self.pending_newlines)
|
|
# self.pending_newlines = 0
|
|
|
|
# for i in out[::-1]:
|
|
# if i == '\n':
|
|
# self.pending_newlines += 1
|
|
# else:
|
|
# break
|
|
|
|
# if self.pending_newlines:
|
|
# out = out[:-self.pending_newlines]
|
|
# self.f.write(out)
|
|
# def println(self, *data):
|
|
# if data and not(len(data) == 1 and data[0] ==''):
|
|
# self.write(*data)
|
|
# self.pending_newlines = max(self.pending_newlines, 1)
|
|
# return
|
|
# pass
|
|
|
|
# for doc in (
|
|
# "Now is the time",
|
|
# r'''func placeholder - with ("""\nstring\n""")''',
|
|
# r'''func placeholder - ' and with ("""\nstring\n""")''',
|
|
# r"""func placeholder - ' and with ('''\nstring\n''') and \"\"\"\nstring\n\"\"\" """
|
|
# ):
|
|
# o = PrintFake()
|
|
# print_docstring(o, ' ', doc)
|
|
# print(o.f.getvalue())
|