aboutsummaryrefslogblamecommitdiffstats
path: root/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
blob: 2c34826bb0bebfd93d61fc51499cf32b7bdc1f95 (plain) (tree)
1
2
3
4
5
6


                                         


                                     




















































































































































                                                                                
require 'action_dispatch/journey/nfa/dot'

module ActionDispatch
  module Journey # :nodoc:
    module GTG # :nodoc:
      class TransitionTable # :nodoc:
        include Journey::NFA::Dot

        attr_reader :memos

        def initialize
          @regexp_states = Hash.new { |h,k| h[k] = {} }
          @string_states = Hash.new { |h,k| h[k] = {} }
          @accepting     = {}
          @memos         = Hash.new { |h,k| h[k] = [] }
        end

        def add_accepting state
          @accepting[state] = true
        end

        def accepting_states
          @accepting.keys
        end

        def accepting? state
          @accepting[state]
        end

        def add_memo idx, memo
          @memos[idx] << memo
        end

        def memo idx
          @memos[idx]
        end

        def eclosure t
          Array(t)
        end

        def move t, a
          move_string(t, a).concat move_regexp(t, a)
        end

        def to_json
          require 'json'

          simple_regexp = Hash.new { |h,k| h[k] = {} }

          @regexp_states.each do |from, hash|
            hash.each do |re, to|
              simple_regexp[from][re.source] = to
            end
          end

          JSON.dump({
            :regexp_states => simple_regexp,
            :string_states => @string_states,
            :accepting     => @accepting
          })
        end

        def to_svg
          svg = IO.popen("dot -Tsvg", 'w+') { |f|
            f.write to_dot
            f.close_write
            f.readlines
          }
          3.times { svg.shift }
          svg.join.sub(/width="[^"]*"/, '').sub(/height="[^"]*"/, '')
        end

        def visualizer paths, title = 'FSM'
          viz_dir   = File.join File.dirname(__FILE__), '..', 'visualizer'
          fsm_js    = File.read File.join(viz_dir, 'fsm.js')
          fsm_css   = File.read File.join(viz_dir, 'fsm.css')
          erb       = File.read File.join(viz_dir, 'index.html.erb')
          states    = "function tt() { return #{to_json}; }"

          fun_routes = paths.shuffle.first(3).map do |ast|
            ast.map { |n|
              case n
              when Nodes::Symbol
                case n.left
                when ':id' then rand(100).to_s
                when ':format' then %w{ xml json }.shuffle.first
                else
                  'omg'
                end
              when Nodes::Terminal then n.symbol
              else
                nil
              end
            }.compact.join
          end

          stylesheets = [fsm_css]
          svg         = to_svg
          javascripts = [states, fsm_js]

          # Annoying hack for 1.9 warnings
          fun_routes  = fun_routes
          stylesheets = stylesheets
          svg         = svg
          javascripts = javascripts

          require 'erb'
          template = ERB.new erb
          template.result(binding)
        end

        def []= from, to, sym
          case sym
          when String
            @string_states[from][sym] = to
          when Regexp
            @regexp_states[from][sym] = to
          else
            raise ArgumentError, 'unknown symbol: %s' % sym.class
          end
        end

        def states
          ss = @string_states.keys + @string_states.values.map(&:values).flatten
          rs = @regexp_states.keys + @regexp_states.values.map(&:values).flatten
          (ss + rs).uniq
        end

        def transitions
          @string_states.map { |from, hash|
            hash.map { |s, to| [from, s, to] }
          }.flatten(1) + @regexp_states.map { |from, hash|
            hash.map { |s, to| [from, s, to] }
          }.flatten(1)
        end

        private
        def move_regexp t, a
          return [] if t.empty?

          t.map { |s|
            @regexp_states[s].map { |re,v| re === a ? v : nil }
          }.flatten.compact.uniq
        end

        def move_string t, a
          return [] if t.empty?

          t.map { |s| @string_states[s][a] }.compact
        end
      end
    end
  end
end