97 lines
1.9 KiB
Ruby
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 |