| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682 |
- # Copyright (c) 2015-2021, 2024 by 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/>.
- """
- All the crazy things we have to do to handle Python functions in 3.0-3.5 or so.
- The saga of changes before and after is in other files.
- """
- from xdis import CO_GENERATOR, code_has_star_arg, code_has_star_star_arg, iscode
- from uncompyle6.parser import ParserError as ParserError2
- from uncompyle6.parsers.treenode import SyntaxTree
- from uncompyle6.scanner import Code
- from uncompyle6.semantics.helper import (
- find_all_globals,
- find_globals_and_nonlocals,
- find_none,
- print_docstring,
- )
- from uncompyle6.semantics.parser_error import ParserError
- from uncompyle6.show import maybe_show_tree_param_default
- # FIXME: DRY the below code...
- def make_function3_annotate(
- self, node, is_lambda, nested=1, code_node=None, annotate_last=-1
- ):
- """
- Dump function definition, doc string, and function
- body. This code is specialized for Python 3"""
- def build_param(ast, name, default):
- """build parameters:
- - handle defaults
- - handle format tuple parameters
- """
- if default:
- value = self.traverse(default, indent="")
- maybe_show_tree_param_default(self, name, value)
- result = "%s=%s" % (name, value)
- if result[-2:] == "= ": # default was 'LOAD_CONST None'
- result += "None"
- return result
- else:
- return name
- # MAKE_FUNCTION_... or MAKE_CLOSURE_...
- assert node[-1].kind.startswith("MAKE_")
- annotate_tuple = None
- for annotate_last in range(len(node) - 1, -1, -1):
- if node[annotate_last] == "annotate_tuple":
- annotate_tuple = node[annotate_last]
- break
- annotate_args = {}
- if (
- annotate_tuple == "annotate_tuple"
- and annotate_tuple[0] in ("LOAD_CONST", "LOAD_NAME")
- and isinstance(annotate_tuple[0].attr, tuple)
- ):
- annotate_tup = annotate_tuple[0].attr
- i = -1
- j = annotate_last - 1
- l = -len(node)
- while j >= l and node[j].kind in ("annotate_arg", "annotate_tuple"):
- annotate_args[annotate_tup[i]] = node[j][0]
- i -= 1
- j -= 1
- args_node = node[-1]
- if isinstance(args_node.attr, tuple):
- # positional args are before kwargs
- defparams = node[: args_node.attr[0]]
- pos_args, kw_args, annotate_argc = args_node.attr
- else:
- defparams = node[: args_node.attr]
- kw_args = 0
- pass
- annotate_dict = {}
- for name in annotate_args.keys():
- n = self.traverse(annotate_args[name], indent="")
- annotate_dict[name] = n
- if (3, 0) <= self.version < (3, 3):
- lambda_index = -2
- elif self.version < (3, 4):
- lambda_index = -3
- else:
- lambda_index = None
- if lambda_index and is_lambda and iscode(node[lambda_index].attr):
- assert node[lambda_index].kind == "LOAD_LAMBDA"
- code = node[lambda_index].attr
- else:
- code = code_node.attr
- assert iscode(code)
- code = Code(code, self.scanner, self.currentclass)
- # add defaults values to parameter names
- argc = code.co_argcount
- kwonlyargcount = code.co_kwonlyargcount
- paramnames = list(code.co_varnames[:argc])
- if kwonlyargcount > 0:
- kwargs = list(code.co_varnames[argc : argc + kwonlyargcount])
- try:
- ast = self.build_ast(
- code._tokens,
- code._customize,
- code,
- is_lambda=is_lambda,
- noneInNames=("None" in code.co_names),
- )
- except (ParserError, ParserError2) as p:
- self.write(str(p))
- if not self.tolerate_errors:
- self.ERROR = p
- return
- indent = self.indent
- if is_lambda:
- self.write("lambda ")
- else:
- self.write("(")
- last_line = self.f.getvalue().split("\n")[-1]
- l = len(last_line)
- indent = " " * l
- line_number = self.line_number
- i = len(paramnames) - len(defparams)
- suffix = ""
- for param in paramnames[:i]:
- self.write(suffix, param)
- suffix = ", "
- if param in annotate_dict:
- self.write(": %s" % annotate_dict[param])
- if line_number != self.line_number:
- suffix = ",\n" + indent
- line_number = self.line_number
- # value, string = annotate_args[param]
- # if string:
- # self.write(': "%s"' % value)
- # else:
- # self.write(': %s' % value)
- suffix = ", " if i > 0 else ""
- for n in node:
- if n == "pos_arg":
- self.write(suffix)
- param = paramnames[i]
- self.write(param)
- if param in annotate_args:
- aa = annotate_args[param]
- if isinstance(aa, tuple):
- aa = aa[0]
- self.write(': "%s"' % aa)
- elif isinstance(aa, SyntaxTree):
- self.write(": ")
- self.preorder(aa)
- self.write("=")
- i += 1
- self.preorder(n)
- if line_number != self.line_number:
- suffix = ",\n" + indent
- line_number = self.line_number
- else:
- suffix = ", "
- if code_has_star_arg(code):
- star_arg = code.co_varnames[argc + kwonlyargcount]
- if annotate_dict and star_arg in annotate_dict:
- self.write(suffix, "*%s: %s" % (star_arg, annotate_dict[star_arg]))
- else:
- self.write(suffix, "*%s" % star_arg)
- argc += 1
- # self.println(indent, '#flags:\t', int(code.co_flags))
- ends_in_comma = False
- if kwonlyargcount > 0:
- if not code_has_star_arg(code):
- if argc > 0:
- self.write(", *, ")
- else:
- self.write("*, ")
- pass
- ends_in_comma = True
- else:
- if argc > 0:
- self.write(", ")
- ends_in_comma = True
- kw_args = [None] * kwonlyargcount
- for n in node:
- if n == "kwargs":
- n = n[0]
- if n == "kwarg":
- name = eval(n[0].pattr)
- idx = kwargs.index(name)
- default = self.traverse(n[1], indent="")
- if annotate_dict and name in annotate_dict:
- kw_args[idx] = "%s: %s=%s" % (name, annotate_dict[name], default)
- else:
- kw_args[idx] = "%s=%s" % (name, default)
- pass
- pass
- # handling other args
- other_kw = [c == None for c in kw_args]
- for i, flag in enumerate(other_kw):
- if flag:
- n = kwargs[i]
- if n in annotate_dict:
- kw_args[i] = "%s: %s" % (n, annotate_dict[n])
- else:
- kw_args[i] = "%s" % n
- self.write(", ".join(kw_args))
- ends_in_comma = False
- else:
- if argc == 0:
- ends_in_comma = True
- if code_has_star_star_arg(code):
- if not ends_in_comma:
- self.write(", ")
- star_star_arg = code.co_varnames[argc + kwonlyargcount]
- if annotate_dict and star_star_arg in annotate_dict:
- self.write("**%s: %s" % (star_star_arg, annotate_dict[star_star_arg]))
- else:
- self.write("**%s" % star_star_arg)
- if is_lambda:
- self.write(": ")
- else:
- self.write(")")
- if "return" in annotate_tuple[0].attr:
- if (line_number != self.line_number) and not no_paramnames:
- self.write("\n" + indent)
- line_number = self.line_number
- self.write(" -> ")
- if "return" in annotate_dict:
- self.write(annotate_dict["return"])
- else:
- # value, string = annotate_args['return']
- # if string:
- # self.write(' -> "%s"' % value)
- # else:
- # self.write(' -> %s' % value)
- self.preorder(node[annotate_last - 1])
- self.println(":")
- if (
- len(code.co_consts) > 0 and code.co_consts[0] is not None and not is_lambda
- ): # ugly
- # docstring exists, dump it
- print_docstring(self, self.indent, code.co_consts[0])
- code._tokens = None # save memory
- assert ast == "stmts"
- all_globals = find_all_globals(ast, set())
- globals, nonlocals = find_globals_and_nonlocals(
- ast, set(), set(), code, self.version
- )
- for g in sorted((all_globals & self.mod_globs) | globals):
- self.println(self.indent, "global ", g)
- for nl in sorted(nonlocals):
- self.println(self.indent, "nonlocal ", nl)
- self.mod_globs -= all_globals
- has_none = "None" in code.co_names
- rn = has_none and not find_none(ast)
- self.gen_source(
- ast, code.co_name, code._customize, is_lambda=is_lambda, returnNone=rn
- )
- code._tokens = code._customize = None # save memory
- def make_function3(self, node, is_lambda, nested=1, code_node=None):
- """Dump function definition, doc string, and function body in
- Python version 3.0 and above
- """
- # For Python 3.3, the evaluation stack in MAKE_FUNCTION is:
- # * default argument objects in positional order
- # * pairs of name and default argument, with the name just below
- # the object on the stack, for keyword-only parameters
- # * parameter annotation objects
- # * a tuple listing the parameter names for the annotations
- # (only if there are only annotation objects)
- # * the code associated with the function (at TOS1)
- # * the qualified name of the function (at TOS)
- # For Python 3.0 .. 3.2 the evaluation stack is:
- # The function object is defined to have argc default parameters,
- # which are found below TOS.
- # * first come positional args in the order they are given in the source,
- # * next come the keyword args in the order they given in the source,
- # * finally is the code associated with the function (at TOS)
- #
- # Note: There is no qualified name at TOS
- # MAKE_CLOSURE adds an additional closure slot
- # In Python 3.6 stack entries change again. I understand
- # 3.7 changes some of those changes. Yes, it is hard to follow
- # and I am sure I haven't been able to keep up.
- # Thank you, Python.
- def build_param(ast, name, default, annotation=None):
- """build parameters:
- - handle defaults
- - handle format tuple parameters
- """
- value = self.traverse(default, indent="")
- if annotation:
- result = "%s: %s=%s" % (name, annotation, value)
- else:
- result = "%s=%s" % (name, value)
- # The below can probably be removed. This is probably
- # a holdover from days when LOAD_CONST erroneously
- # didn't handle LOAD_CONST None properly
- if result[-2:] == "= ": # default was 'LOAD_CONST None'
- result += "None"
- return result
- # MAKE_FUNCTION_... or MAKE_CLOSURE_...
- assert node[-1].kind.startswith("MAKE_")
- # Python 3.3+ adds a qualified name at TOS (-1)
- # moving down the LOAD_LAMBDA instruction
- if (3, 0) <= self.version <= (3, 2):
- lambda_index = -2
- elif (3, 3) <= self.version:
- lambda_index = -3
- else:
- lambda_index = None
- args_node = node[-1]
- annotate_dict = {}
- # Get a list of tree nodes that constitute the values for the "default
- # parameters"; these are default values that appear before any *, and are
- # not to be confused with keyword parameters which may appear after *.
- args_attr = args_node.attr
- if isinstance(args_attr, tuple):
- if len(args_attr) == 3:
- pos_args, kw_args, annotate_argc = args_attr
- else:
- pos_args, kw_args, annotate_argc, closure = args_attr
- i = -4
- kw_pairs = 0
- if closure:
- # FIXME: fill in
- i -= 1
- if annotate_argc:
- # Turn into subroutine and DRY with other use
- annotate_node = node[i]
- if annotate_node == "expr":
- annotate_node = annotate_node[0]
- annotate_name_node = annotate_node[-1]
- if annotate_node == "dict" and annotate_name_node.kind.startswith(
- "BUILD_CONST_KEY_MAP"
- ):
- types = [
- self.traverse(n, indent="") for n in annotate_node[:-2]
- ]
- names = annotate_node[-2].attr
- l = len(types)
- assert l == len(names)
- for i in range(l):
- annotate_dict[names[i]] = types[i]
- pass
- pass
- i -= 1
- if kw_args:
- kw_node = node[i]
- if kw_node == "expr":
- kw_node = kw_node[0]
- if kw_node == "dict":
- kw_pairs = kw_node[-1].attr
- # FIXME: there is probably a better way to classify this.
- have_kwargs = node[0].kind.startswith("kwarg") or node[0] == "no_kwargs"
- if len(node) >= 4:
- lc_index = -4
- else:
- lc_index = -3
- pass
- if len(node) > 2 and (have_kwargs or node[lc_index].kind != "load_closure"):
- # Find the index in "node" where the first default
- # parameter value is located. Note this is in contrast to
- # key-word arguments, pairs of (name, value), which appear after "*".
- # "default_values_start" is this location.
- default_values_start = 0
- if node[0] == "no_kwargs":
- default_values_start += 1
- # If in a lambda named args are a sequence of kwarg, not bundled.
- # If not in a lambda, named args are after kwargs; kwargs are bundled as one node.
- if node[default_values_start] == "kwarg":
- assert node[lambda_index] == "LOAD_LAMBDA"
- i = default_values_start
- defparams = []
- while node[i] == "kwarg":
- defparams.append(node[i][1])
- i += 1
- else:
- if node[default_values_start] == "kwargs":
- default_values_start += 1
- defparams = node[
- default_values_start : default_values_start + args_node.attr[0]
- ]
- else:
- defparams = node[: args_node.attr[0]]
- kw_args = 0
- else:
- defparams = node[: args_node.attr]
- kw_args = 0
- pass
- if lambda_index and is_lambda and iscode(node[lambda_index].attr):
- assert node[lambda_index].kind == "LOAD_LAMBDA"
- code = node[lambda_index].attr
- else:
- code = code_node.attr
- assert iscode(code)
- scanner_code = Code(code, self.scanner, self.currentclass)
- # add defaults values to parameter names
- argc = code.co_argcount
- kwonlyargcount = code.co_kwonlyargcount
- paramnames = list(scanner_code.co_varnames[:argc])
- if kwonlyargcount > 0:
- if is_lambda:
- kwargs = []
- for i in range(kwonlyargcount):
- paramnames.append(scanner_code.co_varnames[argc + i])
- pass
- else:
- kwargs = list(scanner_code.co_varnames[argc : argc + kwonlyargcount])
- # defaults are for last n parameters when not in a lambda, thus reverse
- paramnames.reverse()
- defparams.reverse()
- try:
- ast = self.build_ast(
- scanner_code._tokens,
- scanner_code._customize,
- scanner_code,
- is_lambda=is_lambda,
- noneInNames=("None" in code.co_names),
- )
- except (ParserError, ParserError2) as p:
- self.write(str(p))
- if not self.tolerate_errors:
- self.ERROR = p
- return
- i = len(paramnames) - len(defparams)
- # build parameters
- params = []
- if defparams:
- for i, defparam in enumerate(defparams):
- params.append(
- build_param(
- ast, paramnames[i], defparam, annotate_dict.get(paramnames[i])
- )
- )
- for param in paramnames[i + 1 :]:
- if param in annotate_dict:
- params.append("%s: %s" % (param, annotate_dict[param]))
- else:
- params.append(param)
- else:
- for param in paramnames:
- if param in annotate_dict:
- params.append("%s: %s" % (param, annotate_dict[param]))
- else:
- params.append(param)
- params.reverse() # back to correct order
- if code_has_star_arg(code):
- star_arg = code.co_varnames[argc + kwonlyargcount]
- if annotate_dict and star_arg in annotate_dict:
- params.append("*%s: %s" % (star_arg, annotate_dict[star_arg]))
- else:
- params.append("*%s" % star_arg)
- pass
- if is_lambda:
- params.reverse()
- if not is_lambda:
- argc += 1
- pass
- elif is_lambda and kwonlyargcount > 0:
- params.insert(0, "*")
- kwonlyargcount = 0
- # dump parameter list (with default values)
- if is_lambda:
- self.write("lambda ", ", ".join(params))
- # If the last statement is None (which is the
- # same thing as "return None" in a lambda) and the
- # next to last statement is a "yield". Then we want to
- # drop the (return) None since that was just put there
- # to have something to after the yield finishes.
- # FIXME: this is a bit hoaky and not general
- if (
- len(ast) > 1
- and self.traverse(ast[-1]) == "None"
- and self.traverse(ast[-2]).strip().startswith("yield")
- ):
- del ast[-1]
- # Now pick out the expr part of the last statement
- ast_expr = ast[-1]
- while ast_expr.kind != "expr":
- ast_expr = ast_expr[0]
- ast[-1] = ast_expr
- pass
- else:
- # FIXME: add annotations here
- self.write("(", ", ".join(params))
- # self.println(indent, '#flags:\t', int(code.co_flags))
- # FIXME: Could we remove ends_in_comma and its tests if we just
- # created a parameter list and at the very end did a join on that?
- # Unless careful, We might lose line breaks though.
- ends_in_comma = False
- if kwonlyargcount > 0:
- if not (4 & code.co_flags):
- if argc > 0:
- self.write(", *, ")
- else:
- self.write("*, ")
- pass
- ends_in_comma = True
- else:
- if argc > 0 and node[0] != "kwarg":
- self.write(", ")
- ends_in_comma = True
- kw_args = [None] * kwonlyargcount
- if self.version <= (3, 3):
- kw_nodes = node[0]
- else:
- kw_nodes = node[args_node.attr[0]]
- if kw_nodes == "kwargs":
- for n in kw_nodes:
- name = eval(n[0].pattr)
- default = self.traverse(n[1], indent="")
- idx = kwargs.index(name)
- kw_args[idx] = "%s=%s" % (name, default)
- pass
- pass
- # FIXME: something weird is going on and the below
- # might not be right. On 3.4 kw_nodes != "kwarg"
- # because of some sort of type mismatch. I think
- # the test is for versions earlier than 3.3
- # on 3.5 if we have "kwarg" we still want to do this.
- # Perhaps we should be testing that kw_nodes is iterable?
- if kw_nodes != "kwarg" or self.version == 3.5:
- other_kw = [c == None for c in kw_args]
- for i, flag in enumerate(other_kw):
- if flag:
- if i < len(kwargs):
- kw_args[i] = "%s" % kwargs[i]
- else:
- del kw_args[i]
- pass
- self.write(", ".join(kw_args))
- ends_in_comma = False
- pass
- pass
- else:
- if argc == 0:
- ends_in_comma = True
- if code_has_star_star_arg(code):
- if not ends_in_comma:
- self.write(", ")
- star_star_arg = code.co_varnames[argc + kwonlyargcount]
- if annotate_dict and star_star_arg in annotate_dict:
- self.write("**%s: %s" % (star_star_arg, annotate_dict[star_star_arg]))
- else:
- self.write("**%s" % star_star_arg)
- if is_lambda:
- self.write(": ")
- else:
- self.write(")")
- if annotate_dict and "return" in annotate_dict:
- self.write(" -> %s" % annotate_dict["return"])
- self.println(":")
- if (
- len(code.co_consts) > 0 and code.co_consts[0] is not None and not is_lambda
- ): # ugly
- # docstring exists, dump it
- print_docstring(self, self.indent, code.co_consts[0])
- assert ast == "stmts"
- all_globals = find_all_globals(ast, set())
- globals, nonlocals = find_globals_and_nonlocals(
- ast, set(), set(), code, self.version
- )
- for g in sorted((all_globals & self.mod_globs) | globals):
- self.println(self.indent, "global ", g)
- for nl in sorted(nonlocals):
- self.println(self.indent, "nonlocal ", nl)
- self.mod_globs -= all_globals
- has_none = "None" in code.co_names
- rn = has_none and not find_none(ast)
- self.gen_source(
- ast,
- code.co_name,
- scanner_code._customize,
- is_lambda=is_lambda,
- returnNone=rn,
- debug_opts=self.debug_opts,
- )
- # In obscure cases, a function may be a generator but the "yield"
- # was optimized away. Here, we need to put in unreachable code to
- # add in "yield" just so that the compiler will mark
- # the GENERATOR bit of the function. See for example
- # Python 3.x's test_generator.py test program.
- if not is_lambda and code.co_flags & CO_GENERATOR:
- need_bogus_yield = True
- for token in scanner_code._tokens:
- if token in ("YIELD_VALUE", "YIELD_FROM"):
- need_bogus_yield = False
- break
- pass
- if need_bogus_yield:
- self.template_engine(("%|if False:\n%+%|yield None%-",), node)
- scanner_code._tokens = None # save memory
- scanner_code._customize = None # save memory
|