(root)/
Python-3.12.0/
Tools/
peg_generator/
scripts/
grammar_grapher.py
       1  #!/usr/bin/env python3.8
       2  
       3  """ Convert a grammar into a dot-file suitable for use with GraphViz
       4  
       5      For example:
       6          Generate the GraphViz file:
       7          # scripts/grammar_grapher.py data/python.gram > python.gv
       8  
       9          Then generate the graph...
      10  
      11          # twopi python.gv -Tpng > python_twopi.png
      12  
      13          or
      14  
      15          # dot python.gv -Tpng > python_dot.png
      16  
      17          NOTE: The _dot_ and _twopi_ tools seem to produce the most useful results.
      18                The _circo_ tool is the worst of the bunch. Don't even bother.
      19  """
      20  
      21  import argparse
      22  import sys
      23  
      24  from typing import Any, List
      25  
      26  sys.path.insert(0, ".")
      27  
      28  from pegen.build import build_parser
      29  from pegen.grammar import (
      30      Alt,
      31      Cut,
      32      Forced,
      33      Group,
      34      Leaf,
      35      Lookahead,
      36      Rule,
      37      NameLeaf,
      38      NamedItem,
      39      Opt,
      40      Repeat,
      41      Rhs,
      42  )
      43  
      44  argparser = argparse.ArgumentParser(
      45      prog="graph_grammar",
      46      description="Graph a grammar tree",
      47  )
      48  argparser.add_argument(
      49      "-s",
      50      "--start",
      51      choices=["exec", "eval", "single"],
      52      default="exec",
      53      help="Choose the grammar's start rule (exec, eval or single)",
      54  )
      55  argparser.add_argument("grammar_file", help="The grammar file to graph")
      56  
      57  
      58  def references_for_item(item: Any) -> List[Any]:
      59      if isinstance(item, Alt):
      60          return [_ref for _item in item.items for _ref in references_for_item(_item)]
      61      elif isinstance(item, Cut):
      62          return []
      63      elif isinstance(item, Forced):
      64          return references_for_item(item.node)
      65      elif isinstance(item, Group):
      66          return references_for_item(item.rhs)
      67      elif isinstance(item, Lookahead):
      68          return references_for_item(item.node)
      69      elif isinstance(item, NamedItem):
      70          return references_for_item(item.item)
      71  
      72      # NOTE NameLeaf must be before Leaf
      73      elif isinstance(item, NameLeaf):
      74          if item.value == "ENDMARKER":
      75              return []
      76          return [item.value]
      77      elif isinstance(item, Leaf):
      78          return []
      79  
      80      elif isinstance(item, Opt):
      81          return references_for_item(item.node)
      82      elif isinstance(item, Repeat):
      83          return references_for_item(item.node)
      84      elif isinstance(item, Rhs):
      85          return [_ref for alt in item.alts for _ref in references_for_item(alt)]
      86      elif isinstance(item, Rule):
      87          return references_for_item(item.rhs)
      88      else:
      89          raise RuntimeError(f"Unknown item: {type(item)}")
      90  
      91  
      92  def main() -> None:
      93      args = argparser.parse_args()
      94  
      95      try:
      96          grammar, parser, tokenizer = build_parser(args.grammar_file)
      97      except Exception as err:
      98          print("ERROR: Failed to parse grammar file", file=sys.stderr)
      99          sys.exit(1)
     100  
     101      references = {}
     102      for name, rule in grammar.rules.items():
     103          references[name] = set(references_for_item(rule))
     104  
     105      # Flatten the start node if has only a single reference
     106      root_node = {"exec": "file", "eval": "eval", "single": "interactive"}[args.start]
     107  
     108      print("digraph g1 {")
     109      print('\toverlap="scale";')  # Force twopi to scale the graph to avoid overlaps
     110      print(f'\troot="{root_node}";')
     111      print(f"\t{root_node} [color=green, shape=circle];")
     112      for name, refs in references.items():
     113          for ref in refs:
     114              print(f"\t{name} -> {ref};")
     115      print("}")
     116  
     117  
     118  if __name__ == "__main__":
     119      main()