Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Pattern Matching
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
Aaron Patterson
May 11, 2011
Programming
150
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
62
RubyKaigi 2025: Class New, A New Approach
tenderlove
0
270
RubyKaigi Dev Meeting 2025
tenderlove
1
5.8k
Speeding up Instance Variables in Ruby 3.3
tenderlove
2
580
[Feature #20425] Speeding up delegate methods
tenderlove
3
370
RailsConf 2023
tenderlove
30
1.5k
Don't @ Me! Faster Instance Variables with Object Shapes
tenderlove
1
560
RailsConf 2022 Keynote
tenderlove
2
690
Some Assembly Required
tenderlove
1
650
Other Decks in Programming
See All in Programming
言葉の格闘技のススメ~紙とペンと言葉から始める、キャリアの描き方~
progresscicada
2
140
5分で問診!Composer セキュリティ健康診断
codmoninc
0
910
AWS DevOps AgentのAzure接続機能を検証して見えた活用法/Use Cases Verified for the AWS DevOps Agent's Azure Connectivity Feature
masakiokuda
1
220
GDG Korea Android: 2026 I/O Extended ~ What's new in Android development tools
pluu
0
220
ここ半年くらいでAIに作らせたR用ツール
eitsupi
0
370
komatsuna「分散システムにおけるバグ分析手法」
komatsunaqa
0
240
メールのエイリアス機能を履き違えない
isshinfunada
0
230
Cloudflare is Agents
chimame
0
150
Built Our Own Background Agent at LayerX
layerx
PRO
9
5k
Welcome to the "Parametricity" 🏙️ − Generic だけど Specific な世界 −
guvalif
PRO
1
200
琵琶湖の水は止められてもNet--HTTPのリトライは止められない / You might be able to stop the water flow of Lake Biwa but you can't stop Net::HTTP retries
luccafort
PRO
0
620
freee が目指す データ マネジメント戦略 AI-Ready 時代を支える 攻めのガバナンスとは
freee
PRO
0
300
Featured
See All Featured
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
11
990
Marketing to machines
jonoalderson
1
5.7k
30 Presentation Tips
portentint
PRO
1
360
DevOps and Value Stream Thinking: Enabling flow, efficiency and business value
helenjbeal
1
310
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
49
10k
The State of eCommerce SEO: How to Win in Today's Products SERPs - #SEOweek
aleyda
2
11k
Bridging the Design Gap: How Collaborative Modelling removes blockers to flow between stakeholders and teams @FastFlow conf
baasie
0
640
Art, The Web, and Tiny UX
lynnandtonic
304
22k
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
194
17k
How to optimise 3,500 product descriptions for ecommerce in one day using ChatGPT
katarinadahlin
PRO
2
3.7k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
47
8.3k
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