From c0d50c4d9651d07d9b6b4fb08e7cedd7ea8713fd Mon Sep 17 00:00:00 2001 From: rocky Date: Thu, 24 Dec 2015 19:21:36 -0500 Subject: [PATCH] Improve Python3 class definition handling --- test/bytecode_2.7/00_import.pyc | Bin 0 -> 195 bytes test/bytecode_2.7/01_class.pyc | Bin 296 -> 448 bytes test/bytecode_3.4/01_class.pyc | Bin 274 -> 406 bytes test/simple_source/def/01_class.py | 4 +- test/simple_source/exception/05_try_except.py | 2 + uncompyle6/parsers/parse3.py | 47 ++++++++++++++++-- uncompyle6/semantics/fragments.py | 47 ++++++++++++++++-- uncompyle6/semantics/pysource.py | 27 +++++++--- 8 files changed, 109 insertions(+), 18 deletions(-) create mode 100644 test/bytecode_2.7/00_import.pyc diff --git a/test/bytecode_2.7/00_import.pyc b/test/bytecode_2.7/00_import.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f14d8fe238b0b1ec7bafd29f7402a925afd2b852 GIT binary patch literal 195 zcmZSn%*&O@Rvnhi00m4y+5w1*6@Wwv5HT|3Ffc?x*h~yLj10L<3{gxBj6jKC4Q8OC z%>O{(r@;thmaqVcg2a*xutW(+wz#sm1Y~A@aS)JX&_E@M)q(ut%-n*U)cE53(xT*4 peHgDKx1?C#z#twdnO{_*S5R5P0aR;~o1apelWGSx8e}dHBLETmCno>^ literal 0 HcmV?d00001 diff --git a/test/bytecode_2.7/01_class.pyc b/test/bytecode_2.7/01_class.pyc index f7babcd0d45ad102a259fe72c91c10084f79c929..be7aa4ac8ebd485c7d315d6ab5c9bebb730feca2 100644 GIT binary patch delta 184 zcmZ3%bbvXG`75 k8bCIK21qmr#46zg60Q}=sRbpO`FTOi6Yn`9OcQ1X0AnH>>Hq)$ delta 124 zcmX@Wyn@M=`7+z2_#%Al2Z#xGV}9-7$yranoM57$jb><36x_1 E099EPZvX%Q diff --git a/test/bytecode_3.4/01_class.pyc b/test/bytecode_3.4/01_class.pyc index 71a91979238c6660c4c9bc86088c6357d2e48437..5cbcbe334b5d1a3d88c7b39b32a361a0d0303d96 100644 GIT binary patch delta 211 zcmbQlG>tj*9S<+pvdWsU2u22m#|%h-1;};);$mGO;hw_4kiy8&0w$Z87@|NzObjW^ zU?FCPR7QqoW`-!B2n$27ChJ5eJx0fgh2a9+AOo0y1P@~o&;o{uPDZ{(Abt@uhydA9 u#EMF=fw=5If)9vcdO>_YO=hqew>VuZl2Z#xGV}9_*eAYF7aJnJ2zBn%Ewqzz0&x1SEJEi-2Y^_-QiT;&iP@PAw?O%+D)goSem|$O%>ilw<$^ DSB4fN diff --git a/test/simple_source/def/01_class.py b/test/simple_source/def/01_class.py index 18f59c0d..95cb2774 100644 --- a/test/simple_source/def/01_class.py +++ b/test/simple_source/def/01_class.py @@ -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 diff --git a/test/simple_source/exception/05_try_except.py b/test/simple_source/exception/05_try_except.py index 6babda61..a7bb51db 100644 --- a/test/simple_source/exception/05_try_except.py +++ b/test/simple_source/exception/05_try_except.py @@ -2,3 +2,5 @@ try: pass except ImportError as exc: pass +finally: + del exc diff --git a/uncompyle6/parsers/parse3.py b/uncompyle6/parsers/parse3.py index 86ed8cfb..3ed342e8 100644 --- a/uncompyle6/parsers/parse3.py +++ b/uncompyle6/parsers/parse3.py @@ -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) diff --git a/uncompyle6/semantics/fragments.py b/uncompyle6/semantics/fragments.py index 12b20273..f37ff643 100644 --- a/uncompyle6/semantics/fragments.py +++ b/uncompyle6/semantics/fragments.py @@ -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 diff --git a/uncompyle6/semantics/pysource.py b/uncompyle6/semantics/pysource.py index 41f52727..45e0d8b7 100644 --- a/uncompyle6/semantics/pysource.py +++ b/uncompyle6/semantics/pysource.py @@ -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]