Author: Armin Rigo <[email protected]>
Branch: py3.5-fstring-pep498
Changeset: r89695:4b57696884e6
Date: 2017-01-22 21:31 +0100
http://bitbucket.org/pypy/pypy/changeset/4b57696884e6/
Log: in-progress: turn a simple f-string to AST
diff --git a/pypy/interpreter/astcompiler/astbuilder.py
b/pypy/interpreter/astcompiler/astbuilder.py
--- a/pypy/interpreter/astcompiler/astbuilder.py
+++ b/pypy/interpreter/astcompiler/astbuilder.py
@@ -7,9 +7,9 @@
from rpython.rlib.objectmodel import always_inline, we_are_translated
-def ast_from_node(space, node, compile_info):
+def ast_from_node(space, node, compile_info, recursive_parser=None):
"""Turn a parse tree, node, to AST."""
- ast = ASTBuilder(space, node, compile_info).build_ast()
+ ast = ASTBuilder(space, node, compile_info, recursive_parser).build_ast()
#
# When we are not translated, we send this ast to validate_ast.
# The goal is to check that validate_ast doesn't crash on valid
@@ -54,10 +54,11 @@
class ASTBuilder(object):
- def __init__(self, space, n, compile_info):
+ def __init__(self, space, n, compile_info, recursive_parser=None):
self.space = space
self.compile_info = compile_info
self.root_node = n
+ self.recursive_parser = recursive_parser
def build_ast(self):
"""Convert an top level parse tree node into an AST mod."""
@@ -1206,40 +1207,93 @@
joined_pieces.append(node(w_string, atom_node.get_lineno(),
atom_node.get_column()))
- def _f_string_expr(self, joined_pieces, u, start, atom_node):
+ def _f_constant_string(self, joined_pieces, u, atom_node):
+ self._add_constant_string(joined_pieces, self.space.newunicode(u),
+ atom_node)
+
+ def _f_string_compile(self, source, atom_node):
# Note: a f-string is kept as a single literal up to here.
# At this point only, we recursively call the AST compiler
# on all the '{expr}' parts. The 'expr' part is not parsed
# or even tokenized together with the rest of the source code!
- ...
+ from pypy.interpreter.pyparser import pyparse
+
+ if self.recursive_parser is None:
+ self.error("internal error: parser not available for parsing "
+ "the expressions inside the f-string", atom_node)
+ source = source.encode('utf-8')
+
+ info = pyparse.CompileInfo("<fstring>", "eval",
+ consts.PyCF_SOURCE_IS_UTF8 |
+ consts.PyCF_IGNORE_COOKIE,
+ optimize=self.compile_info.optimize)
+ parse_tree = self.recursive_parser.parse_source(source, info)
+ return ast_from_node(self.space, parse_tree, info)
+
+ def _f_string_expr(self, joined_pieces, u, start, atom_node):
+ conversion = -1 # the conversion char. -1 if not specified.
+ nested_depth = 0 # nesting level for braces/parens/brackets in exprs
+ p = start
+ while p < len(u):
+ ch = u[p]
+ p += 1
+ if ch in u'[{(':
+ nested_depth += 1
+ elif nested_depth > 0 and ch in u']})':
+ nested_depth -= 1
+ elif nested_depth == 0 and ch in u'!:}':
+ # special-case '!='
+ if ch == u'!' and p < len(u) and u[p] == u'=':
+ continue
+ break # normal way out of this loop
+ # XXX forbid comment, but how?
+ else:
+ raise self.error("f-string: unterminated '{' expression")
+ if nested_depth > 0:
+ self.error("f-string: mismatched '(', '{' or '['")
+ if ch == u'!':
+ XXX
+ if ch == u':':
+ XXX
+ assert ch == u'}'
+ end_f_string = p
+ p -= 1 # drop the final '}'
+ assert p >= start
+ expr = self._f_string_compile(u[start:p], atom_node)
+ assert isinstance(expr, ast.Expression)
+ joined_pieces.append(expr.body)
+ return end_f_string
def _parse_f_string(self, joined_pieces, w_string, atom_node):
space = self.space
u = space.unicode_w(w_string)
- conversion = -1 # the conversion char. -1 if not specified.
- nested_depth = 0 # nesting level for braces/parens/brackets in exprs
start = 0
p1 = u.find(u'{')
- p2 = u.find(u'}')
- while p1 >= 0 or p2 >= 0:
- if p1 >= 0 and (p2 < 0 or p1 < p2):
- pn = p1 + 1
- if pn < len(u) and u[pn] == u'{': # '{{' => single '{'
- self._add_constant_string(space.newunicode(u[start:pn]))
+ while True:
+ if p1 < 0:
+ p1 = len(u)
+ p2 = u.find(u'}', start, p1)
+ if p2 >= 0:
+ pn = p2 + 1
+ if pn < len(u) and u[pn] == u'}': # '}}' => single '}'
+ self._f_constant_string(joined_pieces, u[start:pn],
+ atom_node)
start = pn + 1
else:
- start = self._f_string_expr(joined_pieces, u, pn,
atom_node)
- p1 = u.find(u'{', start)
+ self.error("f-string: unexpected '}'", atom_node)
+ continue
+ if p1 == len(u):
+ self._f_constant_string(joined_pieces, u[start:], atom_node)
+ break # no more '{' or '}' left
+ pn = p1 + 1
+ if pn < len(u) and u[pn] == u'{': # '{{' => single '{'
+ self._f_constant_string(joined_pieces, u[start:pn], atom_node)
+ start = pn + 1
else:
- assert p2 >= 0 and (p1 < 0 or p2 < p1)
- pn = p2 + 1
- if pn < len(u) and u[pn] == u'}': # '}}' => single '}'
- self._add_constant_string(space.newunicode(u[start:pn]))
- start = pn + 1
- else:
- self.error("unexpected '}' in f-string", atom_node)
- p2 = u.find(u'}', start)
- self._add_constant_string(space.newunicode(u[start:]))
+ assert u[p1] == u'{'
+ start = self._f_string_expr(joined_pieces, u, pn, atom_node)
+ assert u[start - 1] == u'}'
+ p1 = u.find(u'{', start)
def handle_atom(self, atom_node):
first_child = atom_node.get_child(0)
@@ -1290,11 +1344,12 @@
values = [node for node in joined_pieces
if not (isinstance(node, ast.Str) and not node.s)]
if len(values) > 1:
- return ast.JoinedStr(values)
+ return ast.JoinedStr(values, atom_node.get_lineno(),
+ atom_node.get_column())
elif len(values) == 1:
return values[0]
else:
- assert len(joined_pieces) > 0 # but all empty strings
+ assert len(joined_pieces) > 0 # they are all empty strings
return joined_pieces[0]
#
elif first_child_type == tokens.NUMBER:
diff --git a/pypy/interpreter/astcompiler/test/test_compiler.py
b/pypy/interpreter/astcompiler/test/test_compiler.py
--- a/pypy/interpreter/astcompiler/test/test_compiler.py
+++ b/pypy/interpreter/astcompiler/test/test_compiler.py
@@ -20,7 +20,7 @@
p = pyparse.PythonParser(space)
info = pyparse.CompileInfo("<test>", 'exec')
cst = p.parse_source(expr, info)
- ast = astbuilder.ast_from_node(space, cst, info)
+ ast = astbuilder.ast_from_node(space, cst, info, recursive_parser=p)
function_ast = optimize.optimize_ast(space, ast.body[0], info)
function_ast = ast.body[0]
assert isinstance(function_ast, FunctionDef)
diff --git a/pypy/interpreter/pycompiler.py b/pypy/interpreter/pycompiler.py
--- a/pypy/interpreter/pycompiler.py
+++ b/pypy/interpreter/pycompiler.py
@@ -150,7 +150,8 @@
space = self.space
try:
parse_tree = self.parser.parse_source(source, info)
- mod = astbuilder.ast_from_node(space, parse_tree, info)
+ mod = astbuilder.ast_from_node(space, parse_tree, info,
+ recursive_parser=self.parser)
except parseerror.TabError as e:
raise OperationError(space.w_TabError,
e.wrap_info(space))
diff --git a/pypy/module/parser/pyparser.py b/pypy/module/parser/pyparser.py
--- a/pypy/module/parser/pyparser.py
+++ b/pypy/module/parser/pyparser.py
@@ -9,9 +9,10 @@
class W_STType(W_Root):
- def __init__(self, tree, mode):
+ def __init__(self, tree, mode, recursive_parser=None):
self.tree = tree
self.mode = mode
+ self.recursive_parser = recursive_parser
@specialize.arg(3)
def _build_app_tree(self, space, node, seq_maker, with_lineno,
with_column):
@@ -52,7 +53,7 @@
def descr_compile(self, space, filename="<syntax-tree>"):
info = pyparse.CompileInfo(filename, self.mode)
try:
- ast = ast_from_node(space, self.tree, info)
+ ast = ast_from_node(space, self.tree, info, self.recursive_parser)
result = compile_ast(space, ast, info)
except error.IndentationError as e:
raise OperationError(space.w_IndentationError,
@@ -82,7 +83,7 @@
except error.SyntaxError as e:
raise OperationError(space.w_SyntaxError,
e.wrap_info(space))
- return space.wrap(W_STType(tree, mode))
+ return space.wrap(W_STType(tree, mode, recursive_parser=parser))
@unwrap_spec(source=str)
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit