mirror of
				https://github.com/python/cpython.git
				synced 2025-11-04 07:31:38 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			114 lines
		
	
	
	
		
			3.1 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			114 lines
		
	
	
	
		
			3.1 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
#!/usr/bin/env python3.8
 | 
						|
 | 
						|
""" Convert a grammar into a dot-file suitable for use with GraphViz
 | 
						|
 | 
						|
    For example:
 | 
						|
        Generate the GraphViz file:
 | 
						|
        # scripts/grammar_grapher.py data/python.gram > python.gv
 | 
						|
 | 
						|
        Then generate the graph...
 | 
						|
 | 
						|
        # twopi python.gv -Tpng > python_twopi.png
 | 
						|
 | 
						|
        or
 | 
						|
 | 
						|
        # dot python.gv -Tpng > python_dot.png
 | 
						|
 | 
						|
        NOTE: The _dot_ and _twopi_ tools seem to produce the most useful results.
 | 
						|
              The _circo_ tool is the worst of the bunch. Don't even bother.
 | 
						|
"""
 | 
						|
 | 
						|
import argparse
 | 
						|
import sys
 | 
						|
 | 
						|
from typing import Any, List
 | 
						|
 | 
						|
sys.path.insert(0, ".")
 | 
						|
 | 
						|
from pegen.build import build_parser
 | 
						|
from pegen.grammar import (
 | 
						|
    Alt,
 | 
						|
    Cut,
 | 
						|
    Grammar,
 | 
						|
    Group,
 | 
						|
    Leaf,
 | 
						|
    Lookahead,
 | 
						|
    Rule,
 | 
						|
    NameLeaf,
 | 
						|
    NamedItem,
 | 
						|
    Opt,
 | 
						|
    Repeat,
 | 
						|
    Rhs,
 | 
						|
)
 | 
						|
 | 
						|
argparser = argparse.ArgumentParser(prog="graph_grammar", description="Graph a grammar tree",)
 | 
						|
argparser.add_argument(
 | 
						|
    "-s",
 | 
						|
    "--start",
 | 
						|
    choices=["exec", "eval", "single"],
 | 
						|
    default="exec",
 | 
						|
    help="Choose the grammar's start rule (exec, eval or single)",
 | 
						|
)
 | 
						|
argparser.add_argument("grammar_file", help="The grammar file to graph")
 | 
						|
 | 
						|
 | 
						|
def references_for_item(item: Any) -> List[Any]:
 | 
						|
    if isinstance(item, Alt):
 | 
						|
        return [_ref for _item in item.items for _ref in references_for_item(_item)]
 | 
						|
    elif isinstance(item, Cut):
 | 
						|
        return []
 | 
						|
    elif isinstance(item, Group):
 | 
						|
        return references_for_item(item.rhs)
 | 
						|
    elif isinstance(item, Lookahead):
 | 
						|
        return references_for_item(item.node)
 | 
						|
    elif isinstance(item, NamedItem):
 | 
						|
        return references_for_item(item.item)
 | 
						|
 | 
						|
    # NOTE NameLeaf must be before Leaf
 | 
						|
    elif isinstance(item, NameLeaf):
 | 
						|
        if item.value == "ENDMARKER":
 | 
						|
            return []
 | 
						|
        return [item.value]
 | 
						|
    elif isinstance(item, Leaf):
 | 
						|
        return []
 | 
						|
 | 
						|
    elif isinstance(item, Opt):
 | 
						|
        return references_for_item(item.node)
 | 
						|
    elif isinstance(item, Repeat):
 | 
						|
        return references_for_item(item.node)
 | 
						|
    elif isinstance(item, Rhs):
 | 
						|
        return [_ref for alt in item.alts for _ref in references_for_item(alt)]
 | 
						|
    elif isinstance(item, Rule):
 | 
						|
        return references_for_item(item.rhs)
 | 
						|
    else:
 | 
						|
        raise RuntimeError(f"Unknown item: {type(item)}")
 | 
						|
 | 
						|
 | 
						|
def main() -> None:
 | 
						|
    args = argparser.parse_args()
 | 
						|
 | 
						|
    try:
 | 
						|
        grammar, parser, tokenizer = build_parser(args.grammar_file)
 | 
						|
    except Exception as err:
 | 
						|
        print("ERROR: Failed to parse grammar file", file=sys.stderr)
 | 
						|
        sys.exit(1)
 | 
						|
 | 
						|
    references = {}
 | 
						|
    for name, rule in grammar.rules.items():
 | 
						|
        references[name] = set(references_for_item(rule))
 | 
						|
 | 
						|
    # Flatten the start node if has only a single reference
 | 
						|
    root_node = {"exec": "file", "eval": "eval", "single": "interactive"}[args.start]
 | 
						|
 | 
						|
    print("digraph g1 {")
 | 
						|
    print('\toverlap="scale";')  # Force twopi to scale the graph to avoid overlaps
 | 
						|
    print(f'\troot="{root_node}";')
 | 
						|
    print(f"\t{root_node} [color=green, shape=circle];")
 | 
						|
    for name, refs in references.items():
 | 
						|
        for ref in refs:
 | 
						|
            print(f"\t{name} -> {ref};")
 | 
						|
    print("}")
 | 
						|
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    main()
 |