1#!/usr/bin/ruby
2# encoding: utf-8
3
4=begin LICENSE
5
6[The "BSD licence"]
7Copyright (c) 2009-2010 Kyle Yetter
8All rights reserved.
9
10Redistribution and use in source and binary forms, with or without
11modification, are permitted provided that the following conditions
12are met:
13
14 1. Redistributions of source code must retain the above copyright
15    notice, this list of conditions and the following disclaimer.
16 2. Redistributions in binary form must reproduce the above copyright
17    notice, this list of conditions and the following disclaimer in the
18    documentation and/or other materials provided with the distribution.
19 3. The name of the author may not be used to endorse or promote products
20    derived from this software without specific prior written permission.
21
22THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
23IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
26INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
27NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
31THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
33=end
34
35require 'antlr3'
36require 'erb'
37
38module ANTLR3
39
40=begin rdoc ANTLR3::DOT
41
42An extra utility for generating graphviz DOT file representations of ANTLR
43Abstract Syntax Tree nodes.
44
45This module has been directly ported to Ruby from the ANTLR Python runtime
46library.
47
48=end
49
50module DOT
51  class Context
52    def []=( var, value )
53      instance_variable_set( :"@#{ var }", value )
54    end
55    def []( var )
56      instance_variable_get( :"@#{ var }" )
57    end
58    
59    def initialize( template, vars = {} )
60      @__template__ = template
61      vars.each do |var, value|
62        self[ var ] = value
63      end
64    end
65    
66    def to_s
67      @__template__.result( binding )
68    end
69  end
70  class TreeGenerator
71    TREE_TEMPLATE = ERB.new( Util.tidy( <<-END ) )
72    | digraph {
73    |   ordering=out;
74    |   ranksep=.4;
75    |   node [shape=plaintext, fixedsize=true, fontsize=11, fontname="Courier",
76    |         width=.25, height=.25];
77    |   edge [arrowsize=.5];
78    |   <%= @nodes.join("\n  ") %>
79    |   <%= @edges.join("\n  ") %>
80    | }
81    END
82    
83    NODE_TEMPLATE = ERB.new( Util.tidy( <<-END ) )
84    | <%= @name %> [label="<%= @text %>"];
85    END
86    
87    EDGE_TEMPLATE = ERB.new( Util.tidy( <<-END ) )
88    | <%= @parent %> -> <%= @child %>; // "<%= @parent_text %>" -> "<%= @child_text %>"
89    END
90    
91    def self.generate( tree, adaptor = nil, tree_template = TREE_TEMPLATE,
92                      edge_template = EDGE_TEMPLATE )
93      new.to_dot( tree, adaptor, tree_template, edge_template )
94    end
95    
96    def initialize
97      @node_number = 0
98      @node_to_number_map = Hash.new do |map, node|
99        map[ node ] = @node_number
100        @node_number += 1
101        @node_number - 1
102      end
103    end
104    
105    def to_dot( tree, adaptor = nil, tree_template = TREE_TEMPLATE,
106               edge_template = EDGE_TEMPLATE )
107      adaptor ||= AST::CommonTreeAdaptor.new
108      @node_number = 0
109      tree_template = Context.new( tree_template, :nodes => [], :edges => [] )
110      define_nodes( tree, adaptor, tree_template )
111      
112      @node_number = 0
113      define_edges( tree, adaptor, tree_template, edge_template )
114      return tree_template.to_s
115    end
116    
117    def define_nodes( tree, adaptor, tree_template, known_nodes = nil )
118      known_nodes ||= Set.new
119      tree.nil? and return
120      n = adaptor.child_count( tree )
121      n == 0 and return
122      number = node_number( tree )
123      unless known_nodes.include?( number )
124        parent_node_template = node_template_for( adaptor, child )
125        tree_template[ :nodes ] << parent_node_template
126        known_nodes.add( number )
127      end
128      
129      n.times do |index|
130        child = adaptor.child_of( tree, index )
131        number = @node_to_number_map[ child ]
132        unless known_nodes.include?( number )
133          node_template = node_template_for( adaptor, child )
134          tree_template[ :nodes ] << node_template
135          known_nodes.add( number )
136        end
137        
138        define_nodes( child, adaptor, tree_template, edge_template )
139      end
140    end
141    
142    def define_edges( tree, adaptor, tree_template, edge_template )
143      tree.nil? or return
144      
145      n = adaptor.child_count( tree )
146      n == 0 and return
147      
148      parent_name = 'n%i' % @node_to_number_map[ tree ]
149      parent_text = adaptor.text_of( tree )
150      n.times do |index|
151        child = adaptor.child_of( tree, index )
152        child_text = adaptor.text_of( child )
153        child_name = 'n%i' % @node_to_number_map[ tree ]
154        edge_template = Context.new( edge_template,
155          :parent => parent_name, :child => child_name,
156          :parent_text => parent_text, :child_text => child_text
157        )
158        tree_template[ :edges ] << edge_template
159        define_edges( child, adaptor, tree_template, edge_template )
160      end
161    end
162    
163    def node_template_for( adaptor, tree )
164      text = adaptor.text_of( tree )
165      node_template = Context.new( NODE_TEMPLATE )
166      unique_name = 'n%i' % @node_to_number_map[ tree ]
167      node_template[ :name ] = unique_name
168      text and text = text.gsub( /"/, '\\"' )
169      node_template[ :text ] = text
170      return node_template
171    end
172  end
173end
174end
175