Improve Python3 class definition handling

This commit is contained in:
rocky
2015-12-24 19:21:36 -05:00
parent d2406e9d57
commit c0d50c4d96
8 changed files with 109 additions and 18 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -8,8 +8,8 @@
# classdef ::= LOAD_CONST expr mkfunc CALL_FUNCTION_0 BUILD_CLASS designator
# mkfunc ::= LOAD_CONST MAKE_FUNCTION_0
# class A:
# pass
class A:
pass
class B(Exception):
pass

View File

@@ -2,3 +2,5 @@ try:
pass
except ImportError as exc:
pass
finally:
del exc

View File

@@ -371,11 +371,8 @@ class Python3Parser(PythonParser):
kwarg ::= LOAD_CONST expr
classdef ::= buildclass designator
# Python3 introduced LOAD_BUILD_CLASS
# FIXME: the below should be created by custom rules
buildclass ::= LOAD_BUILD_CLASS mkfunc LOAD_CONST LOAD_NAME CALL_FUNCTION_3
buildclass ::= LOAD_BUILD_CLASS mkfunc LOAD_CONST CALL_FUNCTION_2
# the definition of buildclass is a custom rule
stmt ::= classdefdeco
classdefdeco ::= classdefdeco1 designator
@@ -672,6 +669,44 @@ class Python3Parser(PythonParser):
nullexprlist ::=
'''
def custom_buildclass_rule(self, opname, i, token, tokens, customize):
# look for next MAKE_FUNCTION
for i in range(i+1, len(tokens)):
if tokens[i].type.startswith('MAKE_FUNCTION'):
break
pass
assert i < len(tokens)
assert tokens[i+1].type == 'LOAD_CONST'
# find load names
have_loadname = False
for i in range(i+1, len(tokens)):
if tokens[i].type == 'LOAD_NAME':
have_loadname = True
break
if tokens[i].type in 'CALL_FUNCTION':
break
pass
assert i < len(tokens)
if have_loadname:
j = 1
for i in range(i+1, len(tokens)):
if tokens[i].type in 'CALL_FUNCTION':
break
assert tokens[i].type == 'LOAD_NAME'
j += 1
pass
load_names = 'LOAD_NAME ' * j
else:
j = 0
load_names = ''
# customize CALL_FUNCTION
call_function = 'CALL_FUNCTION_%d' % (j + 2)
rule = ("buildclass ::= LOAD_BUILD_CLASS mkfunc LOAD_CONST %s%s" %
(load_names, call_function))
self.add_unique_rule(rule, opname, token.attr, customize)
return
def add_custom_rules(self, tokens, customize):
"""
Special handling for opcodes that take a variable number
@@ -693,7 +728,7 @@ class Python3Parser(PythonParser):
"""
# from trepan.api import debug
# debug(start_opts={'startup-profile': True})
for token in tokens:
for i, token in enumerate(tokens):
opname = token.type
opname_base = opname[:opname.rfind('_')]
@@ -710,6 +745,8 @@ class Python3Parser(PythonParser):
+ ('kwarg ' * args_kw)
+ 'expr ' * nak + token.type)
self.add_unique_rule(rule, token.type, args_pos, customize)
elif opname == 'LOAD_BUILD_CLASS':
self.custom_buildclass_rule(opname, i, token, tokens, customize)
elif opname_base in ('BUILD_LIST', 'BUILD_TUPLE', 'BUILD_SET'):
rule = 'build_list ::= ' + 'expr ' * token.attr + opname
self.add_unique_rule(rule, opname, token.attr, customize)

View File

@@ -564,17 +564,30 @@ class Traverser(pysource.Walker, object):
def n_classdef(self, node):
# class definition ('class X(A,B,C):')
cclass = self.currentclass
self.currentclass = str(node[0].pattr)
if self.version > 3.0:
buildclass = node[1]
build_list = node[0]
subclass = build_list[1][0].attr
else:
buildclass = node[0]
build_list = buildclass[1][0]
subclass = buildclass[-3][0].attr
self.write('\n\n')
self.currentclass = str(buildclass[0].pattr)
start = len(self.f.getvalue())
self.write(self.indent, 'class ', self.currentclass)
self.print_super_classes(node)
if self.version > 3.0:
self.print_super_classes3(build_list)
else:
self.print_super_classes(build_list)
self.print_(':')
# class body
self.indentMore()
self.build_class(node[2][-2].attr)
self.build_class(subclass)
self.indentLess()
self.currentclass = cclass
@@ -845,6 +858,34 @@ class Traverser(pysource.Walker, object):
self.write(')')
self.set_pos_info(node, start, len(self.f.getvalue()))
def print_super_classes3(self, node):
# FIXME: wrap superclasses onto a node
# as a custom rule
start = len(self.f.getvalue())
n = len(node)-1
assert node[n].type.startswith('CALL_FUNCTION')
for i in range(n-1, 0, -1):
if node[i].type != 'LOAD_NAME':
break
pass
if i == n-1:
return
self.write('(')
line_separator = ', '
sep = ''
i += 1
while i < n:
value = self.traverse(node[i])
self.node_append(sep, value, node[i])
i += 1
self.write(sep, value)
sep = line_separator
self.write(')')
self.set_pos_info(node, start, len(self.f.getvalue()))
def n_mapexpr(self, node):
"""
prettyprint a mapexpr

View File

@@ -1104,20 +1104,22 @@ class Walker(GenericASTTraversal, object):
def print_super_classes3(self, node):
# FIXME: put blow logic into grammar
# FIXME: wrap superclasses onto a node
# as a custom rule
i = 0
for i, n in enumerate(node[:-1]):
if n.type == 'LOAD_NAME':
n = len(node)-1
assert node[n].type.startswith('CALL_FUNCTION')
for i in range(n-1, 0, -1):
if node[i].type != 'LOAD_NAME':
break
pass
if i == 0:
if i == n-1:
return
self.write('(')
line_separator = ', '
sep = ''
while i <= len(node) - 2:
i += 1
while i < n:
value = self.traverse(node[i])
i += 1
self.write(sep, value)
@@ -1493,17 +1495,26 @@ class Walker(GenericASTTraversal, object):
if ast[0][0] == NAME_MODULE:
del ast[0]
QUAL_NAME = AST('stmt',
[ AST('assign',
[ AST('expr', [Token('LOAD_CONST', pattr=self.currentclass)]),
AST('designator', [ Token('STORE_NAME', pattr='__qualname__')])
])])
if ast[0][0] == QUAL_NAME:
del ast[0]
pass
pass
# if docstring exists, dump it
if (code.co_consts and code.co_consts[0] is not None
and ast[0][0] == ASSIGN_DOC_STRING(code.co_consts[0])):
and len(ast) > 0 and ast[0][0] == ASSIGN_DOC_STRING(code.co_consts[0])):
self.print_docstring(indent, code.co_consts[0])
self.print_()
del ast[0]
# the function defining a class normally returns locals(); we
# don't want this to show up in the source, thus remove the node
if ast[-1][0] == RETURN_LOCALS:
if len(ast) > 0 and ast[-1][0] == RETURN_LOCALS:
del ast[-1] # remove last node
# else:
# print ast[-1][-1]