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