rubyx/lib/parslet/graphviz.rb

97 lines
1.9 KiB
Ruby

# Paints a graphviz graph of your parser.
begin
require 'ruby-graphviz'
rescue LoadError
puts "Please install the 'ruby-graphviz' gem first."
fail
end
require 'set'
require 'parslet/atoms/visitor'
module Parslet
class GraphvizVisitor
def initialize g
@graph = g
@known_links = Set.new
@visited = Set.new
end
attr_reader :parent
def visit_parser(root)
recurse root, node('parser')
end
def visit_entity(name, block)
s = node(name)
downwards s
return if @visited.include?(name)
@visited << name
recurse block.call, s
end
def visit_named(name, atom)
recurse atom, parent
end
def visit_repetition(tag, min, max, atom)
recurse atom, parent
end
def visit_alternative(alternatives)
p = parent
alternatives.each do |atom|
recurse atom, p
end
end
def visit_sequence(sequence)
p = parent
sequence.each do |atom|
recurse atom, p
end
end
def visit_lookahead(positive, atom)
recurse atom, parent
end
def visit_re(regexp)
# downwards node(regexp.object_id, label: escape("re(#{regexp.inspect})"))
end
def visit_str(str)
# downwards node(str.object_id, label: escape("#{str.inspect}"))
end
def escape str
str.gsub('"', "'")
end
def node name, opts={}
@graph.add_nodes name.to_s, opts
end
def downwards child
if @parent && !@known_links.include?([@parent, child])
@graph.add_edges(@parent, child)
@known_links << [@parent, child]
end
end
def recurse node, current
@parent = current
node.accept(self)
end
end
module Graphable
def graph opts
g = GraphViz.new(:G, type: :digraph)
visitor = GraphvizVisitor.new(g)
new.accept(visitor)
g.output opts
end
end
class Parser # reopen for introducing the .graph method
extend Graphable
end
end