journey 1.0.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.autotest +8 -0
- data/.gemtest +0 -0
- data/CHANGELOG.rdoc +6 -0
- data/Gemfile +11 -0
- data/Manifest.txt +50 -0
- data/README.rdoc +48 -0
- data/Rakefile +31 -0
- data/journey.gemspec +42 -0
- data/lib/journey.rb +5 -0
- data/lib/journey/backwards.rb +5 -0
- data/lib/journey/core-ext/hash.rb +11 -0
- data/lib/journey/formatter.rb +129 -0
- data/lib/journey/gtg/builder.rb +159 -0
- data/lib/journey/gtg/simulator.rb +44 -0
- data/lib/journey/gtg/transition_table.rb +152 -0
- data/lib/journey/nfa/builder.rb +74 -0
- data/lib/journey/nfa/dot.rb +34 -0
- data/lib/journey/nfa/simulator.rb +45 -0
- data/lib/journey/nfa/transition_table.rb +164 -0
- data/lib/journey/nodes/node.rb +104 -0
- data/lib/journey/parser.rb +204 -0
- data/lib/journey/parser.y +47 -0
- data/lib/journey/parser_extras.rb +21 -0
- data/lib/journey/path/pattern.rb +190 -0
- data/lib/journey/route.rb +92 -0
- data/lib/journey/router.rb +138 -0
- data/lib/journey/router/strexp.rb +22 -0
- data/lib/journey/router/utils.rb +57 -0
- data/lib/journey/routes.rb +74 -0
- data/lib/journey/scanner.rb +58 -0
- data/lib/journey/visitors.rb +186 -0
- data/lib/journey/visualizer/d3.min.js +2 -0
- data/lib/journey/visualizer/fsm.css +34 -0
- data/lib/journey/visualizer/fsm.js +134 -0
- data/lib/journey/visualizer/index.html.erb +50 -0
- data/lib/journey/visualizer/reset.css +48 -0
- data/test/gtg/test_builder.rb +77 -0
- data/test/gtg/test_transition_table.rb +113 -0
- data/test/helper.rb +4 -0
- data/test/nfa/test_simulator.rb +96 -0
- data/test/nfa/test_transition_table.rb +70 -0
- data/test/nodes/test_symbol.rb +15 -0
- data/test/path/test_pattern.rb +260 -0
- data/test/route/definition/test_parser.rb +108 -0
- data/test/route/definition/test_scanner.rb +52 -0
- data/test/router/test_strexp.rb +30 -0
- data/test/router/test_utils.rb +19 -0
- data/test/test_route.rb +95 -0
- data/test/test_router.rb +464 -0
- data/test/test_routes.rb +51 -0
- metadata +168 -0
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'journey/visitors'
|
2
|
+
|
3
|
+
module Journey
|
4
|
+
module Nodes
|
5
|
+
class Node # :nodoc:
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
attr_accessor :left, :memo
|
9
|
+
|
10
|
+
def initialize left
|
11
|
+
@left = left
|
12
|
+
@memo = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def each(&block)
|
16
|
+
Visitors::Each.new(block).accept(self)
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
Visitors::String.new.accept(self)
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_dot
|
24
|
+
Visitors::Dot.new.accept(self)
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_sym
|
28
|
+
name.to_sym
|
29
|
+
end
|
30
|
+
|
31
|
+
def name
|
32
|
+
left.tr ':', ''
|
33
|
+
end
|
34
|
+
|
35
|
+
def type
|
36
|
+
raise NotImplementedError
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Terminal < Node
|
41
|
+
alias :symbol :left
|
42
|
+
end
|
43
|
+
|
44
|
+
%w{ Symbol Slash Literal Dot }.each do |t|
|
45
|
+
class_eval %{
|
46
|
+
class #{t} < Terminal
|
47
|
+
def type; :#{t.upcase}; end
|
48
|
+
end
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
class Symbol < Terminal
|
53
|
+
attr_accessor :regexp
|
54
|
+
alias :symbol :regexp
|
55
|
+
|
56
|
+
DEFAULT_EXP = /[^\.\/\?]+/
|
57
|
+
def initialize left
|
58
|
+
super
|
59
|
+
@regexp = DEFAULT_EXP
|
60
|
+
end
|
61
|
+
|
62
|
+
def default_regexp?
|
63
|
+
regexp == DEFAULT_EXP
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class Unary < Node
|
68
|
+
def children; [value] end
|
69
|
+
end
|
70
|
+
|
71
|
+
class Group < Unary
|
72
|
+
def type; :GROUP; end
|
73
|
+
end
|
74
|
+
|
75
|
+
class Star < Unary
|
76
|
+
def type; :STAR; end
|
77
|
+
end
|
78
|
+
|
79
|
+
class Binary < Node
|
80
|
+
attr_accessor :right
|
81
|
+
|
82
|
+
def initialize left, right
|
83
|
+
super(left)
|
84
|
+
@right = right
|
85
|
+
end
|
86
|
+
|
87
|
+
def children; [left, right] end
|
88
|
+
end
|
89
|
+
|
90
|
+
class Cat < Binary
|
91
|
+
def type; :CAT; end
|
92
|
+
end
|
93
|
+
|
94
|
+
class Or < Node
|
95
|
+
attr_reader :children
|
96
|
+
|
97
|
+
def initialize children
|
98
|
+
@children = children
|
99
|
+
end
|
100
|
+
|
101
|
+
def type; :OR; end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
#
|
2
|
+
# DO NOT MODIFY!!!!
|
3
|
+
# This file is automatically generated by Racc 1.4.6
|
4
|
+
# from Racc grammer file "".
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'racc/parser.rb'
|
8
|
+
|
9
|
+
|
10
|
+
require 'journey/parser_extras'
|
11
|
+
module Journey
|
12
|
+
class Parser < Racc::Parser
|
13
|
+
##### State transition tables begin ###
|
14
|
+
|
15
|
+
racc_action_table = [
|
16
|
+
17, 22, 13, 15, 14, 7, 15, 16, 8, 19,
|
17
|
+
13, 15, 14, 7, 24, 16, 8, 19, 13, 15,
|
18
|
+
14, 7, nil, 16, 8, 13, 15, 14, 7, nil,
|
19
|
+
16, 8, 13, 15, 14, 7, nil, 16, 8 ]
|
20
|
+
|
21
|
+
racc_action_check = [
|
22
|
+
1, 17, 1, 1, 1, 1, 8, 1, 1, 1,
|
23
|
+
20, 20, 20, 20, 20, 20, 20, 20, 7, 7,
|
24
|
+
7, 7, nil, 7, 7, 19, 19, 19, 19, nil,
|
25
|
+
19, 19, 0, 0, 0, 0, nil, 0, 0 ]
|
26
|
+
|
27
|
+
racc_action_pointer = [
|
28
|
+
30, 0, nil, nil, nil, nil, nil, 16, 3, nil,
|
29
|
+
nil, nil, nil, nil, nil, nil, nil, 1, nil, 23,
|
30
|
+
8, nil, nil, nil, nil ]
|
31
|
+
|
32
|
+
racc_action_default = [
|
33
|
+
-18, -18, -2, -3, -4, -5, -6, -18, -18, -10,
|
34
|
+
-11, -12, -13, -14, -15, -16, -17, -18, -1, -18,
|
35
|
+
-18, -9, 25, -8, -7 ]
|
36
|
+
|
37
|
+
racc_goto_table = [
|
38
|
+
18, 1, 21, nil, nil, nil, nil, nil, 20, nil,
|
39
|
+
nil, nil, nil, nil, nil, nil, nil, nil, 23, 18 ]
|
40
|
+
|
41
|
+
racc_goto_check = [
|
42
|
+
2, 1, 7, nil, nil, nil, nil, nil, 1, nil,
|
43
|
+
nil, nil, nil, nil, nil, nil, nil, nil, 2, 2 ]
|
44
|
+
|
45
|
+
racc_goto_pointer = [
|
46
|
+
nil, 1, -1, nil, nil, nil, nil, -6, nil, nil,
|
47
|
+
nil ]
|
48
|
+
|
49
|
+
racc_goto_default = [
|
50
|
+
nil, nil, 2, 3, 4, 5, 6, 10, 9, 11,
|
51
|
+
12 ]
|
52
|
+
|
53
|
+
racc_reduce_table = [
|
54
|
+
0, 0, :racc_error,
|
55
|
+
2, 11, :_reduce_1,
|
56
|
+
1, 11, :_reduce_2,
|
57
|
+
1, 11, :_reduce_none,
|
58
|
+
1, 12, :_reduce_none,
|
59
|
+
1, 12, :_reduce_none,
|
60
|
+
1, 12, :_reduce_none,
|
61
|
+
3, 15, :_reduce_7,
|
62
|
+
3, 13, :_reduce_8,
|
63
|
+
2, 16, :_reduce_9,
|
64
|
+
1, 14, :_reduce_none,
|
65
|
+
1, 14, :_reduce_none,
|
66
|
+
1, 14, :_reduce_none,
|
67
|
+
1, 14, :_reduce_none,
|
68
|
+
1, 19, :_reduce_14,
|
69
|
+
1, 18, :_reduce_15,
|
70
|
+
1, 17, :_reduce_16,
|
71
|
+
1, 20, :_reduce_17 ]
|
72
|
+
|
73
|
+
racc_reduce_n = 18
|
74
|
+
|
75
|
+
racc_shift_n = 25
|
76
|
+
|
77
|
+
racc_token_table = {
|
78
|
+
false => 0,
|
79
|
+
:error => 1,
|
80
|
+
:SLASH => 2,
|
81
|
+
:LITERAL => 3,
|
82
|
+
:SYMBOL => 4,
|
83
|
+
:LPAREN => 5,
|
84
|
+
:RPAREN => 6,
|
85
|
+
:DOT => 7,
|
86
|
+
:STAR => 8,
|
87
|
+
:OR => 9 }
|
88
|
+
|
89
|
+
racc_nt_base = 10
|
90
|
+
|
91
|
+
racc_use_result_var = true
|
92
|
+
|
93
|
+
Racc_arg = [
|
94
|
+
racc_action_table,
|
95
|
+
racc_action_check,
|
96
|
+
racc_action_default,
|
97
|
+
racc_action_pointer,
|
98
|
+
racc_goto_table,
|
99
|
+
racc_goto_check,
|
100
|
+
racc_goto_default,
|
101
|
+
racc_goto_pointer,
|
102
|
+
racc_nt_base,
|
103
|
+
racc_reduce_table,
|
104
|
+
racc_token_table,
|
105
|
+
racc_shift_n,
|
106
|
+
racc_reduce_n,
|
107
|
+
racc_use_result_var ]
|
108
|
+
|
109
|
+
Racc_token_to_s_table = [
|
110
|
+
"$end",
|
111
|
+
"error",
|
112
|
+
"SLASH",
|
113
|
+
"LITERAL",
|
114
|
+
"SYMBOL",
|
115
|
+
"LPAREN",
|
116
|
+
"RPAREN",
|
117
|
+
"DOT",
|
118
|
+
"STAR",
|
119
|
+
"OR",
|
120
|
+
"$start",
|
121
|
+
"expressions",
|
122
|
+
"expression",
|
123
|
+
"or",
|
124
|
+
"terminal",
|
125
|
+
"group",
|
126
|
+
"star",
|
127
|
+
"literal",
|
128
|
+
"symbol",
|
129
|
+
"slash",
|
130
|
+
"dot" ]
|
131
|
+
|
132
|
+
Racc_debug_parser = false
|
133
|
+
|
134
|
+
##### State transition tables end #####
|
135
|
+
|
136
|
+
# reduce 0 omitted
|
137
|
+
|
138
|
+
def _reduce_1(val, _values, result)
|
139
|
+
result = Cat.new(val.first, val.last)
|
140
|
+
result
|
141
|
+
end
|
142
|
+
|
143
|
+
def _reduce_2(val, _values, result)
|
144
|
+
result = val.first
|
145
|
+
result
|
146
|
+
end
|
147
|
+
|
148
|
+
# reduce 3 omitted
|
149
|
+
|
150
|
+
# reduce 4 omitted
|
151
|
+
|
152
|
+
# reduce 5 omitted
|
153
|
+
|
154
|
+
# reduce 6 omitted
|
155
|
+
|
156
|
+
def _reduce_7(val, _values, result)
|
157
|
+
result = Group.new(val[1])
|
158
|
+
result
|
159
|
+
end
|
160
|
+
|
161
|
+
def _reduce_8(val, _values, result)
|
162
|
+
result = Or.new([val.first, val.last])
|
163
|
+
result
|
164
|
+
end
|
165
|
+
|
166
|
+
def _reduce_9(val, _values, result)
|
167
|
+
result = Star.new(Symbol.new(val.last.left))
|
168
|
+
result
|
169
|
+
end
|
170
|
+
|
171
|
+
# reduce 10 omitted
|
172
|
+
|
173
|
+
# reduce 11 omitted
|
174
|
+
|
175
|
+
# reduce 12 omitted
|
176
|
+
|
177
|
+
# reduce 13 omitted
|
178
|
+
|
179
|
+
def _reduce_14(val, _values, result)
|
180
|
+
result = Slash.new('/')
|
181
|
+
result
|
182
|
+
end
|
183
|
+
|
184
|
+
def _reduce_15(val, _values, result)
|
185
|
+
result = Symbol.new(val.first)
|
186
|
+
result
|
187
|
+
end
|
188
|
+
|
189
|
+
def _reduce_16(val, _values, result)
|
190
|
+
result = Literal.new(val.first)
|
191
|
+
result
|
192
|
+
end
|
193
|
+
|
194
|
+
def _reduce_17(val, _values, result)
|
195
|
+
result = Dot.new(val.first)
|
196
|
+
result
|
197
|
+
end
|
198
|
+
|
199
|
+
def _reduce_none(val, _values, result)
|
200
|
+
val[0]
|
201
|
+
end
|
202
|
+
|
203
|
+
end # class Parser
|
204
|
+
end # module Journey
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Journey::Parser
|
2
|
+
|
3
|
+
token SLASH LITERAL SYMBOL LPAREN RPAREN DOT STAR OR
|
4
|
+
|
5
|
+
rule
|
6
|
+
expressions
|
7
|
+
: expressions expression { result = Cat.new(val.first, val.last) }
|
8
|
+
| expression { result = val.first }
|
9
|
+
| or
|
10
|
+
;
|
11
|
+
expression
|
12
|
+
: terminal
|
13
|
+
| group
|
14
|
+
| star
|
15
|
+
;
|
16
|
+
group
|
17
|
+
: LPAREN expressions RPAREN { result = Group.new(val[1]) }
|
18
|
+
;
|
19
|
+
or
|
20
|
+
: expressions OR expression { result = Or.new([val.first, val.last]) }
|
21
|
+
;
|
22
|
+
star
|
23
|
+
: STAR literal { result = Star.new(Symbol.new(val.last.left)) }
|
24
|
+
;
|
25
|
+
terminal
|
26
|
+
: symbol
|
27
|
+
| literal
|
28
|
+
| slash
|
29
|
+
| dot
|
30
|
+
;
|
31
|
+
slash
|
32
|
+
: SLASH { result = Slash.new('/') }
|
33
|
+
;
|
34
|
+
symbol
|
35
|
+
: SYMBOL { result = Symbol.new(val.first) }
|
36
|
+
;
|
37
|
+
literal
|
38
|
+
: LITERAL { result = Literal.new(val.first) }
|
39
|
+
dot
|
40
|
+
: DOT { result = Dot.new(val.first) }
|
41
|
+
;
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
---- header
|
46
|
+
|
47
|
+
require 'journey/parser_extras'
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'journey/scanner'
|
2
|
+
require 'journey/nodes/node'
|
3
|
+
|
4
|
+
module Journey
|
5
|
+
class Parser < Racc::Parser
|
6
|
+
include Journey::Nodes
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@scanner = Scanner.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse string
|
13
|
+
@scanner.scan_setup string
|
14
|
+
do_parse
|
15
|
+
end
|
16
|
+
|
17
|
+
def next_token
|
18
|
+
@scanner.next_token
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
module Journey
|
2
|
+
module Path
|
3
|
+
class Pattern
|
4
|
+
attr_reader :spec, :requirements, :anchored
|
5
|
+
|
6
|
+
def initialize strexp
|
7
|
+
parser = Journey::Parser.new
|
8
|
+
|
9
|
+
@anchored = true
|
10
|
+
|
11
|
+
case strexp
|
12
|
+
when String
|
13
|
+
@spec = parser.parse strexp
|
14
|
+
@requirements = {}
|
15
|
+
@separators = "/.?"
|
16
|
+
when Router::Strexp
|
17
|
+
@spec = parser.parse strexp.path
|
18
|
+
@requirements = strexp.requirements
|
19
|
+
@separators = strexp.separators.join
|
20
|
+
@anchored = strexp.anchor
|
21
|
+
else
|
22
|
+
raise "wtf bro: #{strexp}"
|
23
|
+
end
|
24
|
+
|
25
|
+
@names = nil
|
26
|
+
@optional_names = nil
|
27
|
+
@required_names = nil
|
28
|
+
@re = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def ast
|
32
|
+
@spec.grep(Nodes::Symbol).each do |node|
|
33
|
+
re = @requirements[node.to_sym]
|
34
|
+
node.regexp = re if re
|
35
|
+
end
|
36
|
+
|
37
|
+
@spec.grep(Nodes::Star).each do |node|
|
38
|
+
node = node.left
|
39
|
+
node.regexp = @requirements[node.to_sym] || /(.+)/
|
40
|
+
end
|
41
|
+
|
42
|
+
@spec
|
43
|
+
end
|
44
|
+
|
45
|
+
def names
|
46
|
+
@names ||= spec.grep(Nodes::Symbol).map { |n| n.name }
|
47
|
+
end
|
48
|
+
|
49
|
+
def required_names
|
50
|
+
@required_names ||= names - optional_names
|
51
|
+
end
|
52
|
+
|
53
|
+
def optional_names
|
54
|
+
@optional_names ||= spec.grep(Nodes::Group).map { |group|
|
55
|
+
group.grep(Nodes::Symbol)
|
56
|
+
}.flatten.map { |n| n.name }.uniq
|
57
|
+
end
|
58
|
+
|
59
|
+
class RegexpOffsets < Journey::Visitors::Visitor # :nodoc:
|
60
|
+
attr_reader :offsets
|
61
|
+
|
62
|
+
def initialize matchers
|
63
|
+
@matchers = matchers
|
64
|
+
@capture_count = [0]
|
65
|
+
end
|
66
|
+
|
67
|
+
def visit node
|
68
|
+
super
|
69
|
+
@capture_count
|
70
|
+
end
|
71
|
+
|
72
|
+
def visit_SYMBOL node
|
73
|
+
node = node.to_sym
|
74
|
+
|
75
|
+
if @matchers.key? node
|
76
|
+
re = /#{@matchers[node]}|/
|
77
|
+
@capture_count.push((re.match('').length - 1) + (@capture_count.last || 0))
|
78
|
+
else
|
79
|
+
@capture_count << (@capture_count.last || 0)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class AnchoredRegexp < Journey::Visitors::Visitor # :nodoc:
|
85
|
+
def initialize separator, matchers
|
86
|
+
@separator = separator
|
87
|
+
@matchers = matchers
|
88
|
+
@separator_re = "([^#{separator}]+)"
|
89
|
+
super()
|
90
|
+
end
|
91
|
+
|
92
|
+
def accept node
|
93
|
+
%r{\A#{visit node}\Z}
|
94
|
+
end
|
95
|
+
|
96
|
+
def visit_CAT node
|
97
|
+
[visit(node.left), visit(node.right)].join
|
98
|
+
end
|
99
|
+
|
100
|
+
def visit_SYMBOL node
|
101
|
+
node = node.to_sym
|
102
|
+
|
103
|
+
return @separator_re unless @matchers.key? node
|
104
|
+
|
105
|
+
re = @matchers[node]
|
106
|
+
# FIXME: is the question mark needed?
|
107
|
+
"(#{re}?)"
|
108
|
+
end
|
109
|
+
|
110
|
+
def visit_GROUP node
|
111
|
+
"(?:#{visit node.left})?"
|
112
|
+
end
|
113
|
+
|
114
|
+
def visit_LITERAL node
|
115
|
+
Regexp.escape node.left
|
116
|
+
end
|
117
|
+
alias :visit_DOT :visit_LITERAL
|
118
|
+
|
119
|
+
def visit_SLASH node
|
120
|
+
node.left
|
121
|
+
end
|
122
|
+
|
123
|
+
def visit_STAR node
|
124
|
+
"(.+)"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
class UnanchoredRegexp < AnchoredRegexp # :nodoc:
|
129
|
+
def accept node
|
130
|
+
%r{\A#{visit node}}
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
class MatchData
|
135
|
+
attr_reader :names
|
136
|
+
|
137
|
+
def initialize names, offsets, match
|
138
|
+
@names = names
|
139
|
+
@offsets = offsets
|
140
|
+
@match = match
|
141
|
+
end
|
142
|
+
|
143
|
+
def captures
|
144
|
+
(length - 1).times.map { |i| self[i + 1] }
|
145
|
+
end
|
146
|
+
|
147
|
+
def [] x
|
148
|
+
idx = @offsets[x - 1] + x
|
149
|
+
@match[idx]
|
150
|
+
end
|
151
|
+
|
152
|
+
def length
|
153
|
+
@offsets.length
|
154
|
+
end
|
155
|
+
|
156
|
+
def post_match
|
157
|
+
@match.post_match
|
158
|
+
end
|
159
|
+
|
160
|
+
def to_s
|
161
|
+
@match.to_s
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def match other
|
166
|
+
return unless match = to_regexp.match(other)
|
167
|
+
MatchData.new names, offsets, match
|
168
|
+
end
|
169
|
+
alias :=~ :match
|
170
|
+
|
171
|
+
def source
|
172
|
+
to_regexp.source
|
173
|
+
end
|
174
|
+
|
175
|
+
private
|
176
|
+
def to_regexp
|
177
|
+
@re ||= regexp_visitor.new(@separators, @requirements).accept spec
|
178
|
+
end
|
179
|
+
|
180
|
+
def regexp_visitor
|
181
|
+
@anchored ? AnchoredRegexp : UnanchoredRegexp
|
182
|
+
end
|
183
|
+
|
184
|
+
def offsets
|
185
|
+
viz = RegexpOffsets.new @requirements
|
186
|
+
viz.accept spec
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|