Skip to content

Commit ea585d6

Browse files
authored
Merge pull request rails#52629 from Shopify/opt_scanner
Optimize ActionDispatch::Journey::Scanner
2 parents afecc8f + 90b293c commit ea585d6

File tree

3 files changed

+92
-90
lines changed

3 files changed

+92
-90
lines changed

actionpack/lib/action_dispatch/journey/parser.rb

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def parse_expressions
3636
node = parse_expression
3737

3838
while @next_token
39-
case @next_token.first
39+
case @next_token
4040
when :RPAREN
4141
break
4242
when :OR
@@ -56,43 +56,43 @@ def parse_or(lhs)
5656
end
5757

5858
def parse_expression
59-
if @next_token.first == :STAR
59+
if @next_token == :STAR
6060
parse_star
61-
elsif @next_token.first == :LPAREN
61+
elsif @next_token == :LPAREN
6262
parse_group
6363
else
6464
parse_terminal
6565
end
6666
end
6767

6868
def parse_star
69-
node = Star.new(Symbol.new(@next_token.last, Symbol::GREEDY_EXP))
69+
node = Star.new(Symbol.new(@scanner.last_string, Symbol::GREEDY_EXP))
7070
advance_token
7171
node
7272
end
7373

7474
def parse_group
7575
advance_token
7676
node = parse_expressions
77-
if @next_token.first == :RPAREN
77+
if @next_token == :RPAREN
7878
node = Group.new(node)
7979
advance_token
8080
node
8181
else
82-
raise "error"
82+
raise ArgumentError, "missing right parenthesis."
8383
end
8484
end
8585

8686
def parse_terminal
87-
node = case @next_token.first
87+
node = case @next_token
8888
when :SYMBOL
89-
Symbol.new(@next_token.last)
89+
Symbol.new(@scanner.last_string)
9090
when :LITERAL
91-
Literal.new(@next_token.last)
91+
Literal.new(@scanner.last_literal)
9292
when :SLASH
93-
Slash.new(@next_token.last)
93+
Slash.new("/")
9494
when :DOT
95-
Dot.new(@next_token.last)
95+
Dot.new(".")
9696
end
9797

9898
advance_token

actionpack/lib/action_dispatch/journey/scanner.rb

Lines changed: 40 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,64 +7,62 @@
77
module ActionDispatch
88
module Journey # :nodoc:
99
class Scanner # :nodoc:
10+
STATIC_TOKENS = Array.new(150)
11+
STATIC_TOKENS[".".ord] = :DOT
12+
STATIC_TOKENS["/".ord] = :SLASH
13+
STATIC_TOKENS["(".ord] = :LPAREN
14+
STATIC_TOKENS[")".ord] = :RPAREN
15+
STATIC_TOKENS["|".ord] = :OR
16+
STATIC_TOKENS[":".ord] = :SYMBOL
17+
STATIC_TOKENS["*".ord] = :STAR
18+
STATIC_TOKENS.freeze
19+
20+
class Scanner < StringScanner
21+
unless method_defined?(:peek_byte) # https://github.com/ruby/strscan/pull/89
22+
def peek_byte
23+
string.getbyte(pos)
24+
end
25+
end
26+
end
27+
1028
def initialize
11-
@ss = nil
29+
@scanner = nil
30+
@length = nil
1231
end
1332

1433
def scan_setup(str)
15-
@ss = StringScanner.new(str)
34+
@scanner = Scanner.new(str)
1635
end
1736

18-
def eos?
19-
@ss.eos?
20-
end
37+
def next_token
38+
return if @scanner.eos?
2139

22-
def pos
23-
@ss.pos
40+
until token = scan || @scanner.eos?; end
41+
token
2442
end
2543

26-
def pre_match
27-
@ss.pre_match
44+
def last_string
45+
-@scanner.string.byteslice(@scanner.pos - @length, @length)
2846
end
2947

30-
def next_token
31-
return if @ss.eos?
32-
33-
until token = scan || @ss.eos?; end
34-
token
48+
def last_literal
49+
last_str = @scanner.string.byteslice(@scanner.pos - @length, @length)
50+
last_str.tr! "\\", ""
51+
-last_str
3552
end
3653

3754
private
38-
# takes advantage of String @- deduping capabilities in Ruby 2.5 upwards see:
39-
# https://bugs.ruby-lang.org/issues/13077
40-
def dedup_scan(regex)
41-
r = @ss.scan(regex)
42-
r ? -r : nil
43-
end
44-
4555
def scan
56+
next_byte = @scanner.peek_byte
4657
case
47-
# /
48-
when @ss.skip(/\//)
49-
[:SLASH, "/"]
50-
when @ss.skip(/\(/)
51-
[:LPAREN, "("]
52-
when @ss.skip(/\)/)
53-
[:RPAREN, ")"]
54-
when @ss.skip(/\|/)
55-
[:OR, "|"]
56-
when @ss.skip(/\./)
57-
[:DOT, "."]
58-
when text = dedup_scan(/:\w+/)
59-
[:SYMBOL, text]
60-
when text = dedup_scan(/\*\w+/)
61-
[:STAR, text]
62-
when text = @ss.scan(/(?:[\w%\-~!$&'*+,;=@]|\\[:()])+/)
63-
text.tr! "\\", ""
64-
[:LITERAL, -text]
65-
# any char
66-
when text = dedup_scan(/./)
67-
[:LITERAL, text]
58+
when (token = STATIC_TOKENS[next_byte])
59+
@scanner.pos += 1
60+
@length = @scanner.skip(/\w+/).to_i + 1 if token == :SYMBOL || token == :STAR
61+
token
62+
when @length = @scanner.skip(/(?:[\w%\-~!$&'*+,;=@]|\\[:()])+/)
63+
:LITERAL
64+
when @length = @scanner.skip(/./)
65+
:LITERAL
6866
end
6967
end
7068
end

actionpack/test/journey/route/definition/scanner_test.rb

Lines changed: 41 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -11,50 +11,54 @@ def setup
1111
end
1212

1313
CASES = [
14-
["/", [[:SLASH, "/"]]],
15-
["*omg", [[:STAR, "*omg"]]],
16-
["/page", [[:SLASH, "/"], [:LITERAL, "page"]]],
17-
["/page!", [[:SLASH, "/"], [:LITERAL, "page!"]]],
18-
["/page$", [[:SLASH, "/"], [:LITERAL, "page$"]]],
19-
["/page&", [[:SLASH, "/"], [:LITERAL, "page&"]]],
20-
["/page'", [[:SLASH, "/"], [:LITERAL, "page'"]]],
21-
["/page*", [[:SLASH, "/"], [:LITERAL, "page*"]]],
22-
["/page+", [[:SLASH, "/"], [:LITERAL, "page+"]]],
23-
["/page,", [[:SLASH, "/"], [:LITERAL, "page,"]]],
24-
["/page;", [[:SLASH, "/"], [:LITERAL, "page;"]]],
25-
["/page=", [[:SLASH, "/"], [:LITERAL, "page="]]],
26-
["/page@", [[:SLASH, "/"], [:LITERAL, "page@"]]],
27-
['/page\:', [[:SLASH, "/"], [:LITERAL, "page:"]]],
28-
['/page\(', [[:SLASH, "/"], [:LITERAL, "page("]]],
29-
['/page\)', [[:SLASH, "/"], [:LITERAL, "page)"]]],
30-
["/~page", [[:SLASH, "/"], [:LITERAL, "~page"]]],
31-
["/pa-ge", [[:SLASH, "/"], [:LITERAL, "pa-ge"]]],
32-
["/:page", [[:SLASH, "/"], [:SYMBOL, ":page"]]],
14+
["/", [:SLASH]],
15+
["*omg", [:STAR]],
16+
["/page", [:SLASH, :LITERAL]],
17+
["/page!", [:SLASH, :LITERAL]],
18+
["/page$", [:SLASH, :LITERAL]],
19+
["/page&", [:SLASH, :LITERAL]],
20+
["/page'", [:SLASH, :LITERAL]],
21+
["/page*", [:SLASH, :LITERAL]],
22+
["/page+", [:SLASH, :LITERAL]],
23+
["/page,", [:SLASH, :LITERAL]],
24+
["/page;", [:SLASH, :LITERAL]],
25+
["/page=", [:SLASH, :LITERAL]],
26+
["/page@", [:SLASH, :LITERAL]],
27+
['/page\:', [:SLASH, :LITERAL]],
28+
['/page\(', [:SLASH, :LITERAL]],
29+
['/page\)', [:SLASH, :LITERAL]],
30+
["/~page", [:SLASH, :LITERAL]],
31+
["/pa-ge", [:SLASH, :LITERAL]],
32+
["/:page", [:SLASH, :SYMBOL]],
3333
["/:page|*foo", [
34-
[:SLASH, "/"],
35-
[:SYMBOL, ":page"],
36-
[:OR, "|"],
37-
[:STAR, "*foo"]
34+
:SLASH,
35+
:SYMBOL,
36+
:OR,
37+
:STAR
3838
]],
3939
["/(:page)", [
40-
[:SLASH, "/"],
41-
[:LPAREN, "("],
42-
[:SYMBOL, ":page"],
43-
[:RPAREN, ")"],
40+
:SLASH,
41+
:LPAREN,
42+
:SYMBOL,
43+
:RPAREN,
4444
]],
4545
["(/:action)", [
46-
[:LPAREN, "("],
47-
[:SLASH, "/"],
48-
[:SYMBOL, ":action"],
49-
[:RPAREN, ")"],
46+
:LPAREN,
47+
:SLASH,
48+
:SYMBOL,
49+
:RPAREN,
5050
]],
51-
["(())", [[:LPAREN, "("],
52-
[:LPAREN, "("], [:RPAREN, ")"], [:RPAREN, ")"]]],
51+
["(())", [
52+
:LPAREN,
53+
:LPAREN,
54+
:RPAREN,
55+
:RPAREN,
56+
]],
5357
["(.:format)", [
54-
[:LPAREN, "("],
55-
[:DOT, "."],
56-
[:SYMBOL, ":format"],
57-
[:RPAREN, ")"],
58+
:LPAREN,
59+
:DOT,
60+
:SYMBOL,
61+
:RPAREN,
5862
]],
5963
]
6064

0 commit comments

Comments
 (0)