diff --git a/actionpack/lib/action_dispatch/journey.rb b/actionpack/lib/action_dispatch/journey.rb index 2852efa6ae..18f966a640 100644 --- a/actionpack/lib/action_dispatch/journey.rb +++ b/actionpack/lib/action_dispatch/journey.rb @@ -3,5 +3,3 @@ require "action_dispatch/journey/router" require "action_dispatch/journey/gtg/builder" require "action_dispatch/journey/gtg/simulator" -require "action_dispatch/journey/nfa/builder" -require "action_dispatch/journey/nfa/simulator" diff --git a/actionpack/lib/action_dispatch/journey/nfa/builder.rb b/actionpack/lib/action_dispatch/journey/nfa/builder.rb deleted file mode 100644 index d22302e101..0000000000 --- a/actionpack/lib/action_dispatch/journey/nfa/builder.rb +++ /dev/null @@ -1,78 +0,0 @@ -# frozen_string_literal: true - -require "action_dispatch/journey/nfa/transition_table" -require "action_dispatch/journey/gtg/transition_table" - -module ActionDispatch - module Journey # :nodoc: - module NFA # :nodoc: - class Visitor < Visitors::Visitor # :nodoc: - def initialize(tt) - @tt = tt - @i = -1 - end - - def visit_CAT(node) - left = visit(node.left) - right = visit(node.right) - - @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.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 - children = node.children.map { |c| visit(c) } - to = @i += 1 - - children.each do |child| - @tt[from, child.first] = nil - @tt[child.last, to] = nil - end - - @tt.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 - @tt.accepting = to_i - @tt.add_memo(to_i, node.memo) - - [from_i, to_i] - end - end - - class Builder # :nodoc: - def initialize(ast) - @ast = ast - end - - def transition_table - tt = TransitionTable.new - Visitor.new(tt).accept(@ast) - tt - end - end - end - end -end diff --git a/actionpack/lib/action_dispatch/journey/nfa/dot.rb b/actionpack/lib/action_dispatch/journey/nfa/dot.rb index 56e9e3c83d..48b56c8570 100644 --- a/actionpack/lib/action_dispatch/journey/nfa/dot.rb +++ b/actionpack/lib/action_dispatch/journey/nfa/dot.rb @@ -9,17 +9,6 @@ module ActionDispatch " #{from} -> #{to} [label=\"#{sym || 'ε'}\"];" } - # memo_nodes = memos.values.flatten.map { |n| - # label = n - # if Journey::Route === n - # label = "#{n.verb.source} #{n.path.spec}" - # end - # " #{n.object_id} [label=\"#{label}\", shape=box];" - # } - # memo_edges = memos.flat_map { |k, memos| - # (memos || []).map { |v| " #{k} -> #{v.object_id};" } - # }.uniq - <<-eodot digraph nfa { rankdir=LR; diff --git a/actionpack/lib/action_dispatch/journey/nfa/simulator.rb b/actionpack/lib/action_dispatch/journey/nfa/simulator.rb deleted file mode 100644 index 002f6feb97..0000000000 --- a/actionpack/lib/action_dispatch/journey/nfa/simulator.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -require "strscan" - -module ActionDispatch - module Journey # :nodoc: - module NFA # :nodoc: - class MatchData # :nodoc: - attr_reader :memos - - def initialize(memos) - @memos = memos - end - end - - class Simulator # :nodoc: - attr_reader :tt - - def initialize(transition_table) - @tt = transition_table - end - - def simulate(string) - input = StringScanner.new(string) - state = tt.eclosure(0) - until input.eos? - sym = input.scan(%r([/.?]|[^/.?]+)) - state = tt.eclosure(tt.move(state, sym)) - end - - acceptance_states = state.find_all { |s| - tt.accepting?(tt.eclosure(s).sort.last) - } - - return if acceptance_states.empty? - - memos = acceptance_states.flat_map { |x| tt.memo(x) }.compact - - MatchData.new(memos) - end - - alias :=~ :simulate - alias :match :simulate - end - end - end -end diff --git a/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb b/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb deleted file mode 100644 index b36003089d..0000000000 --- a/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb +++ /dev/null @@ -1,119 +0,0 @@ -# frozen_string_literal: true - -require "action_dispatch/journey/nfa/dot" - -module ActionDispatch - module Journey # :nodoc: - module NFA # :nodoc: - class TransitionTable # :nodoc: - include Journey::NFA::Dot - - attr_accessor :accepting - attr_reader :memos - - def initialize - @table = Hash.new { |h, f| h[f] = {} } - @memos = {} - @accepting = nil - @inverted = nil - end - - def accepting?(state) - accepting == state - end - - def accepting_states - [accepting] - end - - def add_memo(idx, memo) - @memos[idx] = memo - end - - def memo(idx) - @memos[idx] - end - - def []=(i, f, s) - @table[f][i] = s - end - - def merge(left, right) - @memos[right] = @memos.delete(left) - @table[right] = @table.delete(left) - end - - def states - (@table.keys + @table.values.flat_map(&:keys)).uniq - end - - # Returns set of NFA states to which there is a transition on ast symbol - # +a+ from some state +s+ in +t+. - def following_states(t, a) - Array(t).flat_map { |s| inverted[s][a] }.uniq - end - - # Returns set of NFA states to which there is a transition on ast symbol - # +a+ from some state +s+ in +t+. - def move(t, a) - Array(t).map { |s| - inverted[s].keys.compact.find_all { |sym| - sym === a - }.map { |sym| inverted[s][sym] } - }.flatten.uniq - end - - def alphabet - inverted.values.flat_map(&:keys).compact.uniq.sort_by(&:to_s) - end - - # Returns a set of NFA states reachable from some NFA state +s+ in set - # +t+ on nil-transitions alone. - def eclosure(t) - stack = Array(t) - seen = {} - children = [] - - until stack.empty? - s = stack.pop - next if seen[s] - - seen[s] = true - children << s - - stack.concat(inverted[s][nil]) - end - - children.uniq - end - - def transitions - @table.flat_map { |to, hash| - hash.map { |from, sym| [from, sym, to] } - } - end - - private - def inverted - return @inverted if @inverted - - @inverted = Hash.new { |h, from| - h[from] = Hash.new { |j, s| j[s] = [] } - } - - @table.each { |to, hash| - hash.each { |from, sym| - if sym - sym = Nodes::Symbol === sym ? sym.regexp : sym.left - end - - @inverted[from][sym] << to - } - } - - @inverted - end - end - end - end -end diff --git a/actionpack/test/journey/gtg/builder_test.rb b/actionpack/test/journey/gtg/builder_test.rb index b92460884d..9dc786e261 100644 --- a/actionpack/test/journey/gtg/builder_test.rb +++ b/actionpack/test/journey/gtg/builder_test.rb @@ -40,10 +40,10 @@ module ActionDispatch /articles/:id(.:format) } - sim = NFA::Simulator.new table + sim = Simulator.new table - match = sim.match "/articles/new" - assert_equal 2, match.memos.length + memos = sim.memos "/articles/new" + assert_equal 2, memos.length end ## @@ -54,10 +54,10 @@ module ActionDispatch /articles/new(.:format) } - sim = NFA::Simulator.new table + sim = Simulator.new table - match = sim.match "/articles/new" - assert_equal 2, match.memos.length + memos = sim.memos "/articles/new" + assert_equal 2, memos.length end private diff --git a/actionpack/test/journey/nfa/simulator_test.rb b/actionpack/test/journey/nfa/simulator_test.rb deleted file mode 100644 index 6b9f87b452..0000000000 --- a/actionpack/test/journey/nfa/simulator_test.rb +++ /dev/null @@ -1,100 +0,0 @@ -# frozen_string_literal: true - -require "abstract_unit" - -module ActionDispatch - module Journey - module NFA - class TestSimulator < ActiveSupport::TestCase - def test_simulate_simple - sim = simulator_for ["/foo"] - assert_match sim, "/foo" - end - - def test_simulate_simple_no_match - sim = simulator_for ["/foo"] - assert_no_match sim, "foo" - end - - def test_simulate_simple_no_match_too_long - sim = simulator_for ["/foo"] - assert_no_match sim, "/foo/bar" - end - - def test_simulate_simple_no_match_wrong_string - sim = simulator_for ["/foo"] - assert_no_match sim, "/bar" - end - - def test_simulate_regex - sim = simulator_for ["/:foo/bar"] - assert_match sim, "/bar/bar" - assert_match sim, "/foo/bar" - end - - def test_simulate_or - sim = simulator_for ["/foo", "/bar"] - assert_match sim, "/bar" - assert_match sim, "/foo" - assert_no_match sim, "/baz" - end - - def test_simulate_optional - sim = simulator_for ["/foo(/bar)"] - assert_match sim, "/foo" - assert_match sim, "/foo/bar" - assert_no_match sim, "/foo/" - end - - def test_matchdata_has_memos - paths = %w{ /foo /bar } - parser = Journey::Parser.new - asts = paths.map { |x| - ast = parser.parse x - ast.each { |n| n.memo = ast } - ast - } - - expected = asts.first - - builder = Builder.new Nodes::Or.new asts - - sim = Simulator.new builder.transition_table - - md = sim.match "/foo" - assert_equal [expected], md.memos - end - - def test_matchdata_memos_on_merge - parser = Journey::Parser.new - routes = [ - "/articles(.:format)", - "/articles/new(.:format)", - "/articles/:id/edit(.:format)", - "/articles/:id(.:format)", - ].map { |path| - ast = parser.parse path - ast.each { |n| n.memo = ast } - ast - } - - asts = routes.dup - - ast = Nodes::Or.new routes - - nfa = Journey::NFA::Builder.new ast - sim = Simulator.new nfa.transition_table - md = sim.match "/articles" - assert_equal [asts.first], md.memos - end - - def simulator_for(paths) - parser = Journey::Parser.new - asts = paths.map { |x| parser.parse x } - builder = Builder.new Nodes::Or.new asts - Simulator.new builder.transition_table - end - end - end - end -end diff --git a/actionpack/test/journey/nfa/transition_table_test.rb b/actionpack/test/journey/nfa/transition_table_test.rb deleted file mode 100644 index c23611e980..0000000000 --- a/actionpack/test/journey/nfa/transition_table_test.rb +++ /dev/null @@ -1,74 +0,0 @@ -# frozen_string_literal: true - -require "abstract_unit" - -module ActionDispatch - module Journey - module NFA - class TestTransitionTable < ActiveSupport::TestCase - def setup - @parser = Journey::Parser.new - end - - def test_eclosure - table = tt "/" - assert_equal [0], table.eclosure(0) - - table = tt ":a|:b" - assert_equal 3, table.eclosure(0).length - - table = tt "(:a|:b)" - assert_equal 5, table.eclosure(0).length - assert_equal 5, table.eclosure([0]).length - end - - def test_following_states_one - table = tt "/" - - assert_equal [1], table.following_states(0, "/") - assert_equal [1], table.following_states([0], "/") - end - - def test_following_states_group - table = tt "a|b" - states = table.eclosure 0 - - assert_equal 1, table.following_states(states, "a").length - assert_equal 1, table.following_states(states, "b").length - end - - def test_following_states_multi - table = tt "a|a" - states = table.eclosure 0 - - assert_equal 2, table.following_states(states, "a").length - assert_equal 0, table.following_states(states, "b").length - end - - def test_following_states_regexp - table = tt "a|:a" - states = table.eclosure 0 - - assert_equal 1, table.following_states(states, "a").length - assert_equal 1, table.following_states(states, /[^\.\/\?]+/).length - assert_equal 0, table.following_states(states, "b").length - end - - def test_alphabet - table = tt "a|:a" - assert_equal [/[^\.\/\?]+/, "a"], table.alphabet - - table = tt "a|a" - assert_equal ["a"], table.alphabet - end - - private - def tt(string) - ast = @parser.parse string - builder = Builder.new ast - builder.transition_table - end - end - end - end -end