Merge branch 'python-3.0-to-3.2' into python-2.4

This commit is contained in:
rocky
2024-02-11 12:18:39 -05:00
15 changed files with 389 additions and 171 deletions

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2018, 2020-2021 Rocky Bernstein <rocky@gnu.org> # Copyright (C) 2018, 2020-2021 2024 Rocky Bernstein <rocky@gnu.org>
# #
# This program is free software: you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by
@@ -32,9 +32,11 @@
# 3.3 | pip | 10.0.1 | # 3.3 | pip | 10.0.1 |
# 3.4 | pip | 19.1.1 | # 3.4 | pip | 19.1.1 |
import os.path as osp
# Things that change more often go here. # Things that change more often go here.
copyright = """ copyright = """
Copyright (C) 2015-2021 Rocky Bernstein <rb@dustyfeet.com>. Copyright (C) 2015-2021, 2024 Rocky Bernstein <rb@dustyfeet.com>.
""" """
classifiers = [ classifiers = [
@@ -75,7 +77,7 @@ entry_points = {
] ]
} }
ftp_url = None ftp_url = None
install_requires = ["spark-parser >= 1.8.9, < 1.9.0", "xdis >= 6.0.8, < 6.2.0"] install_requires = ["click", "spark-parser >= 1.8.9, < 1.9.0", "xdis >= 6.0.8, < 6.2.0"]
license = "GPL3" license = "GPL3"
mailing_list = "python-debugger@googlegroups.com" mailing_list = "python-debugger@googlegroups.com"
@@ -88,21 +90,18 @@ web = "https://github.com/rocky/python-uncompyle6/"
zip_safe = True zip_safe = True
import os.path
def get_srcdir(): def get_srcdir():
filename = os.path.normcase(os.path.dirname(os.path.abspath(__file__))) filename = osp.normcase(osp.dirname(osp.abspath(__file__)))
return os.path.realpath(filename) return osp.realpath(filename)
srcdir = get_srcdir() srcdir = get_srcdir()
def read(*rnames): def read(*rnames):
return open(os.path.join(srcdir, *rnames)).read() return open(osp.join(srcdir, *rnames)).read()
# Get info from files; set: long_description and __version__ # Get info from files; set: long_description and VERSION
long_description = read("README.rst") + "\n" long_description = read("README.rst") + "\n"
exec(read("uncompyle6/version.py")) exec(read("uncompyle6/version.py"))

5
admin-tools/merge-for-2.4.sh Executable file
View File

@@ -0,0 +1,5 @@
#/bin/bash
cd $(dirname ${BASH_SOURCE[0]})
if . ./setup-python-2.4.sh; then
git merge python-3.0-to-3.2
fi

5
admin-tools/merge-for-3.0.sh Executable file
View File

@@ -0,0 +1,5 @@
#/bin/bash
cd $(dirname ${BASH_SOURCE[0]})
if . ./setup-python-3.0.sh; then
git merge python-3.3-to-3.5
fi

5
admin-tools/merge-for-3.3.sh Executable file
View File

@@ -0,0 +1,5 @@
#/bin/bash
cd $(dirname ${BASH_SOURCE[0]})
if . ./setup-python-3.3.sh; then
git merge master
fi

View File

@@ -1,9 +1,22 @@
#!/usr/bin/env python #!/usr/bin/env python
# Mode: -*- python -*-
# #
# Copyright (c) 2015-2016, 2018, 2020, 2022-2023 by Rocky Bernstein # Copyright (c) 2015-2016, 2018, 2020, 2022-2024
# <rb@dustyfeet.com> # by Rocky Bernstein <rb@dustyfeet.com>
# #
# 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 getopt import getopt
import os import os
import sys import sys
@@ -48,10 +61,9 @@ PATTERNS = ("*.pyc", "*.pyo")
def main(): def main():
Usage_short = ( usage_short = (
"""usage: %s FILE... f"""usage: {program} FILE...
Type -h for for full help.""" Type -h for for full help."""
% program
) )
if len(sys.argv) == 1: if len(sys.argv) == 1:
@@ -72,7 +84,7 @@ Type -h for for full help."""
print(__doc__) print(__doc__)
sys.exit(1) sys.exit(1)
elif opt in ("-V", "--version"): elif opt in ("-V", "--version"):
print("%s %s" % (program, __version__)) print(f"{program} {__version__}")
sys.exit(0) sys.exit(0)
else: else:
print(opt) print(opt)

View File

@@ -1,11 +1,11 @@
#!/usr/bin/env python #!/usr/bin/env python
# Mode: -*- python -*- # Mode: -*- python -*-
# #
# Copyright (c) 2015-2017, 2019-2020, 2023 by Rocky Bernstein # Copyright (c) 2015-2017, 2019-2020, 2023-2024
# by Rocky Bernstein
# Copyright (c) 2000-2002 by hartmut Goebel <h.goebel@crazy-compilers.com> # Copyright (c) 2000-2002 by hartmut Goebel <h.goebel@crazy-compilers.com>
# #
import getopt
import os import os
import sys import sys
import time import time
@@ -16,6 +16,12 @@ from uncompyle6.version import __version__
program = "uncompyle6" program = "uncompyle6"
def usage():
print(__doc__)
sys.exit(1)
__doc__ = """ __doc__ = """
Usage: Usage:
%s [OPTIONS]... [ FILE | DIR]... %s [OPTIONS]... [ FILE | DIR]...
@@ -68,44 +74,18 @@ Extensions of generated files:
(program,) * 5 (program,) * 5
) )
program = "uncompyle6"
def usage():
print(__doc__)
sys.exit(1)
def main_bin():
if not (
sys.version_info[0:2]
in (
(2, 4),
(2, 5),
(2, 6),
(2, 7),
(3, 0),
(3, 1),
(3, 2),
(3, 3),
(3, 4),
(3, 5),
(3, 6),
(3, 7),
(3, 8),
(3, 9),
(3, 10),
(3, 11),
)
): ):
print('Error: %s requires Python 2.4-3.10' % program) print('Error: %s requires Python 2.4-3.10' % program)
sys.exit(-1) sys.exit(-1)
recurse_dirs = False
numproc = 0 numproc = 0
outfile = "-" out_base = None
out_base = None out_base = None
source_paths = [] source_paths = []
timestamp = False timestamp = False
timestampfmt = "# %Y.%m.%d %H:%M:%S %Z" timestampfmt = "# %Y.%m.%d %H:%M:%S %Z"
pyc_paths = files
try: try:
opts, pyc_paths = getopt.getopt( opts, pyc_paths = getopt.getopt(
@@ -182,7 +162,7 @@ def main_bin():
sys.stderr.write(opt) sys.stderr.write(opt)
usage() usage()
# expand directory if specified # Expand directory if "recurse" was specified.
if recurse_dirs: if recurse_dirs:
expanded_files = [] expanded_files = []
for f in pyc_paths: for f in pyc_paths:
@@ -216,15 +196,32 @@ def main_bin():
out_base = outfile out_base = outfile
outfile = None outfile = None
# A second -a turns show_asm="after" into show_asm="before"
if asm_plus or asm:
asm_opt = "both" if asm_plus else "after"
else:
asm_opt = None
if timestamp: if timestamp:
print(time.strftime(timestampfmt)) print(time.strftime(timestampfmt))
if numproc <= 1: if numproc <= 1:
show_ast = {"before": tree or tree_plus, "after": tree_plus}
try: try:
result = main( result = main(
src_base, out_base, pyc_paths, source_paths, outfile, **options src_base,
out_base,
pyc_paths,
source_paths,
outfile,
showasm=asm_opt,
showgrammar=show_grammar,
showast=show_ast,
do_verify=verify,
do_linemaps=linemaps,
start_offset=start_offset,
stop_offset=stop_offset,
) )
result = [options.get("do_verify", None)] + list(result)
if len(pyc_paths) > 1: if len(pyc_paths) > 1:
mess = status_msg(*result) mess = status_msg(*result)
print("# " + mess) print("# " + mess)

View File

@@ -13,10 +13,14 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import ast
import datetime import datetime
import os import os
import os.path as osp
import py_compile import py_compile
import subprocess
import sys import sys
import tempfile
from xdis import iscode from xdis import iscode
from xdis.load import load_module from xdis.load import load_module
@@ -38,9 +42,9 @@ def _get_outstream(outfile):
""" """
Return an opened output file descriptor for ``outfile``. Return an opened output file descriptor for ``outfile``.
""" """
dir_name = os.path.dirname(outfile) dir_name = osp.dirname(outfile)
failed_file = outfile + "_failed" failed_file = outfile + "_failed"
if os.path.exists(failed_file): if osp.exists(failed_file):
os.remove(failed_file) os.remove(failed_file)
try: try:
os.makedirs(dir_name) os.makedirs(dir_name)
@@ -48,6 +52,17 @@ def _get_outstream(outfile):
pass pass
return open(outfile, 'wb') return open(outfile, 'wb')
def syntax_check(filename: str) -> bool:
with open(filename) as f:
source = f.read()
valid = True
try:
ast.parse(source)
except SyntaxError:
valid = False
return valid
def decompile( def decompile(
co, co,
bytecode_version=PYTHON_VERSION_TRIPLE, bytecode_version=PYTHON_VERSION_TRIPLE,
@@ -59,11 +74,13 @@ def decompile(
source_encoding=None, source_encoding=None,
code_objects={}, code_objects={},
source_size=None, source_size=None,
is_pypy=False, is_pypy: bool = False,
magic_int=None, magic_int=None,
mapstream=None, mapstream=None,
do_fragments=False, do_fragments=False,
compile_mode="exec", compile_mode="exec",
start_offset: int = 0,
stop_offset: int = -1,
): ):
""" """
ingests and deparses a given code block 'co' ingests and deparses a given code block 'co'
@@ -152,11 +169,12 @@ def decompile(
compile_mode=compile_mode, compile_mode=compile_mode,
) )
header_count = 3 + len(sys_version_lines) header_count = 3 + len(sys_version_lines)
linemap = [ if deparsed is not None:
(line_no, deparsed.source_linemap[line_no] + header_count) linemap = [
for line_no in sorted(deparsed.source_linemap.keys()) (line_no, deparsed.source_linemap[line_no] + header_count)
] for line_no in sorted(deparsed.source_linemap.keys())
mapstream.write("\n\n# %s\n" % linemap) ]
mapstream.write("\n\n# %s\n" % linemap)
else: else:
if do_fragments: if do_fragments:
deparse_fn = code_deparse_fragments deparse_fn = code_deparse_fragments
@@ -169,8 +187,11 @@ def decompile(
is_pypy=is_pypy, is_pypy=is_pypy,
debug_opts=debug_opts, debug_opts=debug_opts,
compile_mode=compile_mode, compile_mode=compile_mode,
start_offset=start_offset,
stop_offset=stop_offset,
) )
pass pass
real_out.write("\n")
return deparsed return deparsed
except pysource.SourceWalkerError, e: except pysource.SourceWalkerError, e:
# deparsing failed # deparsing failed
@@ -194,7 +215,7 @@ def compile_file(source_path):
def decompile_file( def decompile_file(
filename, filename: str,
outstream=None, outstream=None,
showasm=None, showasm=None,
showast={}, showast={},
@@ -202,6 +223,8 @@ def decompile_file(
source_encoding=None, source_encoding=None,
mapstream=None, mapstream=None,
do_fragments=False, do_fragments=False,
start_offset=0,
stop_offset=-1,
): ):
""" """
decompile Python byte-code file (.pyc). Return objects to decompile Python byte-code file (.pyc). Return objects to
@@ -231,6 +254,8 @@ def decompile_file(
is_pypy=is_pypy, is_pypy=is_pypy,
magic_int=magic_int, magic_int=magic_int,
mapstream=mapstream, mapstream=mapstream,
start_offset=start_offset,
stop_offset=stop_offset,
), ),
) )
else: else:
@@ -251,6 +276,8 @@ def decompile_file(
mapstream=mapstream, mapstream=mapstream,
do_fragments=do_fragments, do_fragments=do_fragments,
compile_mode="exec", compile_mode="exec",
start_offset=start_offset,
stop_offset=stop_offset,
) )
] ]
return deparsed return deparsed
@@ -265,10 +292,13 @@ def main(
outfile=None, outfile=None,
showasm=None, showasm=None,
showast={}, showast={},
showgrammar=False, do_verify=None,
showgrammar: bool = False,
source_encoding=None, source_encoding=None,
do_linemaps=False, do_linemaps=False,
do_fragments=False, do_fragments=False,
start_offset: int = 0,
stop_offset: int = -1,
): ):
""" """
in_base base directory for input files in_base base directory for input files
@@ -281,7 +311,8 @@ def main(
- files below out_base out_base=... - files below out_base out_base=...
- stdout out_base=None, outfile=None - stdout out_base=None, outfile=None
""" """
tot_files = okay_files = failed_files = verify_failed_files = 0 tot_files = okay_files = failed_files = 0
verify_failed_files = 0 if do_verify else 0
current_outfile = outfile current_outfile = outfile
linemap_stream = None linemap_stream = None
@@ -289,9 +320,9 @@ def main(
compiled_files.append(compile_file(source_path)) compiled_files.append(compile_file(source_path))
for filename in compiled_files: for filename in compiled_files:
infile = os.path.join(in_base, filename) infile = osp.join(in_base, filename)
# print("XXX", infile) # print("XXX", infile)
if not os.path.exists(infile): if not osp.exists(infile):
sys.stderr.write("File '%s' doesn't exist. Skipped\n" % infile) sys.stderr.write("File '%s' doesn't exist. Skipped\n" % infile)
continue continue
@@ -304,14 +335,19 @@ def main(
if outfile: # outfile was given as parameter if outfile: # outfile was given as parameter
outstream = _get_outstream(outfile) outstream = _get_outstream(outfile)
elif out_base is None: elif out_base is None:
outstream = sys.stdout out_base = tempfile.mkdtemp(prefix="py-dis-")
if do_verify and filename.endswith(".pyc"):
current_outfile = osp.join(out_base, filename[0:-1])
outstream = open(current_outfile, "w")
else:
outstream = sys.stdout
if do_linemaps: if do_linemaps:
linemap_stream = sys.stdout linemap_stream = sys.stdout
else: else:
if filename.endswith(".pyc"): if filename.endswith(".pyc"):
current_outfile = os.path.join(out_base, filename[0:-1]) current_outfile = osp.join(out_base, filename[0:-1])
else: else:
current_outfile = os.path.join(out_base, filename) + "_dis" current_outfile = osp.join(out_base, filename) + "_dis"
pass pass
pass pass
@@ -319,9 +355,9 @@ def main(
# print(current_outfile, file=sys.stderr) # print(current_outfile, file=sys.stderr)
# Try to uncompile the input file # Try to decompile the input file.
try: try:
deparsed = decompile_file( deparsed_objects = decompile_file(
infile, infile,
outstream, outstream,
showasm, showasm,
@@ -330,11 +366,13 @@ def main(
source_encoding, source_encoding,
linemap_stream, linemap_stream,
do_fragments, do_fragments,
start_offset,
stop_offset,
) )
if do_fragments: if do_fragments:
for d in deparsed: for deparsed_object in deparsed_objects:
last_mod = None last_mod = None
offsets = d.offsets offsets = deparsed_object.offsets
for e in sorted( for e in sorted(
[k for k in offsets.keys() if isinstance(k[1], int)] [k for k in offsets.keys() if isinstance(k[1], int)]
): ):
@@ -343,11 +381,58 @@ def main(
outstream.write("%s\n%s\n%s\n" % (line, e[0], line)) outstream.write("%s\n%s\n%s\n" % (line, e[0], line))
last_mod = e[0] last_mod = e[0]
info = offsets[e] info = offsets[e]
extract_info = d.extract_node_info(info) extract_info = deparse_object.extract_node_info(info)
outstream.write("%s" % info.node.format().strip() + "\n") outstream.write("%s" % info.node.format().strip() + "\n")
outstream.write(extract_info.selectedLine + "\n") outstream.write(extract_info.selectedLine + "\n")
outstream.write(extract_info.markerLine + "\n\n") outstream.write(extract_info.markerLine + "\n\n")
pass pass
if do_verify:
for deparsed_object in deparsed_objects:
deparsed_object.f.close()
if PYTHON_VERSION_TRIPLE[:2] != deparsed_object.version[:2]:
sys.stdout.write(
"\n# skipping running %s; it is %s and we are %s"
% (
deparsed_object.f.name,
version_tuple_to_str(deparsed_object.version, end=2),
version_tuple_to_str(PYTHON_VERSION_TRIPLE, end=2),
)
)
else:
check_type = "syntax check"
if do_verify == "run":
check_type = "run"
if PYTHON_VERSION_TRIPLE >= (3, 7):
result = subprocess.run(
[sys.executable, deparsed_object.f.name],
capture_output=True,
)
valid = result.returncode == 0
output = result.stdout.decode()
if output:
print(output)
pass
else:
result = subprocess.run(
[sys.executable, deparsed_object.f.name],
)
valid = result.returncode == 0
pass
if not valid:
print(result.stderr.decode())
else:
valid = syntax_check(deparsed_object.f.name)
if not valid:
verify_failed_files += 1
sys.stderr.write(
"\n# %s failed on file %s\n"
% (check_type, deparsed_object.f.name)
)
# sys.stderr.write("Ran %\n" % deparsed_object.f.name)
pass pass
tot_files += 1 tot_files += 1
except (ValueError, SyntaxError, ParserError, pysource.SourceWalkerError): except (ValueError, SyntaxError, ParserError, pysource.SourceWalkerError):

View File

@@ -869,6 +869,29 @@ class Python3Parser(PythonParser):
rule = "starred ::= %s %s" % ("expr " * v, opname) rule = "starred ::= %s %s" % ("expr " * v, opname)
self.addRule(rule, nop_func) self.addRule(rule, nop_func)
elif opname in ("BUILD_CONST_LIST", "BUILD_CONST_DICT", "BUILD_CONST_SET"):
if opname == "BUILD_CONST_DICT":
rule = (
"""
add_consts ::= ADD_VALUE*
const_list ::= COLLECTION_START add_consts %s
dict ::= const_list
expr ::= dict
"""
% opname
)
else:
rule = (
"""
add_consts ::= ADD_VALUE*
const_list ::= COLLECTION_START add_consts %s
expr ::= const_list
"""
% opname
)
self.addRule(rule, nop_func)
>>>>>>> python-3.0-to-3.2
elif opname_base in ( elif opname_base in (
"BUILD_LIST", "BUILD_LIST",
"BUILD_SET", "BUILD_SET",
@@ -1191,6 +1214,8 @@ class Python3Parser(PythonParser):
self.add_unique_rule(rule, opname, token.attr, customize) self.add_unique_rule(rule, opname, token.attr, customize)
elif (3, 3) <= self.version < (3, 6): elif (3, 3) <= self.version < (3, 6):
# FIXME move this into version-specific custom rules.
# In fact, some of this has been done for 3.3.
if annotate_args > 0: if annotate_args > 0:
rule = ( rule = (
"mkfunc_annotate ::= %s%s%sannotate_tuple load_closure LOAD_CODE LOAD_STR %s" "mkfunc_annotate ::= %s%s%sannotate_tuple load_closure LOAD_CODE LOAD_STR %s"
@@ -1454,9 +1479,6 @@ class Python3Parser(PythonParser):
) )
) )
if self.version >= (3, 3): if self.version >= (3, 3):
# Normally we remove EXTENDED_ARG from the opcodes, but in the case of
# annotated functions can use the EXTENDED_ARG tuple to signal we have an annotated function.
# Yes this is a little hacky
if self.version == (3, 3): if self.version == (3, 3):
# 3.3 puts kwargs before pos_arg # 3.3 puts kwargs before pos_arg
pos_kw_tuple = ( pos_kw_tuple = (
@@ -1470,17 +1492,17 @@ class Python3Parser(PythonParser):
("kwargs " * kw_args_count), ("kwargs " * kw_args_count),
) )
rule = ( rule = (
"mkfunc_annotate ::= %s%s%sannotate_tuple LOAD_CODE LOAD_STR EXTENDED_ARG %s" "mkfunc_annotate ::= %s%s%sannotate_tuple LOAD_CODE LOAD_STR %s"
% ( % (
pos_kw_tuple[0], pos_kw_tuple[0],
pos_kw_tuple[1], pos_kw_tuple[1],
("call " * annotate_args), ("annotate_arg " * annotate_args),
opname, opname,
) )
) )
self.add_unique_rule(rule, opname, token.attr, customize) self.add_unique_rule(rule, opname, token.attr, customize)
rule = ( rule = (
"mkfunc_annotate ::= %s%s%sannotate_tuple LOAD_CODE LOAD_STR EXTENDED_ARG %s" "mkfunc_annotate ::= %s%s%sannotate_tuple LOAD_CODE LOAD_STR %s"
% ( % (
pos_kw_tuple[0], pos_kw_tuple[0],
pos_kw_tuple[1], pos_kw_tuple[1],
@@ -1489,9 +1511,8 @@ class Python3Parser(PythonParser):
) )
) )
else: else:
# See above comment about use of EXTENDED_ARG
rule = ( rule = (
"mkfunc_annotate ::= %s%s%sannotate_tuple LOAD_CODE EXTENDED_ARG %s" "mkfunc_annotate ::= %s%s%sannotate_tuple LOAD_CODE %s"
% ( % (
("kwargs " * kw_args_count), ("kwargs " * kw_args_count),
("pos_arg " * (pos_args_count)), ("pos_arg " * (pos_args_count)),
@@ -1501,7 +1522,7 @@ class Python3Parser(PythonParser):
) )
self.add_unique_rule(rule, opname, token.attr, customize) self.add_unique_rule(rule, opname, token.attr, customize)
rule = ( rule = (
"mkfunc_annotate ::= %s%s%sannotate_tuple LOAD_CODE EXTENDED_ARG %s" "mkfunc_annotate ::= %s%s%sannotate_tuple LOAD_CODE %s"
% ( % (
("kwargs " * kw_args_count), ("kwargs " * kw_args_count),
("pos_arg " * pos_args_count), ("pos_arg " * pos_args_count),

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2016 Rocky Bernstein # Copyright (c) 2016, 2024 Rocky Bernstein
""" """
spark grammar differences over Python 3.2 for Python 3.3. spark grammar differences over Python 3.2 for Python 3.3.
""" """
@@ -8,7 +8,6 @@ from uncompyle6.parsers.parse32 import Python32Parser
class Python33Parser(Python32Parser): class Python33Parser(Python32Parser):
def p_33on(self, args): def p_33on(self, args):
""" """
# Python 3.3+ adds yield from. # Python 3.3+ adds yield from.
@@ -18,13 +17,22 @@ class Python33Parser(Python32Parser):
""" """
def customize_grammar_rules(self, tokens, customize): def customize_grammar_rules(self, tokens, customize):
self.remove_rules(""" self.remove_rules(
"""
# 3.3+ adds POP_BLOCKS # 3.3+ adds POP_BLOCKS
whileTruestmt ::= SETUP_LOOP l_stmts_opt JUMP_BACK POP_BLOCK NOP COME_FROM_LOOP whileTruestmt ::= SETUP_LOOP l_stmts_opt JUMP_BACK POP_BLOCK NOP COME_FROM_LOOP
whileTruestmt ::= SETUP_LOOP l_stmts_opt JUMP_BACK NOP COME_FROM_LOOP whileTruestmt ::= SETUP_LOOP l_stmts_opt JUMP_BACK NOP COME_FROM_LOOP
""") """
)
super(Python33Parser, self).customize_grammar_rules(tokens, customize) super(Python33Parser, self).customize_grammar_rules(tokens, customize)
# FIXME: move 3.3 stuff out of parse3.py and put it here.
# for i, token in enumerate(tokens):
# opname = token.kind
# opname_base = opname[: opname.rfind("_")]
return return
class Python33ParserSingle(Python33Parser, PythonParserSingle): class Python33ParserSingle(Python33Parser, PythonParserSingle):
pass pass

View File

@@ -97,6 +97,10 @@ class Code(object):
""" """
def __init__(self, co, scanner, classname=None, show_asm=None): def __init__(self, co, scanner, classname=None, show_asm=None):
# Full initialization is given below, but for linters
# well set up some initial values.
self.co_code = None # Really either bytes for >= 3.0 and string in < 3.0
for i in dir(co): for i in dir(co):
if i.startswith("co_"): if i.startswith("co_"):
setattr(self, i, getattr(co, i)) setattr(self, i, getattr(co, i))
@@ -429,7 +433,7 @@ class Scanner:
""" """
try: try:
None in instr None in instr
except: except Exception:
instr = [instr] instr = [instr]
first = self.offset2inst_index[start] first = self.offset2inst_index[start]

View File

@@ -468,6 +468,7 @@ class Scanner3(Scanner):
last_op_was_break = False last_op_was_break = False
new_tokens = [] new_tokens = []
operand_value = 0
for i, inst in enumerate(self.insts): for i, inst in enumerate(self.insts):
opname = inst.opname opname = inst.opname
@@ -518,10 +519,11 @@ class Scanner3(Scanner):
op = inst.opcode op = inst.opcode
if opname == "EXTENDED_ARG": if opname == "EXTENDED_ARG":
# FIXME: The EXTENDED_ARG is used to signal annotation if i + 1 < n:
# parameters operand_value = argval << 16
if i + 1 < n and self.insts[i + 1].opcode != self.opc.MAKE_FUNCTION:
continue continue
else:
operand_value = 0
if inst.offset in jump_targets: if inst.offset in jump_targets:
jump_idx = 0 jump_idx = 0
@@ -628,7 +630,7 @@ class Scanner3(Scanner):
attr = attr[:4] # remove last value: attr[5] == False attr = attr[:4] # remove last value: attr[5] == False
else: else:
pos_args, name_pair_args, annotate_args = parse_fn_counts_30_35( pos_args, name_pair_args, annotate_args = parse_fn_counts_30_35(
inst.argval inst.argval + operand_value
) )
pattr = "%s positional, %s keyword only, %s annotated" % ( pattr = "%s positional, %s keyword only, %s annotated" % (

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2015-2019, 2021-2022 by Rocky Bernstein # Copyright (c) 2015-2019, 2021-2022, 2024 by Rocky Bernstein
# #
# This program is free software: you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by
@@ -21,16 +21,20 @@ scanner routine for Python 3.
# bytecode verification, verify(), uses JUMP_OPs from here # bytecode verification, verify(), uses JUMP_OPs from here
from xdis.opcodes import opcode_33 as opc from xdis.opcodes import opcode_33 as opc
JUMP_OPS = opc.JUMP_OPS
from uncompyle6.scanners.scanner3 import Scanner3 from uncompyle6.scanners.scanner3 import Scanner3
class Scanner33(Scanner3):
JUMP_OPS = opc.JUMP_OPS
class Scanner33(Scanner3):
def __init__(self, show_asm=False, is_pypy=False): def __init__(self, show_asm=False, is_pypy=False):
Scanner3.__init__(self, (3, 3), show_asm) Scanner3.__init__(self, (3, 3), show_asm)
return return
pass pass
if __name__ == "__main__": if __name__ == "__main__":
from xdis.version_info import PYTHON_VERSION_TRIPLE, version_tuple_to_str from xdis.version_info import PYTHON_VERSION_TRIPLE, version_tuple_to_str

View File

@@ -2054,6 +2054,8 @@ def code_deparse(
code_objects={}, code_objects={},
compile_mode="exec", compile_mode="exec",
walker=FragmentsWalker, walker=FragmentsWalker,
start_offset: int = 0,
stop_offset: int = -1,
): ):
""" """
Convert the code object co into a python source fragment. Convert the code object co into a python source fragment.
@@ -2088,6 +2090,22 @@ def code_deparse(
tokens, customize = scanner.ingest(co, code_objects=code_objects, show_asm=show_asm) tokens, customize = scanner.ingest(co, code_objects=code_objects, show_asm=show_asm)
tokens, customize = scanner.ingest(co) tokens, customize = scanner.ingest(co)
if start_offset > 0:
for i, t in enumerate(tokens):
# If t.offset is a string, we want to skip this.
if isinstance(t.offset, int) and t.offset >= start_offset:
tokens = tokens[i:]
break
if stop_offset > -1:
for i, t in enumerate(tokens):
# In contrast to the test for start_offset If t.offset is
# a string, we want to extract the integer offset value.
if t.off2int() >= stop_offset:
tokens = tokens[:i]
break
maybe_show_asm(show_asm, tokens) maybe_show_asm(show_asm, tokens)
debug_parser = dict(PARSER_DEFAULT_DEBUG) debug_parser = dict(PARSER_DEFAULT_DEBUG)

View File

@@ -130,6 +130,7 @@ Python.
# evaluating the escape code. # evaluating the escape code.
import sys import sys
from io import StringIO
from spark_parser import GenericASTTraversal from spark_parser import GenericASTTraversal
from xdis import COMPILER_FLAG_BIT, iscode from xdis import COMPILER_FLAG_BIT, iscode
@@ -158,7 +159,11 @@ from uncompyle6.semantics.consts import (
) )
from uncompyle6.semantics.customize import customize_for_version from uncompyle6.semantics.customize import customize_for_version
from uncompyle6.semantics.gencomp import ComprehensionMixin from uncompyle6.semantics.gencomp import ComprehensionMixin
from uncompyle6.semantics.helper import find_globals_and_nonlocals, print_docstring from uncompyle6.semantics.helper import (
find_globals_and_nonlocals,
is_lambda_mode,
print_docstring,
)
from uncompyle6.semantics.make_function1 import make_function1 from uncompyle6.semantics.make_function1 import make_function1
from uncompyle6.semantics.make_function2 import make_function2 from uncompyle6.semantics.make_function2 import make_function2
from uncompyle6.semantics.make_function3 import make_function3 from uncompyle6.semantics.make_function3 import make_function3
@@ -186,15 +191,6 @@ PARSER_DEFAULT_DEBUG = {
"dups": False, "dups": False,
} }
PARSER_DEFAULT_DEBUG = {
"rules": False,
"transition": False,
"reduce": False,
"errorstack": "full",
"context": True,
"dups": False,
}
IS_PYPY = "__pypy__" in sys.builtin_module_names IS_PYPY = "__pypy__" in sys.builtin_module_names
TREE_DEFAULT_DEBUG = {"before": False, "after": False} TREE_DEFAULT_DEBUG = {"before": False, "after": False}
@@ -216,7 +212,8 @@ class SourceWalkerError(Exception):
class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin): class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
""" """
Class to traverses a Parse Tree of the bytecode instruction built from parsing to produce some sort of source text. Class to traverses a Parse Tree of the bytecode instruction built from parsing to
produce some sort of source text.
The Parse tree may be turned an Abstract Syntax tree as an intermediate step. The Parse tree may be turned an Abstract Syntax tree as an intermediate step.
""" """
@@ -224,7 +221,7 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
def __init__( def __init__(
self, self,
version, version: tuple,
out, out,
scanner, scanner,
showast=TREE_DEFAULT_DEBUG, showast=TREE_DEFAULT_DEBUG,
@@ -234,7 +231,7 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
linestarts={}, linestarts={},
tolerate_errors=False, tolerate_errors=False,
): ):
"""`version' is the Python version (a float) of the Python dialect """`version' is the Python version of the Python dialect
of both the syntax tree and language we should produce. of both the syntax tree and language we should produce.
`out' is IO-like file pointer to where the output should go. It `out' is IO-like file pointer to where the output should go. It
@@ -246,9 +243,12 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
If `showast' is True, we print the syntax tree. If `showast' is True, we print the syntax tree.
`compile_mode' is is either 'exec' or 'single'. It is the compile `compile_mode` is is either `exec`, `single` or `lambda`.
mode that was used to create the Syntax Tree and specifies a
grammar variant within a Python version to use. For `lambda`, the grammar that can be used in lambda
expressions is used. Otherwise, it is the compile mode that
was used to create the Syntax Tree and specifies a grammar
variant within a Python version to use.
`is_pypy` should be True if the Syntax Tree was generated for PyPy. `is_pypy` should be True if the Syntax Tree was generated for PyPy.
@@ -273,10 +273,8 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
self.currentclass = None self.currentclass = None
self.classes = [] self.classes = []
self.debug_parser = dict(debug_parser) self.debug_parser = dict(debug_parser)
# Initialize p_lambda on demand
self.line_number = 1 self.line_number = 1
self.linemap = {} self.linemap = {}
self.p_lambda = None
self.params = params self.params = params
self.param_stack = [] self.param_stack = []
self.ERROR = None self.ERROR = None
@@ -287,11 +285,15 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
self.pending_newlines = 0 self.pending_newlines = 0
self.linestarts = linestarts self.linestarts = linestarts
self.treeTransform = TreeTransform(version=self.version, show_ast=showast) self.treeTransform = TreeTransform(version=self.version, show_ast=showast)
# FIXME: have p.insts update in a better way # FIXME: have p.insts update in a better way
# modularity is broken here # modularity is broken here
self.insts = scanner.insts self.insts = scanner.insts
self.offset2inst_index = scanner.offset2inst_index self.offset2inst_index = scanner.offset2inst_index
# Initialize p_lambda on demand
self.p_lambda = None
# This is in Python 2.6 on. It changes the way # This is in Python 2.6 on. It changes the way
# strings get interpreted. See n_LOAD_CONST # strings get interpreted. See n_LOAD_CONST
self.FUTURE_UNICODE_LITERALS = False self.FUTURE_UNICODE_LITERALS = False
@@ -319,12 +321,13 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
customize_for_version(self, is_pypy, version) customize_for_version(self, is_pypy, version)
return return
def maybe_show_tree(self, ast, phase): def maybe_show_tree(self, tree, phase):
if self.showast.get("before", False): if self.showast.get("before", False):
self.println( self.println(
""" """
---- end before transform ---- end before transform
""" """
+ " "
) )
if self.showast.get("after", False): if self.showast.get("after", False):
self.println( self.println(
@@ -334,7 +337,7 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
+ " " + " "
) )
if self.showast.get(phase, False): if self.showast.get(phase, False):
maybe_show_tree(self, ast) maybe_show_tree(self, tree)
def str_with_template(self, ast): def str_with_template(self, ast):
stream = sys.stdout stream = sys.stdout
@@ -396,7 +399,7 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
def indent_if_source_nl(self, line_number, indent): def indent_if_source_nl(self, line_number, indent):
if line_number != self.line_number: if line_number != self.line_number:
self.write("\n" + indent + INDENT_PER_LEVEL[:-1]) self.write("\n" + indent_spaces + INDENT_PER_LEVEL[:-1])
return self.line_number return self.line_number
f = property( f = property(
@@ -518,19 +521,19 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
def pp_tuple(self, tup): def pp_tuple(self, tup):
"""Pretty print a tuple""" """Pretty print a tuple"""
last_line = self.f.getvalue().split("\n")[-1] last_line = self.f.getvalue().split("\n")[-1]
l = len(last_line) + 1 ll = len(last_line) + 1
indent = " " * l indent = " " * ll
self.write("(") self.write("(")
sep = "" sep = ""
for item in tup: for item in tup:
self.write(sep) self.write(sep)
l += len(sep) ll += len(sep)
s = better_repr(item, self.version) s = better_repr(item, self.version)
l += len(s) ll += len(s)
self.write(s) self.write(s)
sep = "," sep = ","
if l > LINE_LENGTH: if ll > LINE_LENGTH:
l = 0 ll = 0
sep += "\n" + indent sep += "\n" + indent
else: else:
sep += " " sep += " "
@@ -574,6 +577,7 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
def print_super_classes3(self, node): def print_super_classes3(self, node):
n = len(node) - 1 n = len(node) - 1
j = 0
if node.kind != "expr": if node.kind != "expr":
if node == "kwarg": if node == "kwarg":
self.template_engine(("(%[0]{attr}=%c)", 1), node) self.template_engine(("(%[0]{attr}=%c)", 1), node)
@@ -611,9 +615,9 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
self.write("(") self.write("(")
if kwargs: if kwargs:
# Last arg is tuple of keyword values: omit # Last arg is tuple of keyword values: omit
l = n - 1 m = n - 1
else: else:
l = n m = n
if kwargs: if kwargs:
# 3.6+ does this # 3.6+ does this
@@ -625,7 +629,7 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
j += 1 j += 1
j = 0 j = 0
while i < l: while i < m:
self.write(sep) self.write(sep)
value = self.traverse(node[i]) value = self.traverse(node[i])
self.write("%s=%s" % (kwargs[j], value)) self.write("%s=%s" % (kwargs[j], value))
@@ -633,7 +637,7 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
j += 1 j += 1
i += 1 i += 1
else: else:
while i < l: while i < m:
value = self.traverse(node[i]) value = self.traverse(node[i])
i += 1 i += 1
self.write(sep, value) self.write(sep, value)
@@ -709,9 +713,10 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
""" """
# print("-----") # print("-----")
# print(startnode) # print(startnode.kind)
# print(entry[0]) # print(entry[0])
# print('======') # print('======')
fmt = entry[0] fmt = entry[0]
arg = 1 arg = 1
i = 0 i = 0
@@ -880,7 +885,7 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
d = node.__dict__ d = node.__dict__
try: try:
self.write(eval(expr, d, d)) self.write(eval(expr, d, d))
except: except Exception:
raise raise
m = escape.search(fmt, i) m = escape.search(fmt, i)
self.write(fmt[i:]) self.write(fmt[i:])
@@ -1104,8 +1109,8 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
# if docstring exists, dump it # if docstring exists, dump it
if code.co_consts and code.co_consts[0] is not None and len(ast) > 0: if code.co_consts and code.co_consts[0] is not None and len(ast) > 0:
do_doc = False do_doc = False
i = 0
if is_docstring(ast[0], self.version, code.co_consts): if is_docstring(ast[0], self.version, code.co_consts):
i = 0
do_doc = True do_doc = True
elif len(ast) > 1 and is_docstring(ast[1], self.version, code.co_consts): elif len(ast) > 1 and is_docstring(ast[1], self.version, code.co_consts):
i = 1 i = 1
@@ -1201,7 +1206,7 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
is_lambda=False, is_lambda=False,
noneInNames=False, noneInNames=False,
is_top_level_module=False, is_top_level_module=False,
): ) -> GenericASTTraversal:
# FIXME: DRY with fragments.py # FIXME: DRY with fragments.py
# assert isinstance(tokens[0], Token) # assert isinstance(tokens[0], Token)
@@ -1254,7 +1259,7 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
# Build a parse tree from a tokenized and massaged disassembly. # Build a parse tree from a tokenized and massaged disassembly.
try: try:
# FIXME: have p.insts update in a better way # FIXME: have p.insts update in a better way
# modularity is broken here # Modularity is broken here.
p_insts = self.p.insts p_insts = self.p.insts
self.p.insts = self.scanner.insts self.p.insts = self.scanner.insts
self.p.offset2inst_index = self.scanner.offset2inst_index self.p.offset2inst_index = self.scanner.offset2inst_index
@@ -1267,6 +1272,7 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
checker(ast, False, self.ast_errors) checker(ast, False, self.ast_errors)
self.customize(customize) self.customize(customize)
transform_tree = self.treeTransform.transform(ast, code) transform_tree = self.treeTransform.transform(ast, code)
self.maybe_show_tree(ast, phase="before") self.maybe_show_tree(ast, phase="before")
@@ -1288,6 +1294,8 @@ def code_deparse(
compile_mode="exec", compile_mode="exec",
is_pypy=IS_PYPY, is_pypy=IS_PYPY,
walker=SourceWalker, walker=SourceWalker,
start_offset: int = 0,
stop_offset: int = -1,
): ):
""" """
ingests and deparses a given code block 'co'. If version is None, ingests and deparses a given code block 'co'. If version is None,
@@ -1296,6 +1304,9 @@ def code_deparse(
assert iscode(co) assert iscode(co)
if out is None:
out = sys.stdout
if version is None: if version is None:
version = PYTHON_VERSION_TRIPLE version = PYTHON_VERSION_TRIPLE
@@ -1306,6 +1317,21 @@ def code_deparse(
co, code_objects=code_objects, show_asm=debug_opts["asm"] co, code_objects=code_objects, show_asm=debug_opts["asm"]
) )
if start_offset > 0:
for i, t in enumerate(tokens):
# If t.offset is a string, we want to skip this.
if isinstance(t.offset, int) and t.offset >= start_offset:
tokens = tokens[i:]
break
if stop_offset > -1:
for i, t in enumerate(tokens):
# In contrast to the test for start_offset If t.offset is
# a string, we want to extract the integer offset value.
if t.off2int() >= stop_offset:
tokens = tokens[:i]
break
debug_parser = debug_opts.get("grammar", dict(PARSER_DEFAULT_DEBUG)) debug_parser = debug_opts.get("grammar", dict(PARSER_DEFAULT_DEBUG))
# Build Syntax Tree from disassembly. # Build Syntax Tree from disassembly.
@@ -1329,7 +1355,7 @@ def code_deparse(
tokens, tokens,
customize, customize,
co, co,
is_lambda=(compile_mode == "lambda"), is_lambda=is_lambda_mode(compile_mode),
is_top_level_module=is_top_level_module, is_top_level_module=is_top_level_module,
) )
@@ -1338,7 +1364,7 @@ def code_deparse(
return None return None
# FIXME use a lookup table here. # FIXME use a lookup table here.
if compile_mode == "lambda": if is_lambda_mode(compile_mode):
expected_start = "lambda_start" expected_start = "lambda_start"
elif compile_mode == "eval": elif compile_mode == "eval":
expected_start = "expr_start" expected_start = "expr_start"
@@ -1351,6 +1377,7 @@ def code_deparse(
expected_start = None expected_start = None
else: else:
expected_start = None expected_start = None
if expected_start: if expected_start:
assert ( assert (
deparsed.ast == expected_start deparsed.ast == expected_start
@@ -1397,7 +1424,7 @@ def code_deparse(
deparsed.ast, deparsed.ast,
name=co.co_name, name=co.co_name,
customize=customize, customize=customize,
is_lambda=compile_mode == "lambda", is_lambda=is_lambda_mode(compile_mode),
debug_opts=debug_opts, debug_opts=debug_opts,
) )
@@ -1425,9 +1452,12 @@ def deparse_code2str(
compile_mode="exec", compile_mode="exec",
is_pypy=IS_PYPY, is_pypy=IS_PYPY,
walker=SourceWalker, walker=SourceWalker,
): start_offset: int = 0,
"""Return the deparsed text for a Python code object. `out` is where any intermediate stop_offset: int = -1,
output for assembly or tree output will be sent. ) -> str:
"""
Return the deparsed text for a Python code object. `out` is where
any intermediate output for assembly or tree output will be sent.
""" """
return code_deparse( return code_deparse(
code, code,

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2019-2023 by Rocky Bernstein # Copyright (c) 2019-2024 by Rocky Bernstein
# This program is free software: you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by
@@ -13,14 +13,15 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from uncompyle6.show import maybe_show_tree
from copy import copy from copy import copy
from spark_parser import GenericASTTraversal, GenericASTTraversalPruningException from spark_parser import GenericASTTraversal, GenericASTTraversalPruningException
from uncompyle6.semantics.helper import find_code_node
from uncompyle6.parsers.treenode import SyntaxTree from uncompyle6.parsers.treenode import SyntaxTree
from uncompyle6.scanners.tok import NoneToken, Token from uncompyle6.scanners.tok import NoneToken, Token
from uncompyle6.semantics.consts import RETURN_NONE, ASSIGN_DOC_STRING from uncompyle6.semantics.consts import ASSIGN_DOC_STRING, RETURN_NONE
from uncompyle6.semantics.helper import find_code_node
from uncompyle6.show import maybe_show_tree
def is_docstring(node, version, co_consts): def is_docstring(node, version, co_consts):
@@ -55,27 +56,34 @@ def is_docstring(node, version, co_consts):
return node == ASSIGN_DOC_STRING(co_consts[0], doc_load) return node == ASSIGN_DOC_STRING(co_consts[0], doc_load)
def is_not_docstring(call_stmt_node): def is_not_docstring(call_stmt_node) -> bool:
try: try:
return ( return (
call_stmt_node == "call_stmt" call_stmt_node == "call_stmt"
and call_stmt_node[0][0] == "LOAD_STR" and call_stmt_node[0][0] == "LOAD_STR"
and call_stmt_node[1] == "POP_TOP" and call_stmt_node[1] == "POP_TOP"
) )
except: except Exception:
return False return False
class TreeTransform(GenericASTTraversal, object): class TreeTransform(GenericASTTraversal, object):
def __init__(self, version, show_ast=None, is_pypy=False): def __init__(
self,
version: tuple,
is_pypy=False,
show_ast=None,
):
self.version = version self.version = version
self.showast = show_ast self.showast = show_ast
self.is_pypy = is_pypy self.is_pypy = is_pypy
return return
def maybe_show_tree(self, ast): def maybe_show_tree(self, tree):
if isinstance(self.showast, dict) and self.showast: if isinstance(self.showast, dict) and (
maybe_show_tree(self, ast) self.showast.get("before") or self.showast.get("after")
):
maybe_show_tree(self, tree)
def preorder(self, node=None): def preorder(self, node=None):
"""Walk the tree in roughly 'preorder' (a bit of a lie explained below). """Walk the tree in roughly 'preorder' (a bit of a lie explained below).
@@ -119,12 +127,10 @@ class TreeTransform(GenericASTTraversal, object):
mkfunc_pattr = node[-1].pattr mkfunc_pattr = node[-1].pattr
if isinstance(mkfunc_pattr, tuple): if isinstance(mkfunc_pattr, tuple):
assert isinstance(mkfunc_pattr, tuple)
assert len(mkfunc_pattr) == 4 and isinstance(mkfunc_pattr, int) assert len(mkfunc_pattr) == 4 and isinstance(mkfunc_pattr, int)
if ( if len(code.co_consts) > 0 and isinstance(code.co_consts[0], str):
len(code.co_consts) > 0
and isinstance(code.co_consts[0], str)
):
docstring_node = SyntaxTree( docstring_node = SyntaxTree(
"docstring", [Token("LOAD_STR", has_arg=True, pattr=code.co_consts[0])] "docstring", [Token("LOAD_STR", has_arg=True, pattr=code.co_consts[0])]
) )
@@ -136,7 +142,7 @@ class TreeTransform(GenericASTTraversal, object):
def n_ifstmt(self, node): def n_ifstmt(self, node):
"""Here we check if we can turn an `ifstmt` or 'iflaststmtl` into """Here we check if we can turn an `ifstmt` or 'iflaststmtl` into
some kind of `assert` statement""" some kind of `assert` statement"""
testexpr = node[0] testexpr = node[0]
@@ -148,7 +154,11 @@ class TreeTransform(GenericASTTraversal, object):
if ifstmts_jump == "_ifstmts_jumpl" and ifstmts_jump[0] == "_ifstmts_jump": if ifstmts_jump == "_ifstmts_jumpl" and ifstmts_jump[0] == "_ifstmts_jump":
ifstmts_jump = ifstmts_jump[0] ifstmts_jump = ifstmts_jump[0]
elif ifstmts_jump not in ("_ifstmts_jump", "_ifstmts_jumpl", "ifstmts_jumpl"): elif ifstmts_jump not in (
"_ifstmts_jump",
"_ifstmts_jumpl",
"ifstmts_jumpl",
):
return node return node
stmts = ifstmts_jump[0] stmts = ifstmts_jump[0]
else: else:
@@ -208,10 +218,11 @@ class TreeTransform(GenericASTTraversal, object):
kind = "assert2not" kind = "assert2not"
LOAD_ASSERT = call[0].first_child() LOAD_ASSERT = call[0].first_child()
if LOAD_ASSERT not in ( "LOAD_ASSERT", "LOAD_GLOBAL"): if LOAD_ASSERT not in ("LOAD_ASSERT", "LOAD_GLOBAL"):
return node return node
if isinstance(call[1], SyntaxTree): if isinstance(call[1], SyntaxTree):
expr = call[1][0] expr = call[1][0]
assert_expr.transformed_by = "n_ifstmt"
node = SyntaxTree( node = SyntaxTree(
kind, kind,
[ [
@@ -221,8 +232,8 @@ class TreeTransform(GenericASTTraversal, object):
expr, expr,
RAISE_VARARGS_1, RAISE_VARARGS_1,
], ],
transformed_by="n_ifstmt",
) )
node.transformed_by = "n_ifstmt"
pass pass
pass pass
else: else:
@@ -250,9 +261,10 @@ class TreeTransform(GenericASTTraversal, object):
LOAD_ASSERT = expr[0] LOAD_ASSERT = expr[0]
node = SyntaxTree( node = SyntaxTree(
kind, [assert_expr, jump_cond, LOAD_ASSERT, RAISE_VARARGS_1] kind,
[assert_expr, jump_cond, LOAD_ASSERT, RAISE_VARARGS_1],
transformed_by="n_ifstmt",
) )
node.transformed_by = ("n_ifstmt",)
pass pass
pass pass
return node return node
@@ -289,7 +301,12 @@ class TreeTransform(GenericASTTraversal, object):
len_n = len(n) len_n = len(n)
# Sometimes stmt is reduced away and n[0] can be a single reduction like continue -> CONTINUE. # Sometimes stmt is reduced away and n[0] can be a single reduction like continue -> CONTINUE.
if len_n == 1 and isinstance(n[0], SyntaxTree) and len(n[0]) == 1 and n[0] == "stmt": if (
len_n == 1
and isinstance(n[0], SyntaxTree)
and len(n[0]) == 1
and n[0] == "stmt"
):
n = n[0][0] n = n[0][0]
elif len_n == 0: elif len_n == 0:
return node return node
@@ -407,23 +424,27 @@ class TreeTransform(GenericASTTraversal, object):
list_for_node.transformed_by = ("n_list_for",) list_for_node.transformed_by = ("n_list_for",)
return list_for_node return list_for_node
def n_negated_testtrue(self, node):
assert node[0] == "testtrue"
test_node = node[0][0]
test_node.transformed_by = "n_negated_testtrue"
return test_node
def n_stmts(self, node): def n_stmts(self, node):
if node.first_child() == "SETUP_ANNOTATIONS": if node.first_child() == "SETUP_ANNOTATIONS":
prev = node[0][0] prev = node[0][0]
new_stmts = [node[0]] new_stmts = [node[0]]
for i, sstmt in enumerate(node[1:]): for i, sstmt in enumerate(node[1:]):
ann_assign = sstmt[0] ann_assign = sstmt[0]
if ( if ann_assign == "ann_assign" and prev == "assign":
ann_assign == "ann_assign"
and prev == "assign"
):
annotate_var = ann_assign[-2] annotate_var = ann_assign[-2]
if annotate_var.attr == prev[-1][0].attr: if annotate_var.attr == prev[-1][0].attr:
node[i].kind = "deleted " + node[i].kind node[i].kind = "deleted " + node[i].kind
del new_stmts[-1] del new_stmts[-1]
ann_assign_init = SyntaxTree( ann_assign_init = SyntaxTree(
"ann_assign_init", [ann_assign[0], copy(prev[0]), annotate_var] "ann_assign_init",
) [ann_assign[0], copy(prev[0]), annotate_var],
)
if sstmt[0] == "ann_assign": if sstmt[0] == "ann_assign":
sstmt[0] = ann_assign_init sstmt[0] = ann_assign_init
else: else:
@@ -441,26 +462,28 @@ class TreeTransform(GenericASTTraversal, object):
node = self.preorder(node) node = self.preorder(node)
return node return node
def transform(self, ast, code): def transform(self, parse_tree: GenericASTTraversal, code) -> GenericASTTraversal:
self.maybe_show_tree(ast) self.maybe_show_tree(parse_tree)
self.ast = copy(ast) self.ast = copy(parse_tree)
del parse_tree
self.ast = self.traverse(self.ast, is_lambda=False) self.ast = self.traverse(self.ast, is_lambda=False)
n = len(self.ast)
try: try:
# Disambiguate a string (expression) which appears as a "call_stmt" at # Disambiguate a string (expression) which appears as a "call_stmt" at
# the beginning of a function versus a docstring. Seems pretty academic, # the beginning of a function versus a docstring. Seems pretty academic,
# but this is Python. # but this is Python.
call_stmt = ast[0][0] call_stmt = self.ast[0][0]
if is_not_docstring(call_stmt): if is_not_docstring(call_stmt):
call_stmt.kind = "string_at_beginning" call_stmt.kind = "string_at_beginning"
call_stmt.transformed_by = "transform" call_stmt.transformed_by = "transform"
pass pass
except: except Exception:
pass pass
try: try:
for i in range(len(self.ast)): for i in range(n):
sstmt = ast[i] sstmt = self.ast[i]
if len(sstmt) == 1 and sstmt == "sstmt": if len(sstmt) == 1 and sstmt == "sstmt":
self.ast[i] = self.ast[i][0] self.ast[i] = self.ast[i][0]
@@ -486,7 +509,7 @@ class TreeTransform(GenericASTTraversal, object):
if self.ast[-1] == RETURN_NONE: if self.ast[-1] == RETURN_NONE:
self.ast.pop() # remove last node self.ast.pop() # remove last node
# todo: if empty, add 'pass' # todo: if empty, add 'pass'
except: except Exception:
pass pass
return self.ast return self.ast