X Tutup
Skip to content

Commit c0d50c4

Browse files
committed
Improve Python3 class definition handling
1 parent d2406e9 commit c0d50c4

File tree

8 files changed

+109
-18
lines changed

8 files changed

+109
-18
lines changed

test/bytecode_2.7/00_import.pyc

195 Bytes
Binary file not shown.

test/bytecode_2.7/01_class.pyc

152 Bytes
Binary file not shown.

test/bytecode_3.4/01_class.pyc

132 Bytes
Binary file not shown.

test/simple_source/def/01_class.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
# classdef ::= LOAD_CONST expr mkfunc CALL_FUNCTION_0 BUILD_CLASS designator
99
# mkfunc ::= LOAD_CONST MAKE_FUNCTION_0
1010

11-
# class A:
12-
# pass
11+
class A:
12+
pass
1313

1414
class B(Exception):
1515
pass

test/simple_source/exception/05_try_except.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@
22
pass
33
except ImportError as exc:
44
pass
5+
finally:
6+
del exc

uncompyle6/parsers/parse3.py

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -371,11 +371,8 @@ def p_grammar(self, args):
371371
kwarg ::= LOAD_CONST expr
372372
373373
classdef ::= buildclass designator
374-
375374
# Python3 introduced LOAD_BUILD_CLASS
376-
# FIXME: the below should be created by custom rules
377-
buildclass ::= LOAD_BUILD_CLASS mkfunc LOAD_CONST LOAD_NAME CALL_FUNCTION_3
378-
buildclass ::= LOAD_BUILD_CLASS mkfunc LOAD_CONST CALL_FUNCTION_2
375+
# the definition of buildclass is a custom rule
379376
380377
stmt ::= classdefdeco
381378
classdefdeco ::= classdefdeco1 designator
@@ -672,6 +669,44 @@ def p_expr(self, args):
672669
nullexprlist ::=
673670
'''
674671

672+
def custom_buildclass_rule(self, opname, i, token, tokens, customize):
673+
674+
# look for next MAKE_FUNCTION
675+
for i in range(i+1, len(tokens)):
676+
if tokens[i].type.startswith('MAKE_FUNCTION'):
677+
break
678+
pass
679+
assert i < len(tokens)
680+
assert tokens[i+1].type == 'LOAD_CONST'
681+
# find load names
682+
have_loadname = False
683+
for i in range(i+1, len(tokens)):
684+
if tokens[i].type == 'LOAD_NAME':
685+
have_loadname = True
686+
break
687+
if tokens[i].type in 'CALL_FUNCTION':
688+
break
689+
pass
690+
assert i < len(tokens)
691+
if have_loadname:
692+
j = 1
693+
for i in range(i+1, len(tokens)):
694+
if tokens[i].type in 'CALL_FUNCTION':
695+
break
696+
assert tokens[i].type == 'LOAD_NAME'
697+
j += 1
698+
pass
699+
load_names = 'LOAD_NAME ' * j
700+
else:
701+
j = 0
702+
load_names = ''
703+
# customize CALL_FUNCTION
704+
call_function = 'CALL_FUNCTION_%d' % (j + 2)
705+
rule = ("buildclass ::= LOAD_BUILD_CLASS mkfunc LOAD_CONST %s%s" %
706+
(load_names, call_function))
707+
self.add_unique_rule(rule, opname, token.attr, customize)
708+
return
709+
675710
def add_custom_rules(self, tokens, customize):
676711
"""
677712
Special handling for opcodes that take a variable number
@@ -693,7 +728,7 @@ def add_custom_rules(self, tokens, customize):
693728
"""
694729
# from trepan.api import debug
695730
# debug(start_opts={'startup-profile': True})
696-
for token in tokens:
731+
for i, token in enumerate(tokens):
697732
opname = token.type
698733
opname_base = opname[:opname.rfind('_')]
699734

@@ -710,6 +745,8 @@ def add_custom_rules(self, tokens, customize):
710745
+ ('kwarg ' * args_kw)
711746
+ 'expr ' * nak + token.type)
712747
self.add_unique_rule(rule, token.type, args_pos, customize)
748+
elif opname == 'LOAD_BUILD_CLASS':
749+
self.custom_buildclass_rule(opname, i, token, tokens, customize)
713750
elif opname_base in ('BUILD_LIST', 'BUILD_TUPLE', 'BUILD_SET'):
714751
rule = 'build_list ::= ' + 'expr ' * token.attr + opname
715752
self.add_unique_rule(rule, opname, token.attr, customize)

uncompyle6/semantics/fragments.py

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -564,17 +564,30 @@ def n_setcomp(self, node):
564564
def n_classdef(self, node):
565565
# class definition ('class X(A,B,C):')
566566
cclass = self.currentclass
567-
self.currentclass = str(node[0].pattr)
567+
568+
if self.version > 3.0:
569+
buildclass = node[1]
570+
build_list = node[0]
571+
subclass = build_list[1][0].attr
572+
else:
573+
buildclass = node[0]
574+
build_list = buildclass[1][0]
575+
subclass = buildclass[-3][0].attr
568576

569577
self.write('\n\n')
578+
self.currentclass = str(buildclass[0].pattr)
570579
start = len(self.f.getvalue())
571580
self.write(self.indent, 'class ', self.currentclass)
572-
self.print_super_classes(node)
581+
582+
if self.version > 3.0:
583+
self.print_super_classes3(build_list)
584+
else:
585+
self.print_super_classes(build_list)
573586
self.print_(':')
574587

575588
# class body
576589
self.indentMore()
577-
self.build_class(node[2][-2].attr)
590+
self.build_class(subclass)
578591
self.indentLess()
579592

580593
self.currentclass = cclass
@@ -845,6 +858,34 @@ def print_super_classes(self, node):
845858
self.write(')')
846859
self.set_pos_info(node, start, len(self.f.getvalue()))
847860

861+
def print_super_classes3(self, node):
862+
863+
# FIXME: wrap superclasses onto a node
864+
# as a custom rule
865+
start = len(self.f.getvalue())
866+
n = len(node)-1
867+
assert node[n].type.startswith('CALL_FUNCTION')
868+
for i in range(n-1, 0, -1):
869+
if node[i].type != 'LOAD_NAME':
870+
break
871+
pass
872+
873+
if i == n-1:
874+
return
875+
self.write('(')
876+
line_separator = ', '
877+
sep = ''
878+
i += 1
879+
while i < n:
880+
value = self.traverse(node[i])
881+
self.node_append(sep, value, node[i])
882+
i += 1
883+
self.write(sep, value)
884+
sep = line_separator
885+
886+
self.write(')')
887+
self.set_pos_info(node, start, len(self.f.getvalue()))
888+
848889
def n_mapexpr(self, node):
849890
"""
850891
prettyprint a mapexpr

uncompyle6/semantics/pysource.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,20 +1104,22 @@ def print_super_classes(self, node):
11041104

11051105
def print_super_classes3(self, node):
11061106

1107-
# FIXME: put blow logic into grammar
1107+
# FIXME: wrap superclasses onto a node
11081108
# as a custom rule
1109-
i = 0
1110-
for i, n in enumerate(node[:-1]):
1111-
if n.type == 'LOAD_NAME':
1109+
n = len(node)-1
1110+
assert node[n].type.startswith('CALL_FUNCTION')
1111+
for i in range(n-1, 0, -1):
1112+
if node[i].type != 'LOAD_NAME':
11121113
break
11131114
pass
11141115

1115-
if i == 0:
1116+
if i == n-1:
11161117
return
11171118
self.write('(')
11181119
line_separator = ', '
11191120
sep = ''
1120-
while i <= len(node) - 2:
1121+
i += 1
1122+
while i < n:
11211123
value = self.traverse(node[i])
11221124
i += 1
11231125
self.write(sep, value)
@@ -1493,17 +1495,26 @@ def build_class(self, code):
14931495

14941496
if ast[0][0] == NAME_MODULE:
14951497
del ast[0]
1498+
QUAL_NAME = AST('stmt',
1499+
[ AST('assign',
1500+
[ AST('expr', [Token('LOAD_CONST', pattr=self.currentclass)]),
1501+
AST('designator', [ Token('STORE_NAME', pattr='__qualname__')])
1502+
])])
1503+
if ast[0][0] == QUAL_NAME:
1504+
del ast[0]
1505+
pass
1506+
pass
14961507

14971508
# if docstring exists, dump it
14981509
if (code.co_consts and code.co_consts[0] is not None
1499-
and ast[0][0] == ASSIGN_DOC_STRING(code.co_consts[0])):
1510+
and len(ast) > 0 and ast[0][0] == ASSIGN_DOC_STRING(code.co_consts[0])):
15001511
self.print_docstring(indent, code.co_consts[0])
15011512
self.print_()
15021513
del ast[0]
15031514

15041515
# the function defining a class normally returns locals(); we
15051516
# don't want this to show up in the source, thus remove the node
1506-
if ast[-1][0] == RETURN_LOCALS:
1517+
if len(ast) > 0 and ast[-1][0] == RETURN_LOCALS:
15071518
del ast[-1] # remove last node
15081519
# else:
15091520
# print ast[-1][-1]

0 commit comments

Comments
 (0)
X Tutup