[pypy-commit] pypy py3.7: start working on expression unparsing, needed for PEP 563, postponed evaluation

cfbolz pypy.commits at gmail.com
Sat Jan 4 18:07:36 EST 2020


Author: Carl Friedrich Bolz-Tereick <cfbolz at gmx.de>
Branch: py3.7
Changeset: r98445:0882834dcbb1
Date: 2020-01-05 00:06 +0100
http://bitbucket.org/pypy/pypy/changeset/0882834dcbb1/

Log:	start working on expression unparsing, needed for PEP 563, postponed
	evaluation of type annotations

diff --git a/pypy/interpreter/astcompiler/test/test_unparse.py b/pypy/interpreter/astcompiler/test/test_unparse.py
new file mode 100644
--- /dev/null
+++ b/pypy/interpreter/astcompiler/test/test_unparse.py
@@ -0,0 +1,82 @@
+from pypy.interpreter.pyparser import pyparse
+from pypy.interpreter.astcompiler.astbuilder import ast_from_node
+from pypy.interpreter.astcompiler import ast, consts
+from pypy.interpreter.astcompiler.unparse import unparse
+
+
+class TestAstUnparser:
+    def setup_class(cls):
+        cls.parser = pyparse.PythonParser(cls.space)
+
+    def get_ast(self, source, p_mode="exec", flags=None):
+        if flags is None:
+            flags = consts.CO_FUTURE_WITH_STATEMENT
+        info = pyparse.CompileInfo("<test>", p_mode, flags)
+        tree = self.parser.parse_source(source, info)
+        ast_node = ast_from_node(self.space, tree, info, self.parser)
+        return ast_node
+
+    def get_first_expr(self, source, p_mode="exec", flags=None):
+        mod = self.get_ast(source, p_mode, flags)
+        assert len(mod.body) == 1
+        expr = mod.body[0]
+        assert isinstance(expr, ast.Expr)
+        return expr.value
+
+    def test_num(self):
+        ast = self.get_first_expr("1")
+        assert unparse(self.space, ast) == "1"
+        ast = self.get_first_expr("1.63")
+        assert unparse(self.space, ast) == "1.63"
+
+    def test_str(self):
+        ast = self.get_first_expr("u'a'")
+        assert unparse(self.space, ast) == "'a'"
+
+    def test_bytes(self):
+        ast = self.get_first_expr("b'a'")
+        assert unparse(self.space, ast) == "b'a'"
+
+    def test_name(self):
+        ast = self.get_first_expr('a')
+        assert unparse(self.space, ast) == "a"
+
+    def test_unaryop(self):
+        ast = self.get_first_expr('+a')
+        assert unparse(self.space, ast) == "+a"
+        ast = self.get_first_expr('-a')
+        assert unparse(self.space, ast) == "-a"
+        ast = self.get_first_expr('~a')
+        assert unparse(self.space, ast) == "~a"
+        ast = self.get_first_expr('not a')
+        assert unparse(self.space, ast) == "not a"
+
+    def test_binaryop(self):
+        ast = self.get_first_expr('b+a')
+        assert unparse(self.space, ast) == "b + a"
+        ast = self.get_first_expr('c*(b+a)')
+        assert unparse(self.space, ast) == "c * (b + a)"
+        ast = self.get_first_expr('c**(b+a)')
+        assert unparse(self.space, ast) == "c ** (b + a)"
+        ast = self.get_first_expr('2**2**3')
+        assert unparse(self.space, ast) == "2 ** 2 ** 3"
+        ast = self.get_first_expr('(2**2)**3')
+        assert unparse(self.space, ast) == "(2 ** 2) ** 3"
+        
+        ast = self.get_first_expr('2 << 2 << 3')
+        assert unparse(self.space, ast) == "2 << 2 << 3"
+        ast = self.get_first_expr('2<<(2<<3)')
+        assert unparse(self.space, ast) == "2 << (2 << 3)"
+
+    def test_compare(self):
+        ast = self.get_first_expr('b == 5 == c == d')
+        assert unparse(self.space, ast) == 'b == 5 == c == d'
+
+    def test_boolop(self):
+        ast = self.get_first_expr('b and a and c or d')
+        assert unparse(self.space, ast) == "b and a and c or d"
+
+    def test_if(self):
+        ast = self.get_first_expr('a if b else c')
+        assert unparse(self.space, ast) == "a if b else c"
+
diff --git a/pypy/interpreter/astcompiler/unparse.py b/pypy/interpreter/astcompiler/unparse.py
new file mode 100644
--- /dev/null
+++ b/pypy/interpreter/astcompiler/unparse.py
@@ -0,0 +1,217 @@
+from rpython.rlib.rutf8 import Utf8StringBuilder
+from pypy.interpreter.error import oefmt
+from pypy.interpreter.astcompiler import ast
+
+
+PRIORITY_TEST = 0                   # 'if'-'else', 'lambda'
+PRIORITY_OR = 1                     # 'or'
+PRIORITY_AND = 2                    # 'and'
+PRIORITY_NOT = 3                    # 'not'
+PRIORITY_CMP = 4                    # '<', '>', '==', '>=', '<=', '!=',
+                                    #   'in', 'not in', 'is', 'is not'
+PRIORITY_EXPR = 5
+PRIORITY_BOR = PRIORITY_EXPR = 6    # '|'
+PRIORITY_BXOR = 7                   # '^'
+PRIORITY_BAND = 8                   # '&'
+PRIORITY_SHIFT = 9                  # '<<', '>>'
+PRIORITY_ARITH = 10                 # '+', '-'
+PRIORITY_TERM = 11                  # '*', '@', '/', '%', '//'
+PRIORITY_FACTOR = 12                # unary '+', '-', '~'
+PRIORITY_POWER = 13                 # '**'
+PRIORITY_AWAIT = 14                 # 'await'
+PRIORITY_ATOM = 15
+
+class Parenthesizer(object):
+    def __init__(self, visitor, priority):
+        self.visitor = visitor
+        self.priority = priority
+
+    def __enter__(self):
+        visitor = self.visitor
+        level = visitor.level
+        if level > self.priority:
+            visitor.append_ascii("(")
+
+    def __exit__(self, *args):
+        visitor = self.visitor
+        level = visitor.level
+        if level > self.priority:
+            visitor.append_ascii(")")
+
+
+
+class UnparseVisitor(ast.ASTVisitor):
+    def __init__(self, space):
+        self.space = space
+        self.builder = Utf8StringBuilder()
+        self.level = PRIORITY_TEST
+
+    def maybe_parenthesize(self, priority):
+        return Parenthesizer(self, priority)
+
+    def append_w_str(self, w_s):
+        s, l = self.space.utf8_len_w(w_s)
+        self.builder.append_utf8(s, l)
+
+    def append_ascii(self, s):
+        self.builder.append_utf8(s, len(s))
+
+    def append_expr(self, node, priority):
+        level = self.level
+        self.level = priority
+        try:
+            node.walkabout(self)
+        finally:
+            self.level = level
+
+    def default_visitor(self, node):
+        raise oefmt(self.space.w_SystemError,
+                    "%T is not an expression", node)
+
+    def visit_Num(self, node):
+        w_str = self.space.str(node.n)
+        self.append_w_str(w_str)
+
+    def visit_Str(self, node):
+        w_str = self.space.repr(node.s)
+        self.append_w_str(w_str)
+
+    def visit_Bytes(self, node):
+        w_str = self.space.repr(node.s)
+        self.append_w_str(w_str)
+
+    def visit_Name(self, node):
+        self.builder.append(node.id)
+
+    def visit_UnaryOp(self, node):
+        op = node.op
+        if op == ast.Invert:
+            priority = PRIORITY_FACTOR
+            op = "~"
+        elif op == ast.Not:
+            priority = PRIORITY_NOT
+            op = "not "
+        elif op == ast.UAdd:
+            priority = PRIORITY_FACTOR
+            op = "+"
+        elif op == ast.USub:
+            priority = PRIORITY_FACTOR
+            op = "-"
+        else:
+            raise oefmt(self.space.w_SystemError,
+                        "unknown unary operator")
+        with self.maybe_parenthesize(priority):
+            self.append_ascii(op)
+            self.append_expr(node.operand, priority)
+
+    def visit_BinOp(self, node):
+        right_associative = False
+        op = node.op
+        if op == ast.Add:
+            op = " + "
+            priority = PRIORITY_ARITH
+        elif op == ast.Sub:
+            op = " - "
+            priority = PRIORITY_ARITH
+        elif op == ast.Mult:
+            op = " * "
+            priority = PRIORITY_TERM
+        elif op == ast.MatMult:
+            op = " @ "
+            priority = PRIORITY_TERM
+        elif op == ast.Div:
+            op = " / "
+            priority = PRIORITY_TERM
+        elif op == ast.FloorDiv:
+            op = " // "
+            priority = PRIORITY_TERM
+        elif op == ast.Mod:
+            op = " % "
+            priority = PRIORITY_TERM
+        elif op == ast.LShift:
+            op = " << "
+            priority = PRIORITY_SHIFT
+        elif op == ast.RShift:
+            op = " >> "
+            priority = PRIORITY_SHIFT
+        elif op == ast.BitOr:
+            op = " | "
+            priority = PRIORITY_BOR
+        elif op == ast.BitXor:
+            op = " ^ "
+            priority = PRIORITY_BXOR
+        elif op == ast.BitAnd:
+            op = " & "
+            priority = PRIORITY_BAND
+        elif op == ast.Pow:
+            op = " ** "
+            priority = PRIORITY_POWER
+            right_associative = True
+        else:
+            raise oefmt(self.space.w_SystemError,
+                        "unknown unary operator")
+        with self.maybe_parenthesize(priority):
+            self.append_expr(node.left, priority + right_associative)
+            self.append_ascii(op)
+            self.append_expr(node.right, priority + (not right_associative))
+            
+    def visit_BoolOp(self, node):
+        if node.op == ast.And:
+            op = " and "
+            priority = PRIORITY_AND
+        else:
+            op = " or "
+            priority = PRIORITY_OR
+        with self.maybe_parenthesize(priority):
+            for i, value in enumerate(node.values):
+                if i > 0:
+                    self.append_ascii(op)
+                self.append_expr(value, priority + 1)
+
+    def visit_Compare(self, node):
+        with self.maybe_parenthesize(PRIORITY_CMP):
+            self.append_expr(node.left, PRIORITY_CMP + 1)
+            for i in range(len(node.comparators)):
+                op = node.ops[i]
+                value = node.comparators[i]
+                if op == ast.Eq:
+                    op = " == "
+                elif op == ast.NotEq:
+                    op = " != "
+                elif op == ast.Lt:
+                    op = " < "
+                elif op == ast.LtE:
+                    op = " <= "
+                elif op == ast.Gt:
+                    op = " > "
+                elif op == ast.GtE:
+                    op = " >= "
+                elif op == ast.Is:
+                    op = " is "
+                elif op == ast.IsNot:
+                    op = " is not "
+                elif op == ast.In:
+                    op = " in "
+                elif op == ast.NotIn:
+                    op = " not in "
+                else:
+                    raise oefmt(self.space.w_SystemError,
+                                "unknown comparator")
+                self.append_ascii(op)
+                self.append_expr(value, PRIORITY_CMP + 1)
+
+
+    def visit_IfExp(self, node):
+        with self.maybe_parenthesize(PRIORITY_TEST):
+            self.append_expr(node.body, PRIORITY_TEST + 1)
+            self.append_ascii(" if ")
+            self.append_expr(node.test, PRIORITY_TEST + 1)
+            self.append_ascii(" else ")
+            self.append_expr(node.orelse, PRIORITY_TEST + 1)
+
+
+
+def unparse(space, ast):
+    visitor = UnparseVisitor(space)
+    ast.walkabout(visitor)
+    return visitor.builder.build()


More information about the pypy-commit mailing list