From b94cce7b12cee4df54f45d3321221a0797620d0f Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 11 May 2019 06:10:12 -0400 Subject: [PATCH 1/4] Revise format string handling fstring_single{1,2} -> format_value{1,2} to match Python AST names better --- uncompyle6/parser.py | 3 +- uncompyle6/parsers/parse36.py | 28 +++------ uncompyle6/scanners/scanner36.py | 2 + uncompyle6/semantics/customize36.py | 94 ++++++++++++----------------- uncompyle6/semantics/pysource.py | 6 +- 5 files changed, 51 insertions(+), 82 deletions(-) diff --git a/uncompyle6/parser.py b/uncompyle6/parser.py index ffc759f3..53625fe2 100644 --- a/uncompyle6/parser.py +++ b/uncompyle6/parser.py @@ -61,7 +61,6 @@ class PythonParser(GenericASTBuilder): 'imports_cont', 'kvlist_n', # Python 3.6+ - 'joined_str', 'come_from_loops', ] self.collect = frozenset(nt_list) @@ -83,7 +82,7 @@ class PythonParser(GenericASTBuilder): # FIXME: would love to do expr, sstmts, stmts and # so on but that would require major changes to the # semantic actions - self.singleton = frozenset(('str', 'joined_str', 'store', '_stmts', 'suite_stmts_opt', + self.singleton = frozenset(('str', 'store', '_stmts', 'suite_stmts_opt', 'inplace_op')) # Instructions filled in from scanner self.insts = [] diff --git a/uncompyle6/parsers/parse36.py b/uncompyle6/parsers/parse36.py index cb3a3258..f87da84b 100644 --- a/uncompyle6/parsers/parse36.py +++ b/uncompyle6/parsers/parse36.py @@ -188,21 +188,14 @@ class Python36Parser(Python35Parser): self.add_unique_doc_rules(rules_str, customize) elif opname == 'FORMAT_VALUE': rules_str = """ - expr ::= fstring_single - fstring_single ::= expr FORMAT_VALUE - expr ::= fstring_expr - fstring_expr ::= expr FORMAT_VALUE - - str ::= LOAD_CONST - formatted_value ::= fstring_expr - formatted_value ::= str - + expr ::= formatted_value1 + formatted_value1 ::= expr FORMAT_VALUE """ self.add_unique_doc_rules(rules_str, customize) elif opname == 'FORMAT_VALUE_ATTR': rules_str = """ - expr ::= fstring_single - fstring_single ::= expr expr FORMAT_VALUE_ATTR + expr ::= formatted_value2 + formatted_value2 ::= expr expr FORMAT_VALUE_ATTR """ self.add_unique_doc_rules(rules_str, customize) elif opname == 'MAKE_FUNCTION_8': @@ -246,17 +239,12 @@ class Python36Parser(Python35Parser): """ self.addRule(rules_str, nop_func) - elif opname == 'BUILD_STRING': + elif opname.startswith('BUILD_STRING'): v = token.attr - joined_str_n = "formatted_value_%s" % v rules_str = """ - expr ::= fstring_multi - fstring_multi ::= joined_str BUILD_STRING - fstr ::= expr - joined_str ::= fstr+ - fstring_multi ::= %s BUILD_STRING - %s ::= %sBUILD_STRING - """ % (joined_str_n, joined_str_n, "formatted_value " * v) + expr ::= joined_str + joined_str ::= %sBUILD_STRING_%d + """ % ("expr " * v, v) self.add_unique_doc_rules(rules_str, customize) if 'FORMAT_VALUE_ATTR' in self.seen_ops: rules_str = """ diff --git a/uncompyle6/scanners/scanner36.py b/uncompyle6/scanners/scanner36.py index 743954f1..9130d68f 100644 --- a/uncompyle6/scanners/scanner36.py +++ b/uncompyle6/scanners/scanner36.py @@ -33,6 +33,8 @@ class Scanner36(Scanner3): t.op == self.opc.CALL_FUNCTION_EX and t.attr & 1): t.kind = 'CALL_FUNCTION_EX_KW' pass + elif t.op == self.opc.BUILD_STRING: + t.kind = 'BUILD_STRING_%s' % t.attr elif t.op == self.opc.CALL_FUNCTION_KW: t.kind = 'CALL_FUNCTION_KW_%s' % t.attr elif t.op == self.opc.FORMAT_VALUE: diff --git a/uncompyle6/semantics/customize36.py b/uncompyle6/semantics/customize36.py index 2f2dbdfd..54007809 100644 --- a/uncompyle6/semantics/customize36.py +++ b/uncompyle6/semantics/customize36.py @@ -45,13 +45,6 @@ def customize_for_version36(self, version): TABLE_DIRECT.update({ 'tryfinally36': ( '%|try:\n%+%c%-%|finally:\n%+%c%-\n\n', (1, 'returns'), 3 ), - 'fstring_expr': ( "{%c%{conversion}}", - (0, 'expr') ), - # FIXME: the below assumes the format strings - # don't have ''' in them. Fix this properly - 'fstring_single': ( "f'''{%c%{conversion}}'''", 0), - 'formatted_value_attr': ( "f'''{%c%{conversion}}%{string}'''", - (0, 'expr')), 'func_args36': ( "%c(**", 0), 'try_except36': ( '%|try:\n%+%c%-%c\n\n', 1, -2 ), 'except_return': ( '%|except:\n%+%c%-', 3 ), @@ -129,7 +122,7 @@ def customize_for_version36(self, version): expr = node[1] assert expr == 'expr' - + value = self.format_pos_args(expr) if value == '': fmt = "%c(%p)" @@ -157,7 +150,7 @@ def customize_for_version36(self, version): self.template_engine( (fmt, (0, 'expr'), (2, 'build_map_unpack_with_call', 100)), node) - + self.prune() self.n_call_ex_kw2 = call_ex_kw2 @@ -166,18 +159,18 @@ def customize_for_version36(self, version): BUILD_MAP_UNPACK_WITH_CALL""" self.preorder(node[0]) self.write('(') - + value = self.format_pos_args(node[1][0]) if value == '': pass else: self.write(value) self.write(', ') - + self.write('*') self.preorder(node[1][1]) self.write(', ') - + kwargs = node[2] if kwargs == 'expr': kwargs = kwargs[0] @@ -436,58 +429,49 @@ def customize_for_version36(self, version): else: data = fmt_node.attr node.conversion = FSTRING_CONVERSION_MAP.get(data, '') + return node.conversion - def n_fstring_expr(node): - f_conversion(node) - self.default(node) - self.n_fstring_expr = n_fstring_expr - - def n_fstr(node): - if node[0] == 'expr' and node[0][0] == 'fstring_expr': - f_conversion(node[0][0]) - self.default(node[0][0]) - else: - value = strip_quotes(self.traverse(node[0], indent='')) - pass - self.write(value) + def n_formatted_value1(node): + expr = node[0] + assert expr == 'expr' + value = self.traverse(expr, indent='') + conversion = f_conversion(node) + f_str = "f%s" % escape_string("{%s%s}" % (value, conversion)) + self.write(f_str) self.prune() - self.n_fstr = n_fstr - def n_fstring_single(node): - attr4 = len(node) == 3 and node[-1] == 'FORMAT_VALUE_ATTR' and node[-1].attr == 4 - if attr4 and hasattr(node[0][0], 'attr'): - assert node[0] == 'expr' + self.n_formatted_value1 = n_formatted_value1 + + def n_formatted_value2(node): + expr = node[0] + assert expr == 'expr' + value = self.traverse(expr, indent='') + format_value_attr = node[-1] + assert format_value_attr == 'FORMAT_VALUE_ATTR' + attr = format_value_attr.attr + if attr == 4: assert node[1] == 'expr' - self.write("{%s:%s}" % (node[0][0].attr, node[1][0].attr)) - self.prune() + fmt = strip_quotes(self.traverse(node[1], indent='')) + conversion = ":%s" % fmt else: - f_conversion(node) - self.default(node) - self.n_fstring_single = n_fstring_single + conversion = FSTRING_CONVERSION_MAP.get(attr, '') + + f_str = "f%s" % escape_string("{%s%s}" % (value, conversion)) + self.write(f_str) + self.prune() + self.n_formatted_value2 = n_formatted_value2 def n_joined_str(node): result = '' - for fstr_node in node: - assert fstr_node == 'fstr' - assert fstr_node[0] == 'expr' - subnode = fstr_node[0][0] - if subnode.kind == 'fstring_expr': - # Don't include outer f'...' - f_conversion(subnode) - data = strip_quotes(self.traverse(subnode, indent='')) - result += data - elif subnode == 'LOAD_CONST': - result += strip_quotes(escape_string(subnode.attr)) - elif subnode == 'fstring_single': - f_conversion(subnode) - data = self.traverse(subnode, indent='') - if data[0:1] == 'f': - data = strip_quotes(data[1:]) - result += data - pass - else: - result += strip_quotes(self.traverse(subnode, indent='')) + for expr in node[:-1]: + assert expr == 'expr' + value = self.traverse(expr, indent='') + if expr[0].kind.startswith('formatted_value'): + # remove leading 'f' + value = value[1:] pass + # Remove leading quotes + result += strip_quotes(value) pass self.write('f%s' % escape_string(result)) self.prune() diff --git a/uncompyle6/semantics/pysource.py b/uncompyle6/semantics/pysource.py index 7a59a72c..a63b3556 100644 --- a/uncompyle6/semantics/pysource.py +++ b/uncompyle6/semantics/pysource.py @@ -1837,11 +1837,7 @@ class SourceWalker(GenericASTTraversal, object): typ = m.group('type') or '{' node = startnode if m.group('child'): - try: - node = node[int(m.group('child'))] - except: - from trepan.api import debug; debug() - pass + node = node[int(m.group('child'))] if typ == '%': self.write('%') elif typ == '+': From d99e78d46db26b4d459b1a147c6beaef3913634e Mon Sep 17 00:00:00 2001 From: x0ret Date: Mon, 13 May 2019 02:27:43 +0430 Subject: [PATCH 2/4] set precedences value for format strings --- uncompyle6/semantics/consts.py | 120 ++++++++++++++-------------- uncompyle6/semantics/customize36.py | 12 ++- 2 files changed, 71 insertions(+), 61 deletions(-) diff --git a/uncompyle6/semantics/consts.py b/uncompyle6/semantics/consts.py index 9cc7c1ce..4928e79c 100644 --- a/uncompyle6/semantics/consts.py +++ b/uncompyle6/semantics/consts.py @@ -33,8 +33,8 @@ else: # for a list. The top to down order here is reversed # from the list in the above lin. -# Things at the top of this list below with low-value precidence will -# tend to have parenthesis around them. Things at the bottom +# Things at the bottom of this list below with low-value precedence will +# tend to have parenthesis around them. Things at the top # of the list will tend not to have parenthesis around them. # Note: The values in this table tend to be even value. Inside @@ -43,67 +43,67 @@ else: 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_subscript': 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, # Shifts << - 'BINARY_RSHIFT': 12, # Shifts >> - - 'BINARY_AND': 14, # Bitwise AND - 'BINARY_XOR': 16, # Bitwise XOR - 'BINARY_OR': 18, # Bitwise OR - - 'compare': 20, # in, not in, is, is not, <, <=, >, >=, !=, == - 'unary_not': 22, # Boolean NOT - 'and': 24, # Boolean AND - 'ret_and': 24, - - 'or': 26, # Boolean OR - 'ret_or': 26, - - 'conditional': 28, # Conditional expression - 'conditional_lamdba': 28, # Lambda expression - 'conditional_not_lamdba': 28, # Lambda expression - 'conditionalnot': 28, - 'if_expr_true': 28, - 'ret_cond': 28, + 'yield_from': 102, + 'yield': 102, '_mklambda': 30, - 'yield': 102, - 'yield_from': 102 + 'ret_cond': 28, + 'if_expr_true': 28, + 'conditionalnot': 28, + 'conditional_not_lamdba': 28, # Lambda expression + 'conditional_lamdba': 28, # Lambda expression + 'conditional': 28, # Conditional expression + + 'ret_or': 26, + 'or': 26, # Boolean OR + + 'ret_and': 24, + 'and': 24, # Boolean AND + 'unary_not': 22, # Boolean NOT + 'compare': 20, # in, not in, is, is not, <, <=, >, >=, !=, == + + 'BINARY_OR': 18, # Bitwise OR + 'BINARY_XOR': 16, # Bitwise XOR + 'BINARY_AND': 14, # Bitwise AND + + 'BINARY_RSHIFT': 12, # Shifts >> + 'BINARY_LSHIFT': 12, # Shifts << + + 'BINARY_SUBTRACT': 10, + 'BINARY_ADD': 10, + + 'BINARY_MODULO': 8, + 'BINARY_FLOOR_DIVIDE': 8, + 'BINARY_TRUE_DIVIDE': 8, + 'BINARY_DIVIDE': 8, + 'BINARY_MULTIPLY': 8, + + 'unary_expr': 6, + + 'BINARY_POWER': 4, + + 'call': 2, + 'buildslice3': 2, + 'buildslice2': 2, + 'slice3': 2, + 'slice2': 2, + 'slice1': 2, + 'slice0': 2, + 'delete_subscript': 2, + 'store_subscript': 2, + 'subscript2': 2, + 'subscript': 2, + 'attribute': 2, + + 'generator_exp': 0, + 'list_comp': 0, + 'set_comp_expr': 0, + 'set_comp': 0, + 'dict_comp': 0, + 'unary_convert': 0, + 'dict': 0, + 'list': 0, } LINE_LENGTH = 80 diff --git a/uncompyle6/semantics/customize36.py b/uncompyle6/semantics/customize36.py index 54007809..289ecc8a 100644 --- a/uncompyle6/semantics/customize36.py +++ b/uncompyle6/semantics/customize36.py @@ -41,6 +41,7 @@ def customize_for_version36(self, version): PRECEDENCE['call_ex_kw3'] = 1 PRECEDENCE['call_ex_kw4'] = 1 PRECEDENCE['unmap_dict'] = 0 + PRECEDENCE['formatted_value1'] = 100 TABLE_DIRECT.update({ 'tryfinally36': ( '%|try:\n%+%c%-%|finally:\n%+%c%-\n\n', @@ -418,7 +419,6 @@ def customize_for_version36(self, version): node.string = escape_format(fmt_node[0].attr) else: node.string = fmt_node - self.default(node) self.n_formatted_value_attr = n_formatted_value_attr @@ -443,6 +443,9 @@ def customize_for_version36(self, version): self.n_formatted_value1 = n_formatted_value1 def n_formatted_value2(node): + p = self.prec + self.prec = 100 + expr = node[0] assert expr == 'expr' value = self.traverse(expr, indent='') @@ -458,10 +461,15 @@ def customize_for_version36(self, version): f_str = "f%s" % escape_string("{%s%s}" % (value, conversion)) self.write(f_str) + + self.prec = p self.prune() self.n_formatted_value2 = n_formatted_value2 def n_joined_str(node): + p = self.prec + self.prec = 100 + result = '' for expr in node[:-1]: assert expr == 'expr' @@ -474,6 +482,8 @@ def customize_for_version36(self, version): result += strip_quotes(value) pass self.write('f%s' % escape_string(result)) + + self.prec = p self.prune() self.n_joined_str = n_joined_str From 1cc08d959857b6986d21179a3cc0c08d3750e461 Mon Sep 17 00:00:00 2001 From: rocky Date: Mon, 13 May 2019 08:08:29 -0400 Subject: [PATCH 3/4] Make precedence table top-bottom order reference... in https://docs.python.org/2/reference/expressions.html#operator-precedence or https://docs.python.org/3/reference/expressions.html#operator-precedence . --- uncompyle6/semantics/consts.py | 86 +++++++++++++++++----------------- 1 file changed, 44 insertions(+), 42 deletions(-) diff --git a/uncompyle6/semantics/consts.py b/uncompyle6/semantics/consts.py index 4928e79c..10707eea 100644 --- a/uncompyle6/semantics/consts.py +++ b/uncompyle6/semantics/consts.py @@ -27,83 +27,85 @@ 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. The top to down order here is reversed -# from the list in the above lin. +# Operator precidence See +# https://docs.python.org/2/reference/expressions.html#operator-precedence +# or +# https://docs.python.org/3/reference/expressions.html#operator-precedence +# for a list. We keep the same top-to-botom order here as in the above links, +# so we start with low precedence (high values) and go down in value. -# Things at the bottom of this list below with low-value precedence will +# Things at the bottom of this list below with high precedence (low value) will # tend to have parenthesis around them. Things at the top # of the list will tend not to have parenthesis around them. -# Note: The values in this table tend to be even value. Inside +# Note: The values in this table are even numbers. Inside # various templates we use odd values. Avoiding equal-precedent comparisons # avoids ambiguity what to do when the precedence is equal. PRECEDENCE = { - 'yield_from': 102, 'yield': 102, + 'yield_from': 102, '_mklambda': 30, - 'ret_cond': 28, - 'if_expr_true': 28, - 'conditionalnot': 28, - 'conditional_not_lamdba': 28, # Lambda expression - 'conditional_lamdba': 28, # Lambda expression 'conditional': 28, # Conditional expression + 'conditional_lamdba': 28, # Lambda expression + 'conditional_not_lamdba': 28, # Lambda expression + 'conditionalnot': 28, + 'if_expr_true': 28, + 'ret_cond': 28, - 'ret_or': 26, 'or': 26, # Boolean OR + 'ret_or': 26, - 'ret_and': 24, 'and': 24, # Boolean AND - 'unary_not': 22, # Boolean NOT 'compare': 20, # in, not in, is, is not, <, <=, >, >=, !=, == + 'ret_and': 24, + 'unary_not': 22, # Boolean NOT + 'BINARY_AND': 14, # Bitwise AND 'BINARY_OR': 18, # Bitwise OR 'BINARY_XOR': 16, # Bitwise XOR - 'BINARY_AND': 14, # Bitwise AND - 'BINARY_RSHIFT': 12, # Shifts >> 'BINARY_LSHIFT': 12, # Shifts << + 'BINARY_RSHIFT': 12, # Shifts >> - 'BINARY_SUBTRACT': 10, - 'BINARY_ADD': 10, + 'BINARY_ADD': 10, # - + 'BINARY_SUBTRACT': 10, # + - 'BINARY_MODULO': 8, - 'BINARY_FLOOR_DIVIDE': 8, - 'BINARY_TRUE_DIVIDE': 8, - 'BINARY_DIVIDE': 8, - 'BINARY_MULTIPLY': 8, + 'BINARY_DIVIDE': 8, # / + 'BINARY_FLOOR_DIVIDE': 8, # // + 'BINARY_MATRIX_MULTIPLY': 8, # @ + 'BINARY_MODULO': 8, # Remainder, % + 'BINARY_MULTIPLY': 8, # * + 'BINARY_TRUE_DIVIDE': 8, # Division / - 'unary_expr': 6, + 'unary_expr': 6, # +x, -x, ~x - 'BINARY_POWER': 4, + 'BINARY_POWER': 4, # Exponentiation, * - 'call': 2, - 'buildslice3': 2, - 'buildslice2': 2, - 'slice3': 2, - 'slice2': 2, - 'slice1': 2, - 'slice0': 2, + 'attribute': 2, # x.attribute + 'buildslice2': 2, # x[index] + 'buildslice3': 2, # x[index:index] + 'call': 2, # x(arguments...) 'delete_subscript': 2, + 'slice0': 2, + 'slice1': 2, + 'slice2': 2, + 'slice3': 2, 'store_subscript': 2, - 'subscript2': 2, 'subscript': 2, - 'attribute': 2, + 'subscript2': 2, - 'generator_exp': 0, - 'list_comp': 0, - 'set_comp_expr': 0, - 'set_comp': 0, + 'dict': 0, # {expressions...} 'dict_comp': 0, + 'generator_exp': 0, # (expressions...) + 'list': 0, # [expressions...] + 'list_comp': 0, + 'set_comp': 0, + 'set_comp_expr': 0, 'unary_convert': 0, - 'dict': 0, - 'list': 0, } LINE_LENGTH = 80 From 8b5e0f49f850f04047aa7964f717d1ee7910766d Mon Sep 17 00:00:00 2001 From: rocky Date: Mon, 13 May 2019 09:35:47 -0400 Subject: [PATCH 4/4] Handle {{ }} escape, but when appropriate --- test/bytecode_3.6_run/01_fstring.pyc | Bin 901 -> 1565 bytes test/bytecode_3.7_run/01_fstring.pyc | Bin 905 -> 1564 bytes test/simple_source/bug36/01_fstring.py | 26 ++++++++++++++++++++++++- uncompyle6/semantics/customize36.py | 11 +++++++++++ 4 files changed, 36 insertions(+), 1 deletion(-) diff --git a/test/bytecode_3.6_run/01_fstring.pyc b/test/bytecode_3.6_run/01_fstring.pyc index 8f4d459fd101fcd2b2d8390d087352df43d8ff6b..89d675e6a772d18c012950005541264b866fcc7a 100644 GIT binary patch delta 841 zcmZ8f&ubGw6rMM;N!FMuXnqtey0)ccZ5m^L9K;$6iU*M*^tLD=&CG_>&2F9DV7qMy znCeYX;z1}mdGhGdlYfVM_2j?MlW(StNC)1V@6DV2-uve5yUaK@w=y-AeJ_8#_>m^$ zH~DX65O1J`U%t;R!N9pHt{f9yMoVdAACqy4-+o0uLij9VZ-Fm5d0r8DzI1Hx>X_<` zt21|a?T9!9ewQ!fUgUXR;Kep`N}?!AL*OfI(6>M9+twDbR}OJSY=lR^nGtF6sDthUTgV7|rhL@l)g+9R~k zKzB&7Fl>G!Hj@`ImN|3@c(R~^0vTOr3xjkd!l)Ci;!#M^5h|zAw-l3FYFAIESr-sQ zgabD@CHWRbakYZOh8i^4K&y6oj5%r{R?d@5M zmOERHX~i1+E2N6!%*Ym6ID@W@#4q!GI0T##(H_ReC;HkzdKzaQ94OXm%x=lPmSod} zR%;2k6M?9*ghVIVb*~dft)RPGf2i1XDTrsVy#e;;`qgzWXf?gCj$m>#ruM82@)~|% zpT0?A>;xU(h9vEFgvY~kWl*n{>-zUq5fdE&11Kwkv=GPl&gpNWg0hm(LNOH` dy&LeJC+g}S2{%U1+1e!oEM`EFnHkf}!e1k}!7C(A3&|fRUe3HflYx15cSxbWEI*XLB0+ZT}oBDG)+Tk4~S9_APxxiQckO)D2=^QV6$7iTM;&i zB9UIWQH29is08A~kt1h*1^W;93q3J*6AraB<2N4b&AhQcWWLPIJ(``(5?t%ypZ#Cy zlerRn{&{l^2F_J+WlZ=AT1q2(Oiok$)@%9^!siit4}8_h@v_MAwXwk~r&MQLomuDA zW8&obZN7nff#-Oh7n;l|ih?K(f!}F@zWqtxw)Tj;6q8zNH_0{C(Psk9yL6PuGku+h*u&%kAn6;V|hq6|)Au&SXx$$Kwq9f^BosP9?xr-|? z9h9yArBwMe+8$askFJT-FLQn90lpH^9!`xD`o_QlG|t@bD%MKOuFJj_Wz&RKstLGL ziKw%PL}%G;uN6j(pnbUWK(Xsm5YN{Jxt*X;5+3NNF^L)&VF#tr2`#7O zHRKunrca+qHJZdw-@(axL9-@m`e)b|J PIRh+aK!KSV)6Bv@40ggU delta 142 zcmbQk)5*@~#LLUY00cZ2FU5Rgp2#P`=(JH?mr*KJC`Ds7ON!=PrfjAOjKy{(%uS3b z3c(DTTAPnB@-vFOWCd!fVm2^UiDEV|O8Ld8qA5ICf>np@7E4xsX5M5^RyWZouAKbz r_@eyMyp-aV3`I&n9Yso$1DVzNIc#$CQ%ZAE?O1?f#YU5FvYG+_0gfdL diff --git a/test/simple_source/bug36/01_fstring.py b/test/simple_source/bug36/01_fstring.py index b19a8d39..728add4b 100644 --- a/test/simple_source/bug36/01_fstring.py +++ b/test/simple_source/bug36/01_fstring.py @@ -39,6 +39,30 @@ source = 'foo' source = (f"__file__ = r'''{os.path.abspath(filename)}'''\n" + source + "\ndel __file__") -# From 3.7.3 datalasses.py +# Note how { and } are *not* escaped here +f = 'one' +name = 'two' +assert(f"{f}{'{{name}}'} {f}{'{name}'}") == 'one{{name}} one{name}' + +# From 3.7.3 dataclasses.py log_rounds = 5 assert "05$" == f'{log_rounds:02d}$' + + +def testit(a, b, l): + # print(l) + return l + +# The call below shows the need for BUILD_STRING to count expr arguments. +# Also note that we use {{ }} to escape braces in contrast to the example +# above. +def _repr_fn(fields): + return testit('__repr__', + ('self',), + ['return xx + f"(' + + ', '.join([f"{f}={{self.{f}!r}}" + for f in fields]) + + ')"']) + +fields = ['a', 'b', 'c'] +assert _repr_fn(fields) == ['return xx + f"(a={self.a!r}, b={self.b!r}, c={self.c!r})"'] diff --git a/uncompyle6/semantics/customize36.py b/uncompyle6/semantics/customize36.py index 289ecc8a..2245e648 100644 --- a/uncompyle6/semantics/customize36.py +++ b/uncompyle6/semantics/customize36.py @@ -476,8 +476,19 @@ def customize_for_version36(self, version): value = self.traverse(expr, indent='') if expr[0].kind.startswith('formatted_value'): # remove leading 'f' + assert value.startswith('f') value = value[1:] pass + else: + # {{ and }} in Python source-code format strings mean + # { and } respectively. But only when *not* part of a + # formatted value. However in the LOAD_CONST + # bytecode, the escaping of the braces has been + # removed. So we need to put back the braces escaping in + # reconstructing the source. + assert expr[0] == 'LOAD_CONST' + value = value.replace("{", "{{").replace("}", "}}") + # Remove leading quotes result += strip_quotes(value) pass