class Gullah::Dotifier

A little tool to help visualize a parse tree. It generates .dot files parsable by graphviz. If you have graphviz installed, you may be able to invoke it like so and generate a .png file

Gullah::Dotifier.dot parses.first, "tree", make_it: :so

This will generate a file called tree.png showing the parse tree. If you don't have graphviz, or perhaps if you're on a machine which doesn't like the command this generates – I suspect Windows doesn't – you can skip the named argument and just generate the dot file which you can feed into graphviz some other way.

I make no guarantees about this utility. You may want to build your own, in which case this can serve as a simple prototype.

Public Class Methods

dot(parse, file, make_it: false, type: 'png') click to toggle source

Receives a parse and a file name and generates a graph specification readable by graphviz. The specification is written to a file with the specified file name. If make_it is truthy, the dot command will also be invoked and the graph image generated. By default this will be a png, though the type is specifiable via the type named argument.

# File lib/gullah/dotifier.rb, line 25
def self.dot(parse, file, make_it: false, type: 'png')
  new.send :dot, parse, file, make_it, type
end

Private Instance Methods

add_edge(parent, child, property, value) click to toggle source
# File lib/gullah/dotifier.rb, line 79
def add_edge(parent, child, property, value)
  while parent != child
    middle = parent.children.find { |c| c.contains? child.start }
    (@edges[[parent.position, middle.position]] ||= {})[property] = value
    parent = middle
  end
end
dot(parse, file, make_it, type) click to toggle source

making the guts private to simplify the API

# File lib/gullah/dotifier.rb, line 33
def dot(parse, file, make_it, type)
  @edges = {}
  File.open file, 'w' do |f|
    f.puts 'graph {'
    f.puts "\tnode[shape=none]"
    f.puts
    parse.roots.each do |root|
      tree(root, f)
    end
    # put all the leaves in a row at the bottom
    f.puts
    f.puts "\tsubgraph {"
    f.puts "\t\trank=\"same\""
    parse.roots.flat_map(&:leaves).reject(&:ignorable?).each do |leaf|
      f.puts "\t\t#{leaf_name(leaf)}"
    end
    f.puts "\t}"
    f.puts '}'
  end
  `dot -T#{type} -o#{file}.#{type} #{file}` if make_it
end
edge_attributes(node, child) click to toggle source
# File lib/gullah/dotifier.rb, line 87
def edge_attributes(node, child)
  atts = []
  if (properties = @edges[[node.position, child.position]])
    if properties[:error]
      atts << 'color=red'
    elsif properties[:success]
      atts << 'color=green'
    end
  end
  " [#{atts.join(';')}]" if atts.any?
end
leaf_name(node) click to toggle source
# File lib/gullah/dotifier.rb, line 122
def leaf_name(node)
  offset, height = node.position
  "l_#{offset}_#{height}"
end
name(node) click to toggle source
# File lib/gullah/dotifier.rb, line 117
def name(node)
  offset, height = node.position
  "n_#{offset}_#{height}"
end
node_attributes(node) click to toggle source
# File lib/gullah/dotifier.rb, line 99
def node_attributes(node)
  atts = ["label=#{node.trash? ? 'trash' : node.name.to_s.inspect}"]
  if node.trash?
    atts << 'color=red'
    atts << 'shape=box'
  elsif node.boundary?
    atts << 'color=blue'
    atts << 'shape=box'
  elsif node.error?
    atts << 'color=red'
    atts << 'shape=oval'
  elsif node.atts[:satisfied_ancestor] || node.atts[:satisfied_descendant]
    atts << 'color=green'
    atts << 'shape=oval'
  end
  "[#{atts.join(';')}]"
end
tree(node, f) click to toggle source
# File lib/gullah/dotifier.rb, line 55
def tree(node, f)
  return if node.ignorable?

  nn = name(node)
  f.puts "\t#{nn} #{node_attributes(node)}"
  if node.leaf?
    ln = leaf_name(node)
    f.puts "\t#{ln} [label=#{node.text.inspect}]"
    f.puts "\t#{nn} -- #{ln}"
  end
  Array(node.atts[:satisfied_ancestor]).each do |_, loc, *|
    child = node.find loc
    add_edge node, child, :success, true
  end
  Array(node.atts[:failed_ancestor]).each do |_, loc, *|
    child = node.find loc
    add_edge node, child, :error, true
  end
  Array(node.children&.reject(&:ignorable?)).each do |child|
    f.puts "\t#{nn} -- #{name(child)}#{edge_attributes node, child}"
    tree(child, f)
  end
end