Who makes the best
     Asado?
What country has the
 best footballers?
Argentina!
river
river




Nacional B???
NOOOOOOOO!!!!
Aaron Patterson
@tenderlove
Ruby core team
Rails core team
! WARNING !
ZOMG!
We are in Argentina!
José   Yo
WWFMD?
me gusta
¿Por qué Maria?
THANKS!!
Resource
Management
Path Management
Move bet ween theory
   and practice




 Pattern
Matching
Graphviz
 http://graphviz.org
greeting
hello              world
graph.dot

digraph nfa {
  rankdir=LR;

    world [shape = doublecircle];
    hello [shape = circle];
    hello -> world [label="greeting"];
}
graph.dot
digraph nfa {
  rankdir=LR;

    world [shape = doublecircle];
    hello [shape = circle];
    goodbye [shape = circle];

    hello -> world [label="greeting"];
    goodbye -> world
}
hello
          greeting

                     world


goodbye
/articles(.:format)

    /       articles       .       (?-mix:[^./?]+)
0       1              2       3                     5
                           /

                                        new
                               4                     6   /articles/new(.:format)
Journey
Journey
Yes, it's named after the '70s rock sensation
Provides
  1: URL generation
  2: Path Recognition
  3: Route parsing




What is a router?
Why a new router?
Maintenance
LOC
3000



2250



1500



 750



   0
       Journey   Journey-2   Rack-Mount
Known algorithms
References

• Compilers: Principles, Techniques, &
  Tools (Aho, Lam, Sethi, Ullman)

• Intro to Formal Languages and
  Automata (Linz)
Predictability
CPU Time
Memory Usage
Current Performance
            rack-mount                  Journey




   url generation    path recognition       route parsing
me gusta
Patterns
Why patterns matter
Regular Expressions
       /om(g)!/
Rails Routes
resource :articles
/articles(.:format)
/articles/new(.:format)
/articles/:id/edit(.:format)
/articles/:id(.:format)
Parens don't capture
:whatever => /([^./?]+)/
/articles(.:format)

//articles(?:.([^./?]+))?$/
//articles(?:.([^./?]+))?$/

//articles/new(?:.([^./?]+))?$/

//articles/([^./?]+)/edit(?:.([^./?]+))?$/

//articles/([^./?]+)(?:.([^./?]+))?$/
GET /articles/new

//articles(?:.([^./?]+))?$/

//articles/new(?:.([^./?]+))?$/

//articles/([^./?]+)/edit(?:.([^./?]+))?$/

//articles/([^./?]+)(?:.([^./?]+))?$/
GET /articles/new

//articles(?:.([^./?]+))?$/

//articles/new(?:.([^./?]+))?$/

//articles/([^./?]+)/edit(?:.([^./?]+))?$/

//articles/([^./?]+)(?:.([^./?]+))?$/
GET /articles/new

//articles(?:.([^./?]+))?$/

//articles/new(?:.([^./?]+))?$/

//articles/([^./?]+)/edit(?:.([^./?]+))?$/

//articles/([^./?]+)(?:.([^./?]+))?$/



              200 OK!
//articles(?:.([^./?]+))?$/

//articles/new(?:.([^./?]+))?$/

//articles/([^./?]+)/edit(?:.([^./?]+))?$/

//articles/([^./?]+)(?:.([^./?]+))?$/
GET /foos/new

//articles(?:.([^./?]+))?$/

//articles/new(?:.([^./?]+))?$/

//articles/([^./?]+)/edit(?:.([^./?]+))?$/

//articles/([^./?]+)(?:.([^./?]+))?$/
GET /foos/new

//articles(?:.([^./?]+))?$/

//articles/new(?:.([^./?]+))?$/

//articles/([^./?]+)/edit(?:.([^./?]+))?$/

//articles/([^./?]+)(?:.([^./?]+))?$/
GET /foos/new

//articles(?:.([^./?]+))?$/

//articles/new(?:.([^./?]+))?$/

//articles/([^./?]+)/edit(?:.([^./?]+))?$/

//articles/([^./?]+)(?:.([^./?]+))?$/
GET /foos/new

//articles(?:.([^./?]+))?$/

//articles/new(?:.([^./?]+))?$/

//articles/([^./?]+)/edit(?:.([^./?]+))?$/

//articles/([^./?]+)(?:.([^./?]+))?$/
GET /foos/new

//articles(?:.([^./?]+))?$/

//articles/new(?:.([^./?]+))?$/

//articles/([^./?]+)/edit(?:.([^./?]+))?$/

//articles/([^./?]+)(?:.([^./?]+))?$/
GET /foos/new

//articles(?:.([^./?]+))?$/

//articles/new(?:.([^./?]+))?$/

//articles/([^./?]+)/edit(?:.([^./?]+))?$/

//articles/([^./?]+)(?:.([^./?]+))?$/



         404 Not Found
How long does this
      take?
r = routes.length
x = regexp compare
O(r ⨉ x)
Can we do better?
Parse Trees


     Automata


Finite State Machines
Parse Trees
Parsing regexp
(a|b)*abb
○   Describe node types



  ○ b

○ b

a *

  ()

  |

 a b
Parsing rails routes
Journey::Parser
parser = Journey::Parser.new
ast    = parser.parse '/articles(.:format)'

puts ast.to_dot
○

      ○        ()

/   articles    ○

           .        :format
To String!


parser = Journey::Parser.new
ast    = parser.parse '/articles(.:format)'
puts ast.to_s
puts ast.to_regexp
OR AST
parser = Journey::Parser.new

trees = [
  '/articles(.:format)',
  '/articles/new(.:format)',
].map { |s| parser.parse s }

ast = Journey::Nodes::Or.new trees

puts ast.to_dot
|

                 ○                   ○

      ○          ()                  ○         ()

/   articles     ○               ○       new    ○

       .       :format           ○       /          .   :format

                         /       articles
SECRET FEATURE
parser = Journey::Parser.new
ast = parser.parse '/articles|books(.:format)'
SECRET FEATURE
parser = Journey::Parser.new
                            RE T!
                        EC
ast = parser.parse '/articles|books(.:format)'
                    RS
             SU  PE
Automata
(a|b)*abb
Double circle
                    is acceptance
                    state
        b       a
            a       b
    b   0       1   a
            a                2
3
            b
baabb
b       a
            a       b
    b   0       1   a
            a           2
3
            b
b
        b       a
            a       b
    b   0       1   a
            a           2
3
            b
ba
        b        a
            a        b
    b   0        1   a
            a            2
3
            b
baa
        b         a
             a        b
    b   0         1   a
             a            2
3
             b
baabb
        b       a
            a       b
    b   0       1   a
            a           2
3
            b
aab
        b         a
             a        b
    b   0         1   a
             a            2
3
             b
Deterministic Finite
   Automaton
Only one path
Storage
class DFA
  attr_reader :table

  def initialize
    @table = Hash.new { |h, from| h[from] = {} }
    @table[0]['a'] = 1
    @table[0]['b'] = 0
    @table[1]['a'] = 1
    @table[1]['b'] = 2
    @table[2]['a'] = 1
    @table[2]['b'] = 3
    @table[3]['b'] = 0
    @table[3]['a'] = 2
  end
end
FSM Simulation
class Simulator
  def initialize(dfa)
    @dfa = dfa
  end

  def simulate(symbols)
    state = 0
    until symbols.empty?
      state = @dfa.move(state, symbols.shift)
    end
    state
  end
end
move function

class DFA
  ...

  def move(from, symbol)
    @table[from][symbol]
  end
end
irb> sim = Simulator.new(DFA.new)
=> #<Simulator:0x007f95929a82e0 ...>
irb> sim.simulate %w{ b a a b b }
=> 3
irb> sim.simulate %w{ a a b }
=> 2
Time: O(n)
 n = string.length
me gusta
Space: S + T
states.length + transitions.length
Nondeterministic
Finite Automaton
Has nil edges
Can't tell direction
Simulation of NFA
a|b

            a
    ε   2         3   ε
0   ε                 ε   6
            b
        4         5
nil-closure

             a
    ε    2       3    ε
0   ε                 ε   6
             b
         4       5
a

            a
    ε   2       3   ε
0   ε               ε   6
            b
        4       5
Storage
class NFA
  def initialize
    @table = Hash.new { |h, from|
      h[from] = Hash.new { |i,sym| i[sym] = [] }
    }

    @table[0][nil]   <<   2
    @table[0][nil]   <<   4
    @table[2]['a']   <<   3
    @table[4]['b']   <<   5
    @table[3][nil]   <<   6
    @table[5][nil]   <<   6
  end

  def nil_closure(states)
    states.map { |s| @table[s][nil] }.flatten
  end
end
FSM Simulation
class Simulator
  def initialize(nfa)
    @nfa = nfa
  end

  def simulate(symbols)
    states = @nfa.nil_closure([0])

    until symbols.empty?
      next_s = @nfa.move(states, symbols.shift)
      states = @nfa.nil_closure(next_s)
    end

    states
  end
end
Move function

class NFA
  ...

  def move(states, symbol)
    states.map { |s| @table[s][symbol] }.flatten
  end
end
irb> sim = Simulator.new(NFA.new)
=> #<Simulator:0x007faa188a5f88 ...>
irb> sim.simulate %w{ a }
=> [6]
irb> sim.simulate %w{ b }
=> [6]
irb> sim.simulate %w{ b b }
=> []
irb> sim.simulate %w{ c }
=> []
Time: O(r ⨉ x)
r = operators.length, x = string.length
Who cares about
    NFA?
NFA Construction
/articles(.:format)
            ○

        ○        ()

  /   articles    ○

             .        :format
cat nodes


    /
0       1
/articles


    /        articles
0        1              2
Optional

             ε
0   ε       ?????       ε   3
        1           2
ε
    /       articles
0       1              2   ε                             ε   6
                                   .       :format
                               3       4             5
Journey::NFA

parser = Journey::Parser.new
ast    = parser.parse '/articles(.:format)'
nfa    = Journey::NFA::Builder.new ast

tt = nfa.transition_table
puts tt.to_dot
Converting
NFA to DFA
Eliminate nil
 transitions
Collapse duplicate
      edges
/articles(.:format)

                                       ε
    /       articles
0       1              2   ε                             ε   6
                                   .       :format
                               3       4             5
/articles(.:format)

    /       articles       .       :format
0       1              2       3             4
CODES!

parser = Journey::Parser.new
ast    = parser.parse '/articles(.:format)'
nfa    = Journey::NFA::Builder.new ast

tt = nfa.transition_table.generalized_table
puts tt.to_dot
SHORTER CODES!

parser = Journey::Parser.new
ast    = parser.parse '/articles(.:format)'
dfa    = Journey::GTG::Builder.new ast

tt = dfa.transition_table
puts tt.to_dot
ANY NFA
converts to DFA
O(r ⨉ x) => O(x)
r = operations.length, x = string.length
me gusta
Converting Automata
    to Regexp
/articles(.:format)

    /       articles       .       :format
0       1              2       3             4
/articles(.:format)

    /articles       .       :format
0               2       3             4
/articles(.:format)

    /articles       .:format
0               2              4
/articles(.:format)

     /articles(.:format)?
0                           4
Generalized
Transition Graph
resource :users
/users(.:format)
/users/new(.:format)
/users/:id/edit(.:format)
/users/:id(.:format)
resource :users
                                (?-mix:[^./?]+)
                        .   3                     5
    /       users
0       1           2   /
                                     new              .        (?-mix:[^./?]+)
                            4                     6       8                      11
                                (?-mix:[^./?]+)

                                                      /             edit              .        (?-mix:[^./?]+)
                                                  7       9                      12       14                     15
                                                      .
                                                               (?-mix:[^./?]+)
                                                          10                     13
The Plan?
Combine All Routes
Produce DFA
Simulate in O(n) Time
Routing To The
   Future
JS Simulation
Table => JSON

parser = Journey::Parser.new
ast    = parser.parse '/articles(.:format)'
dfa    = Journey::GTG::Builder.new ast

tt = dfa.transition_table
puts tt.to_json
Table => SVG

parser = Journey::Parser.new
ast    = parser.parse '/articles(.:format)'
dfa    = Journey::GTG::Builder.new ast

tt = dfa.transition_table
puts tt.to_svg
JS Tokenizer


function tokenize(input, callback) {
  while(input.length > 0) {
    callback(input.match(/^[/.?]|[^/.?]+/)[0]);
    input = input.replace(/^[/.?]|[^/.?]+/, '');
  }
}
JS Simulator
tokenize(input, function(token) {
  var new_states = [];
  for(var key in states) {
    var state = states[key];

      if(string_states[state] && string_states[state][token]) {
        var new_state = string_states[state][token];
        highlight_edge(state, new_state);
        highlight_state(new_state);
        new_states.push(new_state);
      }
  }

  if(new_states.length == 0) {
    return;
  }
  states = new_states;
});
d3.js
Rails Console


irb> File.open('out.html', 'wb') { |f|
irb* f.write(
irb* Wot::Application.routes.router.visualizer
irb> )}
=> 69074
routes.rb marshalling
Test suggestions
Test coverage
Usage Heat Maps
AST



REGEXP         NFA



         DFA
ROFLSCALE!!!
DFA => YACC
DFA => RACC
DFA => Ragel
Open Questions
Is our GTG
deterministic?
/users/new|/users/:id

                                     new          4
    /       users       /       (?-mix:[^./?]+)
0       1           2       3
                                                  5
"new" =~ /new|[^./?]+/
Can we make it
deterministic?
L1 = {new}
L2 = {[^./?]+}
/users/new|/users/:id

                                   L1      4
     /       users       /       L2 - L1
0        1           2       3
                                           5
Is it worth our effort?
AST



REGEXP         NFA



         DFA
Is it worth our effort?
Thank You!!
<3<3<3<3<3

RubyConf Argentina 2011