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