summaryrefslogtreecommitdiff
path: root/prism/templates/lib/prism/dot_visitor.rb.erb
diff options
context:
space:
mode:
Diffstat (limited to 'prism/templates/lib/prism/dot_visitor.rb.erb')
-rw-r--r--prism/templates/lib/prism/dot_visitor.rb.erb186
1 files changed, 186 insertions, 0 deletions
diff --git a/prism/templates/lib/prism/dot_visitor.rb.erb b/prism/templates/lib/prism/dot_visitor.rb.erb
new file mode 100644
index 0000000000..93c94b19ee
--- /dev/null
+++ b/prism/templates/lib/prism/dot_visitor.rb.erb
@@ -0,0 +1,186 @@
+require "cgi"
+
+module Prism
+ # This visitor provides the ability to call Node#to_dot, which converts a
+ # subtree into a graphviz dot graph.
+ class DotVisitor < Visitor
+ class Field # :nodoc:
+ attr_reader :name, :value, :port
+
+ def initialize(name, value, port)
+ @name = name
+ @value = value
+ @port = port
+ end
+
+ def to_dot
+ if port
+ "<tr><td align=\"left\" colspan=\"2\" port=\"#{name}\">#{name}</td></tr>"
+ else
+ "<tr><td align=\"left\">#{name}</td><td>#{CGI.escapeHTML(value || raise)}</td></tr>"
+ end
+ end
+ end
+
+ class Table # :nodoc:
+ attr_reader :name, :fields
+
+ def initialize(name)
+ @name = name
+ @fields = []
+ end
+
+ def field(name, value = nil, port: false)
+ fields << Field.new(name, value, port)
+ end
+
+ def to_dot
+ dot = <<~DOT
+ <table border="0" cellborder="1" cellspacing="0" cellpadding="4">
+ <tr><td colspan="2"><b>#{name}</b></td></tr>
+ DOT
+
+ if fields.any?
+ "#{dot} #{fields.map(&:to_dot).join("\n ")}\n</table>"
+ else
+ "#{dot}</table>"
+ end
+ end
+ end
+
+ class Digraph # :nodoc:
+ attr_reader :nodes, :waypoints, :edges
+
+ def initialize
+ @nodes = []
+ @waypoints = []
+ @edges = []
+ end
+
+ def node(value)
+ nodes << value
+ end
+
+ def waypoint(value)
+ waypoints << value
+ end
+
+ def edge(value)
+ edges << value
+ end
+
+ def to_dot
+ <<~DOT
+ digraph "Prism" {
+ node [
+ fontname=\"Courier New\"
+ shape=plain
+ style=filled
+ fillcolor=gray95
+ ];
+
+ #{nodes.map { |node| node.gsub(/\n/, "\n ") }.join("\n ")}
+ node [shape=point];
+ #{waypoints.join("\n ")}
+
+ #{edges.join("\n ")}
+ }
+ DOT
+ end
+ end
+
+ private_constant :Field, :Table, :Digraph
+
+ # The digraph that is being built.
+ attr_reader :digraph
+
+ # Initialize a new dot visitor.
+ def initialize
+ @digraph = Digraph.new
+ end
+
+ # Convert this visitor into a graphviz dot graph string.
+ def to_dot
+ digraph.to_dot
+ end
+ <%- nodes.each do |node| -%>
+
+ # Visit a <%= node.name %> node.
+ def visit_<%= node.human %>(node)
+ table = Table.new("<%= node.name %>")
+ id = node_id(node)
+ <%- node.fields.each do |field| -%>
+
+ # <%= field.name %>
+ <%- case field -%>
+ <%- when Prism::Template::NodeField -%>
+ table.field("<%= field.name %>", port: true)
+ digraph.edge("#{id}:<%= field.name %> -> #{node_id(node.<%= field.name %>)};")
+ <%- when Prism::Template::OptionalNodeField -%>
+ unless (<%= field.name %> = node.<%= field.name %>).nil?
+ table.field("<%= field.name %>", port: true)
+ digraph.edge("#{id}:<%= field.name %> -> #{node_id(<%= field.name %>)};")
+ end
+ <%- when Prism::Template::NodeListField -%>
+ if node.<%= field.name %>.any?
+ table.field("<%= field.name %>", port: true)
+
+ waypoint = "#{id}_<%= field.name %>"
+ digraph.waypoint("#{waypoint};")
+
+ digraph.edge("#{id}:<%= field.name %> -> #{waypoint};")
+ node.<%= field.name %>.each { |child| digraph.edge("#{waypoint} -> #{node_id(child)};") }
+ else
+ table.field("<%= field.name %>", "[]")
+ end
+ <%- when Prism::Template::StringField, Prism::Template::ConstantField, Prism::Template::OptionalConstantField, Prism::Template::UInt8Field, Prism::Template::UInt32Field, Prism::Template::ConstantListField, Prism::Template::IntegerField, Prism::Template::DoubleField -%>
+ table.field("<%= field.name %>", node.<%= field.name %>.inspect)
+ <%- when Prism::Template::LocationField -%>
+ table.field("<%= field.name %>", location_inspect(node.<%= field.name %>))
+ <%- when Prism::Template::OptionalLocationField -%>
+ unless (<%= field.name %> = node.<%= field.name %>).nil?
+ table.field("<%= field.name %>", location_inspect(<%= field.name %>))
+ end
+ <%- when Prism::Template::FlagsField -%>
+ <%- flag = flags.find { |flag| flag.name == field.kind }.tap { |flag| raise "Expected to find #{field.kind}" unless flag } -%>
+ table.field("<%= field.name %>", <%= flag.human %>_inspect(node))
+ <%- else -%>
+ <%- raise -%>
+ <%- end -%>
+ <%- end -%>
+
+ digraph.nodes << <<~DOT
+ #{id} [
+ label=<#{table.to_dot.gsub(/\n/, "\n ")}>
+ ];
+ DOT
+
+ super
+ end
+ <%- end -%>
+
+ private
+
+ # Generate a unique node ID for a node throughout the digraph.
+ def node_id(node)
+ "Node_#{node.object_id}"
+ end
+
+ # Inspect a location to display the start and end line and column numbers.
+ def location_inspect(location)
+ "(#{location.start_line},#{location.start_column})-(#{location.end_line},#{location.end_column})"
+ end
+ <%- flags.each do |flag| -%>
+
+ # Inspect a node that has <%= flag.human %> flags to display the flags as a
+ # comma-separated list.
+ def <%= flag.human %>_inspect(node)
+ flags = [] #: Array[String]
+ <%- flag.values.each do |value| -%>
+ flags << "<%= value.name.downcase %>" if node.<%= value.name.downcase %>?
+ <%- end -%>
+ flags.join(", ")
+ end
+ <%- end -%>
+ end
+end