def make_function3_annotate(self, node, is_lambda, nested=1, code_node=None, annotate_last=-1): """ Dump function defintion, 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 if 'return' in annotate_args.keys(): annotate_argc = len(annotate_args) - 1 else: defparams = node[:args_node.attr] kw_args = 0 annotate_argc = 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.2: lambda_index = -2 elif 3.03 <= self.version: 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, 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 kw_pairs = args_node.attr[1] 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(' -> ') # 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 ony 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 """ if self.version >= 3.6: value = default else: value = self.traverse(default, indent='') maybe_show_tree_param_default(self.showast, name, value) 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.03 <= 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) or (self.version >= 3.6 and isinstance(args_attr, list)): 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 (3.0 <= self.version <= 3.3 and len(node) > 2 and node[lambda_index] != 'LOAD_LAMBDA' 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 # args are after kwargs; kwargs are bundled as one node if node[default_values_start] == 'kwargs': default_values_start += 1 defparams = node[default_values_start:default_values_start + args_node.attr[0]] else: if self.version < 3.6: defparams = node[:args_node.attr[0]] kw_args = 0 else: defparams = [] # FIXME: DRY with code below default, kw_args, annotate_argc = args_node.attr[0:3] if default: expr_node = node[0] if node[0] == 'pos_arg': expr_node = expr_node[0] assert expr_node == 'expr', "expecting mkfunc default node to be an expr" if (expr_node[0] == 'LOAD_CONST' and isinstance(expr_node[0].attr, tuple)): defparams = [repr(a) for a in expr_node[0].attr] elif expr_node[0] in frozenset( ('list', 'tuple', 'dict', 'set')): defparams = [ self.traverse(n, indent='') for n in expr_node[0][:-1] ] else: defparams = [] pass else: if self.version < 3.6: defparams = node[:args_node.attr] kw_args = 0 else: default, kw_args, annotate, closure = args_node.attr if default: expr_node = node[0] if node[0] == 'pos_arg': expr_node = expr_node[0] assert expr_node == 'expr', "expecting mkfunc default node to be an expr" if (expr_node[0] == 'LOAD_CONST' and isinstance(expr_node[0].attr, tuple)): defparams = [repr(a) for a in expr_node[0].attr] elif expr_node[0] in frozenset( ('list', 'tuple', 'dict', 'set')): defparams = [ self.traverse(n, indent='') for n in expr_node[0][:-1] ] else: defparams = [] i = -4 kw_pairs = 0 if closure: # FIXME: fill in annotate = node[i] 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 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: kwargs = list(scanner_code.co_varnames[argc:argc + kwonlyargcount]) # defaults are for last n parameters, thus reverse paramnames.reverse() defparams.reverse() try: ast = self.build_ast(scanner_code._tokens, scanner_code._customize, 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 if self.version >= 3.0: if self.version < 3.6: kw_pairs = args_node.attr[1] else: kw_pairs = 0 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): if self.version > 3.0: 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) else: params.append('*%s' % code.co_varnames[argc]) argc += 1 # 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: self.write(", ") ends_in_comma = True if 3.0 <= self.version <= 3.5: kw_args = [None] * kwonlyargcount kw_nodes = node[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) other_kw = [c == None for c in kw_args] for i, flag in enumerate(other_kw): if flag: kw_args[i] = "%s" % kwargs[i] self.write(', '.join(kw_args)) ends_in_comma = False elif self.version >= 3.6: # argc = node[-1].attr # co = node[-3].attr # argcount = co.co_argcount # kwonlyargcount = co.co_kwonlyargcount free_tup = ann_dict = kw_dict = default_tup = None fn_bits = node[-1].attr index = -4 # Skip over: # MAKE_FUNCTION, # LOAD_CONST qualified name, # LOAD_CONST code object if fn_bits[-1]: free_tup = node[index] index -= 1 if fn_bits[-2]: ann_dict = node[index] index -= 1 if fn_bits[-3]: kw_dict = node[index] index -= 1 if fn_bits[-4]: default_tup = node[index] if kw_dict == 'expr': kw_dict = kw_dict[0] # FIXME: handle free_tup, annotate_dict, and default_tup kw_args = [None] * kwonlyargcount if kw_dict: assert kw_dict == 'dict' defaults = [self.traverse(n, indent='') for n in kw_dict[:-2]] names = eval(self.traverse(kw_dict[-2])) assert len(defaults) == len(names) sep = '' # FIXME: possibly handle line breaks for i, n in enumerate(names): idx = kwargs.index(n) if annotate_dict and n in annotate_dict: t = "%s: %s=%s" % (n, annotate_dict[n], defaults[i]) else: t = "%s=%s" % (n, defaults[i]) kw_args[idx] = t pass pass # handle others other_kw = [c == None for c in kw_args] for i, flag in enumerate(other_kw): if flag: n = kwargs[i] if ann_dict and 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 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]) scanner_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, scanner_code._customize, is_lambda=is_lambda, returnNone=rn) scanner_code._tokens = None scanner_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 ony 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 = default maybe_show_tree_param_default(self.showast, name, value) 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 lambda_index = -3 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) or isinstance(args_attr, list): 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 defparams = [] # FIXME: DRY with code below default, kw_args, annotate_argc = args_node.attr[0:3] if default: expr_node = node[0] if node[0] == "pos_arg": expr_node = expr_node[0] assert expr_node == "expr", "expecting mkfunc default node to be an expr" if expr_node[0] == "LOAD_CONST" and isinstance(expr_node[0].attr, tuple): defparams = [repr(a) for a in expr_node[0].attr] elif expr_node[0] in frozenset(("list", "tuple", "dict", "set")): defparams = [self.traverse(n, indent="") for n in expr_node[0][:-1]] else: defparams = [] pass else: default, kw_args, annotate, closure = args_node.attr if default: expr_node = node[0] if node[0] == "pos_arg": expr_node = expr_node[0] assert expr_node == "expr", "expecting mkfunc default node to be an expr" if expr_node[0] == "LOAD_CONST" and isinstance(expr_node[0].attr, tuple): defparams = [repr(a) for a in expr_node[0].attr] elif expr_node[0] in frozenset(("list", "tuple", "dict", "set")): defparams = [self.traverse(n, indent="") for n in expr_node[0][:-1]] else: defparams = [] i = -4 kw_pairs = 0 if closure: # FIXME: fill in # annotate = node[i] 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 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]) kwargs = list(scanner_code.co_varnames[argc : argc + kwonlyargcount]) # defaults are for last n parameters, thus reverse paramnames.reverse() defparams.reverse() try: ast = self.build_ast( scanner_code._tokens, scanner_code._customize, 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 star_arg in annotate_dict: params.append("*%s: %s" % (star_arg, annotate_dict[star_arg])) else: params.append("*%s" % star_arg) argc += 1 # 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: self.write("(", ", ".join(params)) # self.println(indent, '#flags:\t', int(code.co_flags)) 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: self.write(", ") ends_in_comma = True ann_dict = kw_dict = default_tup = None fn_bits = node[-1].attr index = -4 # Skip over: # MAKE_FUNCTION, # LOAD_CONST qualified name, # LOAD_CONST code object if fn_bits[-1]: index -= 1 if fn_bits[-2]: ann_dict = node[index] index -= 1 if fn_bits[-3]: kw_dict = node[index] index -= 1 if fn_bits[-4]: default_tup = node[index] if kw_dict == "expr": kw_dict = kw_dict[0] kw_args = [None] * kwonlyargcount # FIXME: handle free_tup, ann_dict, and default_tup if kw_dict: assert kw_dict == "dict" defaults = [self.traverse(n, indent="") for n in kw_dict[:-2]] names = eval(self.traverse(kw_dict[-2])) assert len(defaults) == len(names) sep = "" # FIXME: possibly handle line breaks for i, n in enumerate(names): idx = kwargs.index(n) if annotate_dict and n in annotate_dict: t = "%s: %s=%s" % (n, annotate_dict[n], defaults[i]) else: t = "%s=%s" % (n, defaults[i]) kw_args[idx] = t pass pass # handle others 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 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 ( node[-2] == "docstring" and not is_lambda ): # docstring exists, dump it self.println(self.traverse(node[-2])) scanner_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, scanner_code._customize, is_lambda=is_lambda, returnNone=rn ) scanner_code._tokens = None scanner_code._customize = None # save memory
def make_function2(self, node, is_lambda, nested=1, code_node=None): """ Dump function defintion, doc string, and function body. This code is specialied for Python 2. """ # FIXME: call make_function3 if we are self.version >= 3.0 # and then simplify the below. def build_param(ast, name, default): """build parameters: - handle defaults - handle format tuple parameters """ # if formal parameter is a tuple, the paramater name # starts with a dot (eg. '.1', '.2') if name.startswith('.'): # replace the name with the tuple-string name = self.get_tuple_parameter(ast, name) pass if default: value = self.traverse(default, indent='') maybe_show_tree_param_default(self.showast, 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_') args_node = node[-1] if isinstance(args_node.attr, tuple): # positional args are after kwargs defparams = node[1:args_node.attr[0] + 1] pos_args, kw_args, annotate_argc = args_node.attr else: defparams = node[:args_node.attr] kw_args = 0 pass 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 paramnames = list(code.co_varnames[:argc]) # defaults are for last n parameters, thus reverse paramnames.reverse() defparams.reverse() try: ast = self.build_ast(code._tokens, code._customize, 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 kw_pairs = 0 indent = self.indent # build parameters params = [ build_param(ast, name, default) for name, default in zip_longest(paramnames, defparams, fillvalue=None) ] params.reverse() # back to correct order if code_has_star_arg(code): params.append('*%s' % code.co_varnames[argc]) argc += 1 # 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: self.write("(", ", ".join(params)) if kw_args > 0: if not (4 & code.co_flags): if argc > 0: self.write(", *, ") else: self.write("*, ") pass else: self.write(", ") for n in node: if n == 'pos_arg': continue else: self.preorder(n) break pass if code_has_star_star_arg(code): if argc > 0: self.write(', ') self.write('**%s' % code.co_varnames[argc + kw_pairs]) if is_lambda: self.write(": ") else: 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, indent, code.co_consts[0]) code._tokens = None # save memory if not is_lambda: assert ast == 'stmts' all_globals = find_all_globals(ast, set()) globals, nonlocals = find_globals_and_nonlocals(ast, set(), set(), code, self.version) # Python 2 doesn't support the "nonlocal" statement assert self.version >= 3.0 or not nonlocals for g in sorted((all_globals & self.mod_globs) | globals): self.println(self.indent, 'global ', g) 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 = None code._customize = None # save memory
def make_function3_annotate(self, node, isLambda, nested=1, codeNode=None, annotate_last=-1): """ Dump function defintion, 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_ast_param_default(self.showast, 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 if 'return' in annotate_args.keys(): annotate_argc = len(annotate_args) - 1 else: defparams = node[:args_node.attr] kw_args = 0 annotate_argc = 0 pass if 3.0 <= self.version <= 3.2: lambda_index = -2 elif 3.03 <= self.version: lambda_index = -3 else: lambda_index = None if lambda_index and isLambda and iscode(node[lambda_index].attr): assert node[lambda_index].kind == 'LOAD_LAMBDA' code = node[lambda_index].attr else: code = codeNode.attr assert iscode(code) code = Code(code, self.scanner, self.currentclass) # add defaults values to parameter names argc = code.co_argcount paramnames = list(code.co_varnames[:argc]) try: ast = self.build_ast(code._tokens, code._customize, isLambda=isLambda, noneInNames=('None' in code.co_names)) except ParserError as p: self.write(str(p)) self.ERROR = p return kw_pairs = args_node.attr[1] indent = self.indent if isLambda: 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 if code_has_star_arg(code): self.write('*%s' % code.co_varnames[argc + kw_pairs]) argc += 1 i = len(paramnames) - len(defparams) suffix = '' no_paramnames = len(paramnames[:i]) == 0 for param in paramnames[:i]: self.write(suffix, param) suffix = ', ' if param in annotate_tuple[0].attr: p = annotate_tuple[0].attr.index(param) self.write(': ') self.preorder(node[p]) 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': no_paramnames = False 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, AST): 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 = ', ' # self.println(indent, '#flags:\t', int(code.co_flags)) if kw_args + annotate_argc > 0: if no_paramnames: if not code_has_star_arg(code): if argc > 0: self.write(", *, ") else: self.write("*, ") pass else: self.write(", ") kwargs = node[0] last = len(kwargs) - 1 i = 0 for n in node[0]: if n == 'kwarg': if (line_number != self.line_number): self.write("\n" + indent) line_number = self.line_number self.write('%s=' % n[0].pattr) self.preorder(n[1]) if i < last: self.write(', ') i += 1 pass pass annotate_args = [] for n in node: if n == 'annotate_arg': annotate_args.append(n[0]) elif n == 'annotate_tuple': t = n[0].attr if t[-1] == 'return': t = t[0:-1] annotate_args = annotate_args[:-1] pass last = len(annotate_args) - 1 for i in range(len(annotate_args)): self.write("%s: " % (t[i])) self.preorder(annotate_args[i]) if i < last: self.write(', ') pass pass break pass pass if code_has_star_star_arg(code): if argc > 0: self.write(', ') self.write('**%s' % code.co_varnames[argc + kw_pairs]) if isLambda: 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(' -> ') # 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 isLambda): # 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()) for g in ((all_globals & self.mod_globs) | find_globals(ast, set())): self.println(self.indent, 'global ', g) 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, isLambda=isLambda, returnNone=rn) code._tokens = code._customize = None # save memory
def make_function3(self, node, isLambda, nested=1, codeNode=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 ony 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 # Thank you, Python, for a such a well-thought out system that has # changed 4 or so times. def build_param(ast, name, default): """build parameters: - handle defaults - handle format tuple parameters """ if default: value = self.traverse(default, indent='') maybe_show_ast_param_default(self.showast, 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_') # 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.03 <= self.version: lambda_index = -3 else: lambda_index = None args_node = node[-1] if isinstance(args_node.attr, tuple): pos_args, kw_args, annotate_argc = args_node.attr if self.version <= 3.3 and len( node) > 2 and node[lambda_index] != 'LOAD_LAMBDA': # args are after kwargs; kwargs are bundled as one node defparams = node[1:args_node.attr[0] + 1] else: # args are before kwargs; kwags as bundled as one node defparams = node[:args_node.attr[0]] else: if self.version < 3.6: defparams = node[:args_node.attr] else: default, kw, annotate, closure = args_node.attr # FIXME: start here for Python 3.6 and above: defparams = [] # if default: # defparams = node[-(2 + kw + annotate + closure)] # else: # defparams = [] kw_args = 0 pass if lambda_index and isLambda and iscode(node[lambda_index].attr): assert node[lambda_index].kind == 'LOAD_LAMBDA' code = node[lambda_index].attr else: code = codeNode.attr assert iscode(code) code = Code(code, self.scanner, self.currentclass) # add defaults values to parameter names argc = code.co_argcount paramnames = list(code.co_varnames[:argc]) # defaults are for last n parameters, thus reverse if not 3.0 <= self.version <= 3.1: paramnames.reverse() defparams.reverse() try: ast = self.build_ast(code._tokens, code._customize, isLambda=isLambda, noneInNames=('None' in code.co_names)) except ParserError as p: self.write(str(p)) self.ERROR = p return kw_pairs = args_node.attr[1] if self.version >= 3.0 else 0 # build parameters params = [ build_param(ast, name, d) for name, d in zip_longest(paramnames, defparams, fillvalue=None) ] if not 3.0 <= self.version <= 3.1: params.reverse() # back to correct order if code_has_star_arg(code): if self.version > 3.0: params.append('*%s' % code.co_varnames[argc + kw_pairs]) else: params.append('*%s' % code.co_varnames[argc]) argc += 1 # dump parameter list (with default values) if isLambda: self.write("lambda ", ", ".join(params)) else: self.write("(", ", ".join(params)) # self.println(indent, '#flags:\t', int(code.co_flags)) if kw_args > 0: if not (4 & code.co_flags): if argc > 0: self.write(", *, ") else: self.write("*, ") pass else: self.write(", ") if not 3.0 <= self.version <= 3.2: for n in node: if n == 'pos_arg': continue elif self.version >= 3.4 and not (n.kind in ('kwargs', 'kwarg')): continue else: self.preorder(n) break else: kwargs = node[0] last = len(kwargs) - 1 i = 0 for n in node[0]: if n == 'kwarg': self.write('%s=' % n[0].pattr) self.preorder(n[1]) if i < last: self.write(', ') i += 1 pass pass pass pass if code_has_star_star_arg(code): if argc > 0: self.write(', ') self.write('**%s' % code.co_varnames[argc + kw_pairs]) if isLambda: self.write(": ") else: self.println("):") if len(code.co_consts ) > 0 and code.co_consts[0] is not None and not isLambda: # 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()) for g in ((all_globals & self.mod_globs) | find_globals(ast, set())): self.println(self.indent, 'global ', g) 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, isLambda=isLambda, returnNone=rn) code._tokens = None code._customize = None # save memory
def make_function2(self, node, isLambda, nested=1, codeNode=None): """ Dump function defintion, doc string, and function body. This code is specialied for Python 2. """ # FIXME: call make_function3 if we are self.version >= 3.0 # and then simplify the below. def build_param(ast, name, default): """build parameters: - handle defaults - handle format tuple parameters """ # if formal parameter is a tuple, the paramater name # starts with a dot (eg. '.1', '.2') if name.startswith('.'): # replace the name with the tuple-string name = self.get_tuple_parameter(ast, name) pass if default: value = self.traverse(default, indent='') maybe_show_ast_param_default(self.showast, 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_') args_node = node[-1] if isinstance(args_node.attr, tuple): # positional args are after kwargs defparams = node[1:args_node.attr[0] + 1] pos_args, kw_args, annotate_argc = args_node.attr else: defparams = node[:args_node.attr] kw_args = 0 annotate_argc = 0 pass lambda_index = None if lambda_index and isLambda and iscode(node[lambda_index].attr): assert node[lambda_index].kind == 'LOAD_LAMBDA' code = node[lambda_index].attr else: code = codeNode.attr assert iscode(code) code = Code(code, self.scanner, self.currentclass) # add defaults values to parameter names argc = code.co_argcount paramnames = list(code.co_varnames[:argc]) # defaults are for last n parameters, thus reverse paramnames.reverse() defparams.reverse() try: ast = self.build_ast(code._tokens, code._customize, isLambda=isLambda, noneInNames=('None' in code.co_names)) except ParserError as p: self.write(str(p)) self.ERROR = p return kw_pairs = args_node.attr[1] if self.version >= 3.0 else 0 indent = self.indent # build parameters params = [ build_param(ast, name, default) for name, default in zip_longest(paramnames, defparams, fillvalue=None) ] params.reverse() # back to correct order if code_has_star_arg(code): params.append('*%s' % code.co_varnames[argc]) argc += 1 # dump parameter list (with default values) if isLambda: self.write("lambda ", ", ".join(params)) else: self.write("(", ", ".join(params)) if kw_args > 0: if not (4 & code.co_flags): if argc > 0: self.write(", *, ") else: self.write("*, ") pass else: self.write(", ") for n in node: if n == 'pos_arg': continue else: self.preorder(n) break pass if code_has_star_star_arg(code): if argc > 0: self.write(', ') if argc + kw_pairs > 0: self.write('**%s' % code.co_varnames[argc + kw_pairs]) if isLambda: self.write(": ") else: self.println("):") if len(code.co_consts ) > 0 and code.co_consts[0] is not None and not isLambda: # ugly # docstring exists, dump it print_docstring(self, indent, code.co_consts[0]) code._tokens = None # save memory assert ast == 'stmts' all_globals = find_all_globals(ast, set()) for g in ((all_globals & self.mod_globs) | find_globals(ast, set())): self.println(self.indent, 'global ', g) 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, isLambda=isLambda, returnNone=rn) code._tokens = None 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 ony 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="") maybe_show_tree_param_default(self.showast, name, value) 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.03 <= 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, 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 kw_pairs = 0 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) # 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
def make_function3(self, node, is_lambda, nested=1, codeNode=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 ony 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): """build parameters: - handle defaults - handle format tuple parameters """ if self.version >= 3.6: value = default else: value = self.traverse(default, indent='') maybe_show_tree_param_default(self.showast, name, value) 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.03 <= self.version: lambda_index = -3 else: lambda_index = None args_node = node[-1] if isinstance(args_node.attr, tuple): pos_args, kw_args, annotate_argc = args_node.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 (self.version <= 3.3 and len(node) > 2 and node[lambda_index] != 'LOAD_LAMBDA' and (have_kwargs or node[lc_index].kind != 'load_closure')): # args are after kwargs; kwargs are bundled as one node defparams = node[1:args_node.attr[0] + 1] else: # args are before kwargs; kwags as bundled as one node defparams = node[:args_node.attr[0]] pass else: if self.version < 3.6: defparams = node[:args_node.attr] kw_args = 0 else: default, kw_args, annotate, closure = args_node.attr if default: assert node[ 0] == 'expr', "expecting mkfunc default node to be an expr" expr_node = node[0] if (expr_node[0] == 'LOAD_CONST' and isinstance(expr_node[0].attr, tuple)): defparams = [repr(a) for a in expr_node[0].attr] elif expr_node[0] in frozenset( ('list', 'tuple', 'dict', 'set')): defparams = [ self.traverse(n, indent='') for n in expr_node[0][:-1] ] else: defparams = [] # FIXME: handle kw, annotate and closure 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 = codeNode.attr assert iscode(code) scanner_code = Code(code, self.scanner, self.currentclass) # add defaults values to parameter names argc = code.co_argcount paramnames = list(scanner_code.co_varnames[:argc]) # defaults are for last n parameters, thus reverse if not 3.0 <= self.version <= 3.1 or self.version >= 3.6: paramnames.reverse() defparams.reverse() try: ast = self.build_ast(scanner_code._tokens, scanner_code._customize, 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 kw_pairs = args_node.attr[1] if self.version >= 3.0 else 0 # build parameters params = [] if defparams: for i, defparam in enumerate(defparams): params.append(build_param(ast, paramnames[i], defparam)) params += paramnames[i + 1:] else: params = paramnames if not 3.0 <= self.version <= 3.1 or self.version >= 3.6: params.reverse() # back to correct order if code_has_star_arg(code): if self.version > 3.0: params.append('*%s' % code.co_varnames[argc + kw_pairs]) else: params.append('*%s' % code.co_varnames[argc]) argc += 1 # 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: self.write("(", ", ".join(params)) # self.println(indent, '#flags:\t', int(code.co_flags)) ends_in_comma = False if kw_args > 0: if not (4 & code.co_flags): if argc > 0: self.write(", *, ") else: self.write("*, ") pass else: self.write(", ") ends_in_comma = True # FIXME: this is not correct for 3.5. or 3.6 (which works different) # and 3.7? if 3.0 <= self.version <= 3.2: kwargs = node[0] last = len(kwargs) - 1 i = 0 for n in node[0]: if n == 'kwarg': self.write('%s=' % n[0].pattr) self.preorder(n[1]) if i < last: self.write(', ') ends_in_comma = True pass else: ends_in_comma = False pass i += 1 pass pass elif self.version <= 3.5: # FIXME this is not qute right for 3.5 for n in node: if n == 'pos_arg': continue elif self.version >= 3.4 and not (n.kind in ( 'kwargs', 'no_kwargs', 'kwarg')): continue else: self.preorder(n) ends_in_comma = False break elif self.version >= 3.6: # argc = node[-1].attr # co = node[-3].attr # argcount = co.co_argcount # kwonlyargcount = co.co_kwonlyargcount free_tup = annotate_dict = kw_dict = default_tup = None fn_bits = node[-1].attr index = -4 # Skip over: # MAKE_FUNCTION, # LOAD_CONST qualified name, # LOAD_CONST code object if fn_bits[-1]: free_tup = node[index] index -= 1 if fn_bits[-2]: annotate_dict = node[index] index -= 1 if fn_bits[-3]: kw_dict = node[index] index -= 1 if fn_bits[-4]: default_tup = node[index] if kw_dict == 'expr': kw_dict = kw_dict[0] # FIXME: handle free_tup, annotate_dict, and default_tup if kw_dict: assert kw_dict == 'dict' defaults = [self.traverse(n, indent='') for n in kw_dict[:-2]] names = eval(self.traverse(kw_dict[-2])) assert len(defaults) == len(names) sep = '' # FIXME: possibly handle line breaks for i, n in enumerate(names): self.write(sep) self.write("%s=%s" % (n, defaults[i])) sep = ', ' ends_in_comma = True pass pass pass if code_has_star_star_arg(code): if argc > 0 and not ends_in_comma: self.write(', ') self.write('**%s' % code.co_varnames[argc + kw_pairs]) if is_lambda: self.write(": ") else: 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]) scanner_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, scanner_code._customize, is_lambda=is_lambda, returnNone=rn) scanner_code._tokens = None scanner_code._customize = None # save memory