| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 |
- # 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):
- if isinstance(docstring, bytes):
- docstring = docstring.decode("utf8", errors="backslashreplace")
- 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())
|