Author: Armin Rigo <ar...@tunes.org> 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 pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit