diff --git a/lib/awkward/visitor.rb b/lib/awkward/visitor.rb index 5c798ca..6bb77b7 100644 --- a/lib/awkward/visitor.rb +++ b/lib/awkward/visitor.rb @@ -1,41 +1,74 @@ module Awkward class Visitor - Node = Struct.new :object + class Node < Struct.new :object # :nodoc: + def name + object.class.name + end + + private + def escape string + string.gsub '"', '\"' + end + end + Edge = Struct.new :name, :left, :right - attr_reader :nodes attr_reader :edges def initialize @nodes = [] + @stack = [] @edges = [] @callstack = [] @seen = {} end + def nodes + @seen.values + end + def accept o - return cycle(o) if @seen[o.object_id] + return connect(@stack.last, @seen[o.object_id]) if @seen.key? o.object_id node = Node.new o @seen[o.object_id] = node - @nodes.push node + connect(@stack.last, node) unless @callstack.empty? - cycle o + @stack.push node send "visit_#{o.class.name.gsub('::', '_')}", o + + @stack.pop end def to_dot + dot = <<-eodot +digraph "Graph" { +node [width=0.375,height=0.25,shape=box]; + eodot + + nodes.each do |node| + dot.concat <<-eonode + #{node.object_id} [label="#{node.name}"]; + eonode + end + + edges.each do |edge| + dot.concat <<-eoedge + #{edge.left.object_id} -> #{edge.right.object_id} [label="#{edge.name}"]; + eoedge + end + dot + "}" end private def visit_Hash o - o.each do |k,v| - edge(:key) { accept k } - edge(:value) { accept v } + o.each_with_index do |(k,v),i| + edge("key: #{i}") { accept k } + edge("value: #{i}") { accept v } end end @@ -55,14 +88,8 @@ def edge sym @callstack.pop end - def escape string - string.gsub '"', '\"' - end - - def cycle o - if last = @nodes.last && @callstack.last - @edges << Edge.new(@callstack.last, last.object_id, o.object_id) - end + def connect from, to + @edges << Edge.new(@callstack.last, from, to) end end end diff --git a/test/test_awkward.rb b/test/test_awkward.rb index 8704d52..d8314ad 100644 --- a/test/test_awkward.rb +++ b/test/test_awkward.rb @@ -19,4 +19,27 @@ def test_edges awkward.accept @tree assert_equal 7, awkward.edges.length end + + def test_edges_have_nodes + awkward = Awkward::Visitor.new + awkward.accept @tree + nodes = awkward.edges.map { |e| [e.left, e.right] }.flatten.uniq + expected = awkward.nodes + assert_equal expected.sort_by(&:object_id), nodes.sort_by(&:object_id) + end + + def test_to_dot + awkward = Awkward::Visitor.new + awkward.accept @tree + awkward.to_dot + end + + def test_cyclic + foo = {} + foo[:a] = { :b => { :c => foo } } + + awkward = Awkward::Visitor.new + awkward.accept foo + awkward.to_dot + end end