Merge pull request #60 from rocky/buildstring

Buildstring
This commit is contained in:
Daniel Bradburn
2016-10-22 20:08:30 +02:00
committed by GitHub
7 changed files with 77 additions and 43 deletions

View File

@@ -21,9 +21,13 @@ def expressions(draw):
'container',
'self.attribute',
'self.method()',
'sorted(items, key=lambda x: x.name)',
'func(*args, **kwargs)',
'text or default',
# These expressions are failing, I think these are control
# flow problems rather than problems with FORMAT_VALUE,
# however I need to confirm this...
#'sorted(items, key=lambda x: x.name)',
#'func(*args, **kwargs)',
#'text or default',
#'43 if life_the_universe and everything else None'
)))
@@ -119,6 +123,8 @@ def test_format_specifiers(format_specifier):
def run_test(text):
hypothesis.assume(len(text))
hypothesis.assume("f'{" in text)
expr = text + '\n'
code = compile(expr, '<string>', 'single')
deparsed = deparse_code(PYTHON_VERSION, code, compile_mode='single')
@@ -136,8 +142,8 @@ def test_uncompyle_fstring(fstring):
@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}'",
"f'{abc}{abc!s}'",
"f'{abc}0'",
])
def test_uncompyle_direct(fstring):
"""useful for debugging"""

Binary file not shown.

View File

@@ -1,3 +1,5 @@
var1 = 'x'
var2 = 'y'
print(f'interpolate {var1} strings {var2!r} {var2!s} py36')
print(f'{abc}0')
print(f'{abc}{abc!s}')

View File

@@ -42,13 +42,22 @@ class PythonParser(GenericASTBuilder):
return
def add_unique_rules(self, rules, customize):
"""Add rules to grammar
"""Add rules (a list of string) to grammar
"""
for rule in rules:
if len(rule) == 0:
continue
opname = rule.split('::=')[0].strip()
self.add_unique_rule(rule, opname, 0, customize)
return
def add_unique_doc_rules(self, rules_str, customize):
"""Add rules (a docstring-like list of rules) to grammar
"""
rules = [r.strip() for r in rules_str.split("\n")]
self.add_unique_rules(rules, customize)
return
def cleanup(self):
"""
Remove recursive references to allow garbage

View File

@@ -500,7 +500,6 @@ class Python3Parser(PythonParser):
load_attr ::= expr LOOKUP_METHOD
call_function ::= expr CALL_METHOD
"""
saw_format_value = False
for i, token in enumerate(tokens):
opname = token.type
opname_base = opname[:opname.rfind('_')]
@@ -513,13 +512,6 @@ class Python3Parser(PythonParser):
assign2_pypy ::= expr expr designator designator
""", nop_func)
continue
elif opname == 'FORMAT_VALUE':
# Python 3.6+
self.addRule("""
expr ::= fstring_expr
fstring_expr ::= expr FORMAT_VALUE
""", nop_func)
elif opname in ('CALL_FUNCTION', 'CALL_FUNCTION_VAR',
'CALL_FUNCTION_VAR_KW', 'CALL_FUNCTION_KW'):
self.custom_classfunc_rule(opname, token, customize)
@@ -542,14 +534,6 @@ class Python3Parser(PythonParser):
if opname_base == 'BUILD_TUPLE':
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:
format_or_str_n = "formatted_value_or_str_%s" % v
self.addRule("""
expr ::= joined_str
joined_str ::= LOAD_CONST LOAD_ATTR %s CALL_FUNCTION_1
%s ::= %s%s
""" % (format_or_str_n, format_or_str_n, ("formatted_value_or_str " *v), opname),
nop_func)
elif opname == 'LOOKUP_METHOD':
# A PyPy speciality - DRY with parse2
@@ -734,7 +718,6 @@ class Python33ParserSingle(Python33Parser, PythonParserSingle):
def info(args):
# Check grammar
# Should also add a way to dump grammar
p = Python3Parser()
if len(args) > 0:
arg = args[0]
@@ -746,7 +729,9 @@ def info(args):
elif arg == '3.2':
p = Python32Parser()
p.checkGrammar()
if len(sys.argv) > 1 and sys.argv[1] == 'dump':
print('-' * 50)
p.dumpGrammar()
if __name__ == '__main__':
import sys

View File

@@ -16,16 +16,37 @@ class Python36Parser(Python35Parser):
def p_36misc(self, args):
"""
formatted_value ::= LOAD_FAST FORMAT_VALUE
str ::= LOAD_CONST
joined_str ::= LOAD_CONST LOAD_ATTR format_value_or_strs
BUILD_LIST CALL_FUNCTION
format_value_or_strs ::= format_value_or_strs format_value_or_str
format_value_or_strs ::= format_value_or_str
format_value_or_str ::= format_value
format_value_or_str ::= str
fstring_multi ::= fstring_expr_or_strs BUILD_STRING
fstring_expr_or_strs ::= fstring_expr_or_strs fstring_expr_or_str
fstring_expr_or_strs ::= fstring_expr_or_str
"""
def add_custom_rules(self, tokens, customize):
super(Python36Parser, self).add_custom_rules(tokens, customize)
for i, token in enumerate(tokens):
opname = token.type
if opname == 'FORMAT_VALUE':
rules_str = """
expr ::= fstring_single
fstring_single ::= expr FORMAT_VALUE
"""
self.add_unique_doc_rules(rules_str, customize)
elif opname == 'BUILD_STRING':
v = token.attr
fstring_expr_or_str_n = "fstring_expr_or_str_%s" % v
rules_str = """
expr ::= fstring_expr
fstring_expr ::= expr FORMAT_VALUE
str ::= LOAD_CONST
fstring_expr_or_str ::= fstring_expr
fstring_expr_or_str ::= str
expr ::= fstring_multi
fstring_multi ::= %s BUILD_STRING
%s ::= %sBUILD_STRING
""" % (fstring_expr_or_str_n, fstring_expr_or_str_n, "fstring_expr_or_str " * v)
self.add_unique_doc_rules(rules_str, customize)
class Python36ParserSingle(Python36Parser, PythonParserSingle):
pass

View File

@@ -514,12 +514,11 @@ class SourceWalker(GenericASTTraversal, object):
self.name = None
self.version = version
self.is_pypy = is_pypy
self.customize_for_version(is_pypy, version)
return
@staticmethod
def customize_for_version(is_pypy, version):
def customize_for_version(self, is_pypy, version):
if is_pypy:
########################
# PyPy changes
@@ -632,8 +631,26 @@ class SourceWalker(GenericASTTraversal, object):
# Python 3.6+ Additions
#######################
TABLE_DIRECT.update({
'fstring_expr': ( "f'{%c%{conversion}}'", 0),
'fstring_expr': ( "{%c%{conversion}}", 0),
'fstring_single': ( "f'{%c%{conversion}}'", 0),
'fstring_multi': ( "f'%c'", 0),
})
FSTRING_CONVERSION_MAP = {1: '!s', 2: '!r', 3: '!a'}
def f_conversion(node):
node.conversion = FSTRING_CONVERSION_MAP.get(node.data[1].attr, '')
def n_fstring_expr(node):
f_conversion(node)
self.default(node)
self.n_fstring_expr = n_fstring_expr
def n_fstring_single(node):
f_conversion(node)
self.default(node)
self.n_fstring_single = n_fstring_single
return
f = property(lambda s: s.params['f'],
@@ -1886,12 +1903,6 @@ class SourceWalker(GenericASTTraversal, object):
node[-2][0].type = 'unpack_w_parens'
self.default(node)
FSTRING_CONVERSION_MAP = {1: '!s', 2: '!r', 3: '!a'}
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