diff --git a/pytest/test_pysource.py b/pytest/test_pysource.py index 902222c0..bc866671 100644 --- a/pytest/test_pysource.py +++ b/pytest/test_pysource.py @@ -127,11 +127,17 @@ def test_tables(): "Full entry: %s" % (name, k, arg, typ, entry[arg], type(entry[arg]), entry) ) - assert len(tup) == 2 + assert 2 <= len(tup) <= 3 for j, x in enumerate(tup): - assert isinstance(x, int), ( - "%s[%s][%d][%d] type '%s' is '%s should be an int but is %s. Full entry: %s" % - (name, k, arg, j, typ, x, type(x), entry) + if len(tup) == 3 and j == 1: + assert isinstance(x, str), ( + "%s[%s][%d][%d] type '%s' is '%s should be an string but is %s. Full entry: %s" % + (name, k, arg, j, typ, x, type(x), entry) + ) + else: + assert isinstance(x, int), ( + "%s[%s][%d][%d] type '%s' is '%s should be an int but is %s. Full entry: %s" % + (name, k, arg, j, typ, x, type(x), entry) ) pass arg += 1 diff --git a/test/bytecode_2.7_run/04_subscript.pyc b/test/bytecode_2.7_run/04_subscript.pyc new file mode 100644 index 00000000..3cd38e7c Binary files /dev/null and b/test/bytecode_2.7_run/04_subscript.pyc differ diff --git a/test/bytecode_3.6_run/04_subscript.pyc b/test/bytecode_3.6_run/04_subscript.pyc new file mode 100644 index 00000000..baaf71a4 Binary files /dev/null and b/test/bytecode_3.6_run/04_subscript.pyc differ diff --git a/test/simple_source/expression/04_subscript.py b/test/simple_source/expression/04_subscript.py new file mode 100644 index 00000000..1d6dfca0 --- /dev/null +++ b/test/simple_source/expression/04_subscript.py @@ -0,0 +1,11 @@ +# From 3.6.8 idlelib/query.py +# Bug was handling parens around subscript + +# RUNNABLE! +a = {'text': 1} +b = {'text': 3} +for widget, entry, expect in ( + (a, b, 1), + (None, b, 3) + ): + assert (widget or entry)['text'] == expect diff --git a/uncompyle6/parser.py b/uncompyle6/parser.py index e7e5675c..ffc759f3 100644 --- a/uncompyle6/parser.py +++ b/uncompyle6/parser.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015-2018 Rocky Bernstein +# Copyright (c) 2015-2019 Rocky Bernstein # Copyright (c) 2005 by Dan Pascu # Copyright (c) 2000-2002 by hartmut Goebel # Copyright (c) 1999 John Aycock @@ -589,14 +589,14 @@ class PythonParser(GenericASTBuilder): ## designLists ::= ## Will need to redo semantic actiion - store ::= STORE_FAST - store ::= STORE_NAME - store ::= STORE_GLOBAL - store ::= STORE_DEREF - store ::= expr STORE_ATTR - store ::= store_subscr - store_subscr ::= expr expr STORE_SUBSCR - store ::= unpack + store ::= STORE_FAST + store ::= STORE_NAME + store ::= STORE_GLOBAL + store ::= STORE_DEREF + store ::= expr STORE_ATTR + store ::= store_subscript + store_subscript ::= expr expr STORE_SUBSCR + store ::= unpack ''' diff --git a/uncompyle6/semantics/consts.py b/uncompyle6/semantics/consts.py index 6183e060..719b5789 100644 --- a/uncompyle6/semantics/consts.py +++ b/uncompyle6/semantics/consts.py @@ -27,6 +27,78 @@ else: maxint = sys.maxint +# Operator precidence +# See https://docs.python.org/2/reference/expressions.html +# or https://docs.python.org/3/reference/expressions.html +# for a list. + +# Things at the top of this list below with low-value precidence will +# tend to have parenthesis around them. Things at the bottom +# of the list will tend not to have parenthesis around them. +PRECEDENCE = { + 'list': 0, + 'dict': 0, + 'unary_convert': 0, + 'dict_comp': 0, + 'set_comp': 0, + 'set_comp_expr': 0, + 'list_comp': 0, + 'generator_exp': 0, + + 'attribute': 2, + 'subscript': 2, + 'subscript2': 2, + 'store_subscript': 2, + 'delete_subscr': 2, + 'slice0': 2, + 'slice1': 2, + 'slice2': 2, + 'slice3': 2, + 'buildslice2': 2, + 'buildslice3': 2, + 'call': 2, + + 'BINARY_POWER': 4, + + 'unary_expr': 6, + + 'BINARY_MULTIPLY': 8, + 'BINARY_DIVIDE': 8, + 'BINARY_TRUE_DIVIDE': 8, + 'BINARY_FLOOR_DIVIDE': 8, + 'BINARY_MODULO': 8, + + 'BINARY_ADD': 10, + 'BINARY_SUBTRACT': 10, + + 'BINARY_LSHIFT': 12, + 'BINARY_RSHIFT': 12, + + 'BINARY_AND': 14, + 'BINARY_XOR': 16, + 'BINARY_OR': 18, + + 'compare': 20, + 'unary_not': 22, + 'and': 24, + 'ret_and': 24, + + 'or': 26, + 'ret_or': 26, + + 'conditional': 28, + 'conditional_lamdba': 28, + 'conditional_not_lamdba': 28, + 'conditionalnot': 28, + 'ret_cond': 28, + 'ret_cond_not': 28, + + '_mklambda': 30, + + 'yield': 101, + 'yield_from': 101 +} + LINE_LENGTH = 80 # Some parse trees created below are used for comparing code @@ -150,15 +222,17 @@ TABLE_DIRECT = { 'DELETE_FAST': ( '%|del %{pattr}\n', ), 'DELETE_NAME': ( '%|del %{pattr}\n', ), 'DELETE_GLOBAL': ( '%|del %{pattr}\n', ), - 'delete_subscr': ( '%|del %c[%c]\n', - (0, 'expr'), (1, 'expr') ), - 'subscript': ( '%c[%p]', - (0, 'expr'), - (1, 100) ), - 'subscript2': ( '%c[%c]', - (0, 'expr'), + 'delete_subscr': ( '%|del %p[%c]\n', + (0, 'expr', PRECEDENCE['subscript']), (1, 'expr') ), + 'subscript': ( '%p[%c]', + (0, 'expr', PRECEDENCE['subscript']), (1, 'expr') ), - 'store_subscr': ( '%c[%c]', 0, 1), + 'subscript2': ( '%p[%c]', + (0, 'expr', PRECEDENCE['subscript']), + (1, 'expr') ), + 'store_subscript': ( '%p[%c]', + (0, 'expr', PRECEDENCE['subscript']), + (1, 'expr') ), 'STORE_FAST': ( '%{pattr}', ), 'STORE_NAME': ( '%{pattr}', ), 'STORE_GLOBAL': ( '%{pattr}', ), @@ -337,76 +411,6 @@ MAP = { 'exprlist': MAP_R0, } -# Operator precidence -# See https://docs.python.org/2/reference/expressions.html -# or https://docs.python.org/3/reference/expressions.html -# for a list. - -# Things at the top of this list below with low-value precidence will -# tend to have parenthesis around them. Things at the bottom -# of the list will tend not to have parenthesis around them. -PRECEDENCE = { - 'list': 0, - 'dict': 0, - 'unary_convert': 0, - 'dict_comp': 0, - 'set_comp': 0, - 'set_comp_expr': 0, - 'list_comp': 0, - 'generator_exp': 0, - - 'attribute': 2, - 'subscript': 2, - 'subscript2': 2, - 'slice0': 2, - 'slice1': 2, - 'slice2': 2, - 'slice3': 2, - 'buildslice2': 2, - 'buildslice3': 2, - 'call': 2, - - 'BINARY_POWER': 4, - - 'unary_expr': 6, - - 'BINARY_MULTIPLY': 8, - 'BINARY_DIVIDE': 8, - 'BINARY_TRUE_DIVIDE': 8, - 'BINARY_FLOOR_DIVIDE': 8, - 'BINARY_MODULO': 8, - - 'BINARY_ADD': 10, - 'BINARY_SUBTRACT': 10, - - 'BINARY_LSHIFT': 12, - 'BINARY_RSHIFT': 12, - - 'BINARY_AND': 14, - 'BINARY_XOR': 16, - 'BINARY_OR': 18, - - 'compare': 20, - 'unary_not': 22, - 'and': 24, - 'ret_and': 24, - - 'or': 26, - 'ret_or': 26, - - 'conditional': 28, - 'conditional_lamdba': 28, - 'conditional_not_lamdba': 28, - 'conditionalnot': 28, - 'ret_cond': 28, - 'ret_cond_not': 28, - - '_mklambda': 30, - - 'yield': 101, - 'yield_from': 101 -} - ASSIGN_TUPLE_PARAM = lambda param_name: \ SyntaxTree('expr', [ Token('LOAD_FAST', pattr=param_name) ]) diff --git a/uncompyle6/semantics/pysource.py b/uncompyle6/semantics/pysource.py index b254b22d..4edeaaca 100644 --- a/uncompyle6/semantics/pysource.py +++ b/uncompyle6/semantics/pysource.py @@ -88,7 +88,8 @@ Python. # # %p like %c but sets the operator precedence. # Its argument then is a tuple indicating the node -# index and the precidence value, an integer. +# index and the precedence value, an integer. If 3 items are given, +# the second item is the nonterminal name and the precedence is given last. # # %C evaluate children recursively, with sibling children separated by the # given string. It needs a 3-tuple: a starting node, the maximimum @@ -616,7 +617,7 @@ class SourceWalker(GenericASTTraversal, object): node[-2][0].kind = 'build_tuple2' self.default(node) - n_store_subscr = n_subscript = n_delete_subscr + n_store_subscript = n_subscript = n_delete_subscr # Note: this node is only in Python 2.x # FIXME: figure out how to get this into customization @@ -1873,7 +1874,18 @@ class SourceWalker(GenericASTTraversal, object): arg += 1 elif typ == 'p': p = self.prec - (index, self.prec) = entry[arg] + tup = entry[arg] + assert isinstance(tup, tuple) + if len(tup) == 3: + (index, nonterm_name, self.prec) = tup + assert node[index] == nonterm_name, ( + "at %s[%d], expected '%s' node; got '%s'" % ( + node.kind, arg, nonterm_name, node[index].kind) + ) + else: + assert len(tup) == 2 + (index, self.prec) = entry[arg] + self.preorder(node[index]) self.prec = p arg += 1