Skip to content
Browse files

ast -> nfa started

  • Loading branch information...
1 parent 3e46edc commit f5323ca870217a0e5d6ec4ea92da00df53cfbe0f @tenderlove tenderlove committed Sep 24, 2011
Showing with 193 additions and 1 deletion.
  1. +2 −0 lib/journey.rb
  2. +152 −0 lib/journey/nfa/builder.rb
  3. +1 −1 test/helper.rb
  4. +38 −0 test/nfa/test_transition_table.rb
View
2 lib/journey.rb
@@ -0,0 +1,2 @@
+require 'journey/router'
+require 'journey/nfa/builder'
View
152 lib/journey/nfa/builder.rb
@@ -0,0 +1,152 @@
+# encoding: utf-8
+module Journey
+ module NFA
+ class TransitionTable
+ def initialize
+ @table = Hash.new { |h,f| h[f] = {} }
+ @accepting = Hash.new(false)
+ @inverted = nil
+ end
+
+ def add_accepting s
+ @accepting[s] = true
+ end
+
+ def remove_accepting s
+ @accepting.delete s
+ end
+
+ def []= i, f, s
+ @table[f][i] = s
+ end
+
+ def merge left, right
+ @table[right] = @table.delete(left)
+ end
+
+ def edges idx
+ inverted[idx]
+ end
+
+ def eclosure idx
+ Array(idx).map { |i|
+ inverted[i].reject { |sym,_| sym }.map { |_,s|
+ [s] + eclosure(s)
+ }
+ }.flatten
+ end
+
+ def to_dot
+ edges = @table.map { |to, hash|
+ hash.map { |from, sym|
+ "#{from} -> #{to} [label=\"#{sym || 'ε'}\"];"
+ }
+ }.flatten
+
+ nodes = (@table.keys + @table.values.map(&:keys).flatten).uniq.map { |i|
+ "#{i} [label=\"#{i.to_s(16)}\"];"
+ }
+
+ <<-eodot
+digraph nfa {
+ rankdir=LR;
+ size="8,5"
+ node [shape = doublecircle];
+ #{@accepting.keys.join ' ' };
+ node [shape = circle];
+ #{nodes.join "\n"}
+ #{edges.join "\n"}
+}
+ eodot
+ end
+
+ private
+ def inverted
+ return @inverted if @inverted
+
+ @inverted = Hash.new { |h,from| h[from] = [] }
+ @table.each { |to, hash|
+ hash.each { |from, sym|
+ @inverted[from] << [sym, to]
+ }
+ }
+
+ @inverted
+ end
+ end
+
+ class Visitor < Visitors::Visitor
+ def initialize tt
+ @tt = tt
+ @i = -1
+ end
+
+ def visit_CAT node
+ left = visit node.left
+ right = visit node.right
+
+ @tt.remove_accepting left.last
+
+ @tt.merge left.last, right.first
+
+ [left.first, right.last]
+ end
+
+ def visit_GROUP node
+ from = @i += 1
+ left = visit node.left
+ to = @i += 1
+
+ @tt.remove_accepting left.last
+ @tt.add_accepting to
+
+ @tt[from, left.first] = nil
+ @tt[left.last, to] = nil
+ @tt[from, to] = nil
+
+ [from, to]
+ end
+
+ def visit_OR node
+ from = @i += 1
+ left = visit node.left
+ right = visit node.right
+ to = @i += 1
+
+ @tt.remove_accepting left.last
+ @tt.remove_accepting right.last
+
+ @tt[from, left.first] = nil
+ @tt[from, right.first] = nil
+ @tt[left.last, to] = nil
+ @tt[right.last, to] = nil
+
+ @tt.add_accepting to
+
+ [from, to]
+ end
+
+ def terminal node
+ from_i = @i += 1 # new state
+ to_i = @i += 1 # new state
+
+ @tt[from_i, to_i] = node.left
+ @tt.add_accepting to_i
+
+ [from_i, to_i]
+ end
+ end
+
+ class Builder
+ def initialize ast
+ @ast = ast
+ end
+
+ def transition_table
+ tt = TransitionTable.new
+ Visitor.new(tt).accept @ast
+ tt
+ end
+ end
+ end
+end
View
2 test/helper.rb
@@ -1,4 +1,4 @@
require 'rubygems'
require 'minitest/autorun'
-require 'journey/router'
+require 'journey'
require 'stringio'
View
38 test/nfa/test_transition_table.rb
@@ -0,0 +1,38 @@
+require 'helper'
+
+module Journey
+ module NFA
+ class TestTransitionTable < MiniTest::Unit::TestCase
+ def setup
+ @parser = Journey::Parser.new
+ end
+
+ def test_one_edge
+ table = tt '/'
+ edges = table.edges(0)
+
+ assert_equal 1, edges.length
+ assert_equal '/', edges.first.first
+ end
+
+ def test_eclosure
+ table = tt '/'
+ assert_equal [], table.eclosure(0)
+
+ table = tt ':a|:b'
+ assert_equal 2, table.eclosure(0).length
+
+ table = tt '(:a|:b)'
+ assert_equal 4, table.eclosure(0).length
+ assert_equal 4, table.eclosure([0]).length
+ end
+
+ private
+ def tt string
+ ast = @parser.parse string
+ builder = Builder.new ast
+ builder.transition_table
+ end
+ end
+ end
+end

0 comments on commit f5323ca

Please sign in to comment.
Something went wrong with that request. Please try again.