Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Pattern Matching
Search
Aaron Patterson
May 11, 2011
Programming
160
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Pattern Matching
MagRails 2011
Aaron Patterson
May 11, 2011
More Decks by Aaron Patterson
See All by Aaron Patterson
Faster FFI for Ruby
tenderlove
0
75
RubyKaigi 2025: Class New, A New Approach
tenderlove
0
290
RubyKaigi Dev Meeting 2025
tenderlove
1
6.3k
Speeding up Instance Variables in Ruby 3.3
tenderlove
2
600
[Feature #20425] Speeding up delegate methods
tenderlove
3
380
RailsConf 2023
tenderlove
30
1.5k
Don't @ Me! Faster Instance Variables with Object Shapes
tenderlove
1
580
RailsConf 2022 Keynote
tenderlove
2
700
Some Assembly Required
tenderlove
1
670
Other Decks in Programming
See All in Programming
Omarchy Tokyo やると聞いて UMPC 買ってセットアップしてきた
mtsmfm
0
140
TiDB Cloudのカスタムコントローラーによるオートスケール対応
takaidohigasi
0
110
Security issues being discussed on Web Platforms
petamoriken
0
930
自分的「カンファレンスの楽しみ方」
syumai
0
200
一参加者から『中の人』へ 〜全通PHPerがブースに立って学んだ、カンファレンスを100倍楽しむコツ〜
wp_daisuke
0
150
AI に Inclusive UI を書かせよう — Design Rules Skill で Compose UI を作り直す
theoriatec2024
1
490
AI Agent時代のリアーキテクチャ戦略と実践
hokaccha
9
4.4k
AIエージェント時代のコードレビューを設計する
nogu66
6
2.7k
手動確認はもう限界 〜XCUITestでCustom URL Schemeの遷移を起動種別ごとに自動テストする〜 / Testing Custom URL Schemes with XCUITest
otouto
0
280
iOSDC2026登壇資料.pdf
riofujimon
0
160
ゲームコントローラやキーボードのファームウェアをSwiftで書く
kishikawakatsumi
1
240
How I Stole PSI from Android Studio - DroidKaigi2026
worker8
0
120
Featured
See All Featured
GraphQLの誤解/rethinking-graphql
sonatard
75
12k
SEO Brein meetup: CTRL+C is not how to scale international SEO
lindahogenes
1
2.9k
The Power of CSS Pseudo Elements
geoffreycrofte
82
6.6k
The Cult of Friendly URLs
andyhume
79
7k
Unsuck your backbone
ammeep
672
58k
Mobile First: as difficult as doing things right
swwweet
225
10k
AI: The stuff that nobody shows you
jnunemaker
PRO
10
1k
Code Review Best Practice
trishagee
74
20k
Visual Storytelling: How to be a Superhuman Communicator
reverentgeek
2
660
Mind Mapping
helmedeiros
1
360
Git: the NoSQL Database
bkeepers
PRO
432
67k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
28
3.6k
Transcript
Pattern Matching
omg
Steak Holder
Aaron Patterson
@tenderlove
Ruby core team
Rails core team
! WARNING !
Stage II: Advanced Beginner
0. Excited to be in England 1. English country, I
have accent. 2. Fear and guilt 3. Fear of plugs and outlets 4. Can't stop thinking of strange 5. Romance Ebi
Manchester Agile Rails
None
None
None
None
! WARNING !
Graphviz http://graphviz.org
world hello greeting
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 } graph.dot
world hello greeting goodbye
2 3 . 4 / /articles(.:format) 5 6 /articles/new(.:format) 0
1 / articles (?-mix:[^./?]+) new
Pattern Matching Move between theory and practice
Journey Yes, it's named after the '70s rock sensation
What is a router? Provides 1: URL generation 2: Path
Recognition 3: Route parsing
Why a new router?
Maintenance
0 700 1400 2100 2800 Journey Journey-2 Rack-Mount LOC
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 url generation path recognition route parsing rack-mount Journey
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 200 OK!
/\/articles(?:.([^.\/?]+))?$/ /\/articles\/new(?:.([^.\/?]+))?$/ /\/articles\/([^.\/?]+)\/edit(?:.([^.\/?]+))?$/ /\/articles\/([^.\/?]+)(?:.([^.\/?]+))?$/ GET /foos/new 404 Not Found
How long does this take?
r = routes.length
x = regexp compare
O(r ⨉ x)
Can we do better?
Finite State Machines Parse Trees Automata
Parse Trees
Parsing regexp
(a|b)*abb
◦ ◦ b ◦ b a * a b ()
| Describe node types
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 ◦ . :format
◦ () ◦ new ◦ / / articles ◦ . :format
SECRET FEATURE parser = Journey::Parser.new ast = parser.parse '/articles|books(.:format)' SUPER
SECRET!
Automata
(a|b)*abb
3 0 b 2 a b 1 a a b
b a Double circle is acceptance state
baabb
3 0 b 2 a b 1 a a b
b a
3 0 b 2 a b 1 a a b
b a b
3 0 b 2 a b 1 a a b
b a ba
3 0 b 2 a b 1 a a b
b a baa
3 0 b 2 a b 1 a a b
b a baabb
aab 3 0 b 2 a b 1 a a
b b a
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
class DFA ... def move(from, symbol) @table[from][symbol] end end move
function
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
Space: S + T states.length + transitions.length
Nondeterministic Finite Automaton
Has nil edges
Can't tell direction
Simulation of NFA
6 0 2 ε 4 ε 3 a 5 b
ε ε a|b
6 0 2 ε 4 ε 3 a 5 b
ε ε nil-closure
6 0 2 ε 4 ε 3 a ε 5
b ε a
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
NFA Construction
◦ ◦ () / articles ◦ . :format /articles(.:format)
cat nodes 1 0 /
/articles 2 0 1 / articles
Optional 3 0 ε 1 ε 2 ????? ε
6 0 1 / 2 articles ε 3 ε 4
. 5 :format ε
parser = Journey::Parser.new ast = parser.parse '/articles(.:format)' nfa = Journey::NFA::Builder.new
ast tt = nfa.transition_table puts tt.to_dot Journey::NFA
Converting NFA to DFA
Eliminate nil transitions
Collapse duplicate edges
6 0 1 / 2 articles ε 3 ε 4
. 5 :format ε /articles(.:format)
2 3 . 4 0 1 / articles :format /articles(.:format)
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
Converting Automata to Regexp
2 3 . 4 0 1 / articles :format /articles(.:format)
2 3 . 4 0 /articles :format /articles(.:format)
/articles(.:format) 2 4 .:format 0 /articles
/articles(.:format) 4 0 /articles(.:format)?
Generalized Transition Graph
/users(.:format) /users/new(.:format) /users/:id/edit(.:format) /users/:id(.:format) resource :users
2 3 . 4 / 5 6 8 . 7
9 / 10 . 11 12 14 . 13 15 0 1 / users (?-mix:[^./?]+) new (?-mix:[^./?]+) (?-mix:[^./?]+) edit (?-mix:[^./?]+) (?-mix:[^./?]+) resource :users
The Plan?
Combine All Routes
Produce DFA
Simulate in O(n) Time
The Future
JS Simulation
None
Rails Console irb> File.open('out.html', 'wb') { |f| irb* f.write( irb*
Wot::Application.routes.router.visualizer irb> )} => 69074
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; });
routes.rb marshalling
Test suggestions
Test coverage
Usage Heat Maps
????
Open Questions
Is our GTG deterministic?
/users/new|/users/:id 4 5 0 1 / 2 users 3 /
new (?-mix:[^./?]+)
"new" =~ /new|[^.\/?]+/
Can we make it deterministic?
L1 = {new} L2 = {[^./?]+}
/users/new|/users/:id 4 5 0 1 / 2 users 3 /
L1 L2 - L1
Is it worth our effort?
REGEX AST NFA DFA
DFA => YACC
DFA => RACC
DFA => Ragel
Is it worth our effort?
Thanks Tatsuya Ono!
Thank You!!
<3<3<3<3<3