You've already forked python-uncompyle6
mirror of
https://github.com/rocky/python-uncompyle6.git
synced 2025-08-03 00:45:53 +08:00
559 lines
17 KiB
Python
559 lines
17 KiB
Python
# Copyright (c) 2015, 2016 by Rocky Bernstein
|
|
# Copyright (c) 2000-2002 by hartmut Goebel <h.goebel@crazy-compilers.com>
|
|
"""
|
|
All the crazy things we have to do to handle Python functions
|
|
"""
|
|
from xdis.code import iscode
|
|
from uncompyle6.scanner import Code
|
|
from uncompyle6.parsers.astnode import AST
|
|
from uncompyle6 import PYTHON3
|
|
from uncompyle6.semantics.parser_error import ParserError
|
|
|
|
if PYTHON3:
|
|
from itertools import zip_longest
|
|
else:
|
|
from itertools import izip_longest as zip_longest
|
|
|
|
from uncompyle6.show import maybe_show_ast_param_default
|
|
|
|
def find_all_globals(node, globs):
|
|
"""Find globals in this statement."""
|
|
for n in node:
|
|
if isinstance(n, AST):
|
|
globs = find_all_globals(n, globs)
|
|
elif n.type in ('STORE_GLOBAL', 'DELETE_GLOBAL', 'LOAD_GLOBAL'):
|
|
globs.add(n.pattr)
|
|
return globs
|
|
|
|
def find_globals(node, globs):
|
|
"""Find globals in this statement."""
|
|
for n in node:
|
|
if isinstance(n, AST):
|
|
globs = find_globals(n, globs)
|
|
elif n.type in ('STORE_GLOBAL', 'DELETE_GLOBAL'):
|
|
globs.add(n.pattr)
|
|
return globs
|
|
|
|
def find_none(node):
|
|
for n in node:
|
|
if isinstance(n, AST):
|
|
if not n in ('return_stmt', 'return_if_stmt'):
|
|
if find_none(n):
|
|
return True
|
|
elif n.type == 'LOAD_CONST' and n.pattr is None:
|
|
return True
|
|
return False
|
|
|
|
# FIXME: DRY the below code...
|
|
|
|
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].type.startswith('MAKE_')
|
|
|
|
annotate_tuple = node[annotate_last]
|
|
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].type in ('annotate_arg' 'annotate_tuple'):
|
|
annotate_args[annotate_tup[i]] = (node[j][0].attr,
|
|
node[j][0] == 'LOAD_CONST')
|
|
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
|
|
|
|
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].type == '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 4 & code.co_flags: # flag 2 -> variable number of args
|
|
self.write('*%s' % code.co_varnames[argc + kw_pairs])
|
|
argc += 1
|
|
|
|
i = len(paramnames) - len(defparams)
|
|
suffix = ''
|
|
for param in paramnames[:i]:
|
|
self.write(suffix, param)
|
|
if param in annotate_args:
|
|
value, string = annotate_args[param]
|
|
if string:
|
|
self.write(': "%s"' % value)
|
|
else:
|
|
self.write(': %s' % value)
|
|
suffix = ', '
|
|
|
|
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:
|
|
self.write(':"%s' % annotate_args[param])
|
|
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 > 0:
|
|
if not (4 & code.co_flags):
|
|
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':
|
|
self.write('%s=' % n[0].pattr)
|
|
self.preorder(n[1])
|
|
if i < last:
|
|
self.write(', ')
|
|
i += 1
|
|
pass
|
|
pass
|
|
pass
|
|
|
|
if 8 & code.co_flags: # flag 3 -> keyword args
|
|
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_args:
|
|
value, string = annotate_args['return']
|
|
if string:
|
|
self.write(' -> "%s"' % value)
|
|
else:
|
|
self.write(' -> %s' % value)
|
|
|
|
self.println(":")
|
|
|
|
if (len(code.co_consts) > 0 and
|
|
code.co_consts[0] is not None and not isLambda): # ugly
|
|
# docstring exists, dump it
|
|
self.print_docstring(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_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].type.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 isLambda and iscode(node[lambda_index].attr):
|
|
assert node[lambda_index].type == '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 4 & code.co_flags: # flag 2 -> variable number of args
|
|
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 8 & code.co_flags: # flag 3 -> keyword args
|
|
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
|
|
self.print_docstring(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, isLambda, nested=1, codeNode=None):
|
|
"""Dump function definition, doc string, and function body."""
|
|
|
|
# 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 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].type.startswith('MAKE_')
|
|
|
|
args_node = node[-1]
|
|
if isinstance(args_node.attr, tuple):
|
|
if self.version <= 3.3:
|
|
# positional args are after kwargs
|
|
defparams = node[1:args_node.attr[0]+1]
|
|
else:
|
|
# 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
|
|
|
|
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].type == '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.2:
|
|
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
|
|
if self.version != 3.2:
|
|
params = [build_param(ast, name, default) for
|
|
name, default in zip_longest(paramnames, defparams, fillvalue=None)]
|
|
params.reverse() # back to correct order
|
|
|
|
if 4 & code.co_flags: # flag 2 -> variable number of args
|
|
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))
|
|
|
|
else:
|
|
if isLambda:
|
|
self.write("lambda ")
|
|
else:
|
|
self.write("(")
|
|
pass
|
|
|
|
last_line = self.f.getvalue().split("\n")[-1]
|
|
l = len(last_line)
|
|
indent = ' ' * l
|
|
line_number = self.line_number
|
|
|
|
if 4 & code.co_flags: # flag 2 -> variable number of args
|
|
self.write('*%s' % code.co_varnames[argc + kw_pairs])
|
|
argc += 1
|
|
|
|
i = len(paramnames) - len(defparams)
|
|
self.write(", ".join(paramnames[:i]))
|
|
suffix = ', ' if i > 0 else ''
|
|
for n in node:
|
|
if n == 'pos_arg':
|
|
self.write(suffix)
|
|
self.write(paramnames[i] + '=')
|
|
i += 1
|
|
self.preorder(n)
|
|
if (line_number != self.line_number):
|
|
suffix = ",\n" + indent
|
|
line_number = self.line_number
|
|
else:
|
|
suffix = ', '
|
|
|
|
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 n.type != 'kwargs':
|
|
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 8 & code.co_flags: # flag 3 -> keyword args
|
|
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
|
|
self.print_docstring(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
|