mirror of
				https://github.com/python/cpython.git
				synced 2025-10-30 21:21:22 +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()
 | 
