• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3.8
2
3"""Show the parse tree for a given program, nicely formatted.
4
5Example:
6
7$ scripts/show_parse.py a+b
8Module(
9    body=[
10        Expr(
11            value=BinOp(
12                left=Name(id="a", ctx=Load()), op=Add(), right=Name(id="b", ctx=Load())
13            )
14        )
15    ],
16    type_ignores=[],
17)
18$
19
20Use -v to show line numbers and column offsets.
21
22The formatting is done using black.  You can also import this module
23and call one of its functions.
24"""
25
26import argparse
27import ast
28import difflib
29import os
30import sys
31import tempfile
32
33import _peg_parser
34
35from typing import List
36
37sys.path.insert(0, os.getcwd())
38from pegen.ast_dump import ast_dump
39
40parser = argparse.ArgumentParser()
41parser.add_argument(
42    "-d", "--diff", action="store_true", help="show diff between grammar and ast (requires -g)"
43)
44parser.add_argument(
45    "-p",
46    "--parser",
47    choices=["new", "old"],
48    default="new",
49    help="choose the parser to use"
50)
51parser.add_argument(
52    "-m",
53    "--multiline",
54    action="store_true",
55    help="concatenate program arguments using newline instead of space",
56)
57parser.add_argument("-v", "--verbose", action="store_true", help="show line/column numbers")
58parser.add_argument("program", nargs="+", help="program to parse (will be concatenated)")
59
60
61def format_tree(tree: ast.AST, verbose: bool = False) -> str:
62    with tempfile.NamedTemporaryFile("w+") as tf:
63        tf.write(ast_dump(tree, include_attributes=verbose))
64        tf.write("\n")
65        tf.flush()
66        cmd = f"black -q {tf.name}"
67        sts = os.system(cmd)
68        if sts:
69            raise RuntimeError(f"Command {cmd!r} failed with status 0x{sts:x}")
70        tf.seek(0)
71        return tf.read()
72
73
74def diff_trees(a: ast.AST, b: ast.AST, verbose: bool = False) -> List[str]:
75    sa = format_tree(a, verbose)
76    sb = format_tree(b, verbose)
77    la = sa.splitlines()
78    lb = sb.splitlines()
79    return list(difflib.unified_diff(la, lb, "a", "b", lineterm=""))
80
81
82def show_parse(source: str, verbose: bool = False) -> str:
83    tree = _peg_parser.parse_string(source, oldparser=True)
84    return format_tree(tree, verbose).rstrip("\n")
85
86
87def print_parse(source: str, verbose: bool = False) -> None:
88    print(show_parse(source, verbose))
89
90
91def main() -> None:
92    args = parser.parse_args()
93    new_parser = args.parser == "new"
94    if args.multiline:
95        sep = "\n"
96    else:
97        sep = " "
98    program = sep.join(args.program)
99    if new_parser:
100        tree = _peg_parser.parse_string(program)
101
102        if args.diff:
103            a = _peg_parser.parse_string(program, oldparser=True)
104            b = tree
105            diff = diff_trees(a, b, args.verbose)
106            if diff:
107                for line in diff:
108                    print(line)
109            else:
110                print("# Trees are the same")
111        else:
112            print("# Parsed using the new parser")
113            print(format_tree(tree, args.verbose))
114    else:
115        tree = _peg_parser.parse_string(program, oldparser=True)
116        print("# Parsed using the old parser")
117        print(format_tree(tree, args.verbose))
118
119
120if __name__ == "__main__":
121    main()
122