From bae3d2e36191552cd8f562ed51cd244c394e5a06 Mon Sep 17 00:00:00 2001 From: moagstar Date: Wed, 21 Sep 2016 22:04:46 +0200 Subject: [PATCH] merge fstring changes from moagstar --- pytest/test_fstring.py | 42 +++++++++++++++++++------------- uncompyle6/parsers/parse3.py | 9 ++----- uncompyle6/semantics/pysource.py | 10 +++++--- 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/pytest/test_fstring.py b/pytest/test_fstring.py index 0b6329e7..e06d4fd0 100644 --- a/pytest/test_fstring.py +++ b/pytest/test_fstring.py @@ -87,19 +87,23 @@ def fstrings(draw): :return: A valid f-string. """ + character_strategy = st.characters( + blacklist_characters='\r\n\'\\s{}', + min_codepoint=1, + max_codepoint=1000, + ) is_raw = draw(st.booleans()) integer_strategy = st.integers(min_value=0, max_value=3) expression_count = draw(integer_strategy) content = [] for _ in range(expression_count): expression = draw(expressions()) - # not yet : conversion not supported - conversion = ''#draw(st.sampled_from(('', '!s', '!r', '!a',))) + conversion = draw(st.sampled_from(('', '!s', '!r', '!a',))) has_specifier = draw(st.booleans()) specifier = ':' + draw(format_specifiers()) if has_specifier else '' content.append('{{{}{}}}'.format(expression, conversion, specifier)) + content.append(draw(st.text(character_strategy))) content = ''.join(content) - return "f{}'{}'".format('r' if is_raw else '', content) @@ -114,23 +118,27 @@ def test_format_specifiers(format_specifier): raise +def run_test(text): + expr = text + '\n' + code = compile(expr, '', 'single') + deparsed = deparse_code(PYTHON_VERSION, code, compile_mode='single') + recompiled = compile(deparsed.text, '', 'single') + if recompiled != code: + assert 'dis(' + deparsed.text.strip('\n') + ')' == 'dis(' + expr.strip('\n') + ')' + + @pytest.mark.skipif(PYTHON_VERSION < 3.6, reason='need at least python 3.6') @hypothesis.given(fstrings()) def test_uncompyle_fstring(fstring): """Verify uncompyling fstring bytecode""" + run_test(fstring) - # ignore fstring with no expressions an fsring with - # no expressions just gets compiled to a normal string. - hypothesis.assume('{' in fstring) - # BUG : At the moment a single expression is not supported - # for example f'{abc}'. - hypothesis.assume(fstring.count('{') > 1) - - expr = fstring + '\n' - code = compile(expr, '', 'single') - deparsed = deparse_code(PYTHON_VERSION, code, compile_mode='single') - recompiled = compile(deparsed.text, '', 'single') - - if recompiled != code: - assert deparsed.text == expr +@pytest.mark.skipif(PYTHON_VERSION < 3.6, reason='need at least python 3.6') +@pytest.mark.parametrize('fstring', [ + #"f'{abc}{abc!s}'", + "f'{abc!s}'", +]) +def test_uncompyle_direct(fstring): + """useful for debugging""" + run_test(fstring) \ No newline at end of file diff --git a/uncompyle6/parsers/parse3.py b/uncompyle6/parsers/parse3.py index 3b211ca1..9940f7a3 100644 --- a/uncompyle6/parsers/parse3.py +++ b/uncompyle6/parsers/parse3.py @@ -462,13 +462,9 @@ class Python3Parser(PythonParser): elif opname == 'FORMAT_VALUE': # Python 3.6+ self.addRule(""" - formatted_value ::= expr FORMAT_VALUE - formatted_value ::= expr FORMAT_VALUE - str ::= LOAD_CONST - formatted_value_or_str ::= formatted_value - formatted_value_or_str ::= str + expr ::= fstring_expr + fstring_expr ::= expr FORMAT_VALUE """, nop_func) - saw_format_value = True elif opname in ('CALL_FUNCTION', 'CALL_FUNCTION_VAR', 'CALL_FUNCTION_VAR_KW', 'CALL_FUNCTION_KW'): @@ -493,7 +489,6 @@ class Python3Parser(PythonParser): rule = ('load_closure ::= %s%s' % (('LOAD_CLOSURE ' * v), opname)) self.add_unique_rule(rule, opname, token.attr, customize) if opname_base == 'BUILD_LIST' and saw_format_value: - saw_format_value = False format_or_str_n = "formatted_value_or_str_%s" % v self.addRule(""" expr ::= joined_str diff --git a/uncompyle6/semantics/pysource.py b/uncompyle6/semantics/pysource.py index 56640695..69cd6f7f 100644 --- a/uncompyle6/semantics/pysource.py +++ b/uncompyle6/semantics/pysource.py @@ -624,9 +624,7 @@ class SourceWalker(GenericASTTraversal, object): # Python 3.6+ Additions ####################### TABLE_DIRECT.update({ - 'formatted_value': ( '{%c%c}', 0, 1), - 'FORMAT_VALUE': ( '%{pattr}', ), - 'joined_str': ( "f'%c'", 2), + 'fstring_expr': ( "f'{%c%{conversion}}'", 0), }) return @@ -1778,6 +1776,12 @@ class SourceWalker(GenericASTTraversal, object): node[-2][0].type = 'unpack_w_parens' self.default(node) + FSTRING_CONVERSION_MAP = {i+1: '!'+x for i, x in enumerate('sra')} + + def n_fstring_expr(self, node): + node.conversion = self.FSTRING_CONVERSION_MAP.get(node.data[1].attr, '') + self.default(node) + def engine(self, entry, startnode): """The format template interpetation engine. See the comment at the beginning of this module for the how we interpret format specifications such as