cpython/Tools/peg_generator/pegen/grammar_visualizer.py

import argparse
import sys
from typing import Any, Callable, Iterator

from pegen.build import build_parser
from pegen.grammar import Grammar, Rule

argparser = argparse.ArgumentParser(
    prog="pegen", description="Pretty print the AST for a given PEG grammar"
)
argparser.add_argument("filename", help="Grammar description")


class ASTGrammarPrinter:
    def children(self, node: Rule) -> Iterator[Any]:
        for value in node:
            if isinstance(value, list):
                yield from value
            else:
                yield value

    def name(self, node: Rule) -> str:
        if not list(self.children(node)):
            return repr(node)
        return node.__class__.__name__

    def print_grammar_ast(self, grammar: Grammar, printer: Callable[..., None] = print) -> None:
        for rule in grammar.rules.values():
            printer(self.print_nodes_recursively(rule))

    def print_nodes_recursively(self, node: Rule, prefix: str = "", istail: bool = True) -> str:
        children = list(self.children(node))
        value = self.name(node)

        line = prefix + ("└──" if istail else "├──") + value + "\n"
        sufix = "   " if istail else "│  "

        if not children:
            return line

        *children, last = children
        for child in children:
            line += self.print_nodes_recursively(child, prefix + sufix, False)
        line += self.print_nodes_recursively(last, prefix + sufix, True)

        return line


def main() -> None:
    args = argparser.parse_args()

    try:
        grammar, parser, tokenizer = build_parser(args.filename)
    except Exception as err:
        print("ERROR: Failed to parse grammar file", file=sys.stderr)
        sys.exit(1)

    visitor = ASTGrammarPrinter()
    visitor.print_grammar_ast(grammar)


if __name__ == "__main__":
    main()