text: ###############################################################################
  #  AST visualizer - generates a DOT file for Graphviz.                        #
  #                                                                             #
  #  To generate an image from the DOT file run $ dot -Tpng -o ast.png ast.dot  #
  #                                                                             #
  ###############################################################################
  import argparse
  import textwrap
  
  from spi import Lexer, Parser, NodeVisitor
  
  
  class ASTVisualizer(NodeVisitor):
      def __init__(self, parser):
          self.parser = parser
          self.ncount = 1
          self.dot_header = [textwrap.dedent("""\
          digraph astgraph {
            node [shape=circle, fontsize=12, fontname="Courier", height=.1];
            ranksep=.3;
            edge [arrowsize=.5]
  
          """)]
          self.dot_body = []
          self.dot_footer = ['}']
  
      def visit_Program(self, node):
          s = '  node{} [label="Program"]\n'.format(self.ncount)
          self.dot_body.append(s)
          node._num = self.ncount
          self.ncount += 1
  
          self.visit(node.block)
  
          s = '  node{} -> node{}\n'.format(node._num, node.block._num)
          self.dot_body.append(s)
  
      def visit_Block(self, node):
          s = '  node{} [label="Block"]\n'.format(self.ncount)
          self.dot_body.append(s)
          node._num = self.ncount
          self.ncount += 1
  
          for declaration in node.declarations:
              self.visit(declaration)
          self.visit(node.compound_statement)
  
          for decl_node in node.declarations:
              s = '  node{} -> node{}\n'.format(node._num, decl_node._num)
              self.dot_body.append(s)
  
          s = '  node{} -> node{}\n'.format(
              node._num,
              node.compound_statement._num
          )
          self.dot_body.append(s)
  
      def visit_VarDecl(self, node):
          s = '  node{} [label="VarDecl"]\n'.format(self.ncount)
          self.dot_body.append(s)
          node._num = self.ncount
          self.ncount += 1
  
          self.visit(node.var_node)
          s = '  node{} -> node{}\n'.format(node._num, node.var_node._num)
          self.dot_body.append(s)
  
          self.visit(node.type_node)
          s = '  node{} -> node{}\n'.format(node._num, node.type_node._num)
          self.dot_body.append(s)
  
      def visit_ProcedureDecl(self, node):
          s = '  node{} [label="ProcDecl:{}"]\n'.format(
              self.ncount,
              node.proc_name
          )
          self.dot_body.append(s)
          node._num = self.ncount
          self.ncount += 1
  
          for param_node in node.params:
              self.visit(param_node)
              s = '  node{} -> node{}\n'.format(node._num, param_node._num)
              self.dot_body.append(s)
  
          self.visit(node.block_node)
          s = '  node{} -> node{}\n'.format(node._num, node.block_node._num)
          self.dot_body.append(s)
  
      def visit_Param(self, node):
          s = '  node{} [label="Param"]\n'.format(self.ncount)
          self.dot_body.append(s)
          node._num = self.ncount
          self.ncount += 1
  
          self.visit(node.var_node)
          s = '  node{} -> node{}\n'.format(node._num, node.var_node._num)
          self.dot_body.append(s)
  
          self.visit(node.type_node)
          s = '  node{} -> node{}\n'.format(node._num, node.type_node._num)
          self.dot_body.append(s)
  
  
      def visit_Type(self, node):
          s = '  node{} [label="{}"]\n'.format(self.ncount, node.token.value)
          self.dot_body.append(s)
          node._num = self.ncount
          self.ncount += 1
  
      def visit_Num(self, node):
          s = '  node{} [label="{}"]\n'.format(self.ncount, node.token.value)
          self.dot_body.append(s)
          node._num = self.ncount
          self.ncount += 1
  
      def visit_BinOp(self, node):
          s = '  node{} [label="{}"]\n'.format(self.ncount, node.op.value)
          self.dot_body.append(s)
          node._num = self.ncount
          self.ncount += 1
  
          self.visit(node.left)
          self.visit(node.right)
  
          for child_node in (node.left, node.right):
              s = '  node{} -> node{}\n'.format(node._num, child_node._num)
              self.dot_body.append(s)
  
      def visit_UnaryOp(self, node):
          s = '  node{} [label="unary {}"]\n'.format(self.ncount, node.op.value)
          self.dot_body.append(s)
          node._num = self.ncount
          self.ncount += 1
  
          self.visit(node.expr)
          s = '  node{} -> node{}\n'.format(node._num, node.expr._num)
          self.dot_body.append(s)
  
      def visit_Compound(self, node):
          s = '  node{} [label="Compound"]\n'.format(self.ncount)
          self.dot_body.append(s)
          node._num = self.ncount
          self.ncount += 1
  
          for child in node.children:
              self.visit(child)
              s = '  node{} -> node{}\n'.format(node._num, child._num)
              self.dot_body.append(s)
  
      def visit_Assign(self, node):
          s = '  node{} [label="{}"]\n'.format(self.ncount, node.op.value)
          self.dot_body.append(s)
          node._num = self.ncount
          self.ncount += 1
  
          self.visit(node.left)
          self.visit(node.right)
  
          for child_node in (node.left, node.right):
              s = '  node{} -> node{}\n'.format(node._num, child_node._num)
              self.dot_body.append(s)
  
      def visit_Var(self, node):
          s = '  node{} [label="{}"]\n'.format(self.ncount, node.value)
          self.dot_body.append(s)
          node._num = self.ncount
          self.ncount += 1
  
      def visit_NoOp(self, node):
          s = '  node{} [label="NoOp"]\n'.format(self.ncount)
          self.dot_body.append(s)
          node._num = self.ncount
          self.ncount += 1
  
      def gendot(self):
          tree = self.parser.parse()
          self.visit(tree)
          return ''.join(self.dot_header + self.dot_body + self.dot_footer)
  
  
  def main():
      argparser = argparse.ArgumentParser(
          description='Generate an AST DOT file.'
      )
      argparser.add_argument(
          'fname',
          help='Pascal source file'
      )
      args = argparser.parse_args()
      fname = args.fname
      text = open(fname, 'r').read()
  
      lexer = Lexer(text)
      parser = Parser(lexer)
      viz = ASTVisualizer(parser)
      content = viz.gendot()
      print(content)
  
  
  if __name__ == '__main__':
      main()
  