andyjeffries-journey 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +8 -0
- data/.gemtest +0 -0
- data/CHANGELOG.rdoc +6 -0
- data/Gemfile +11 -0
- data/Manifest.txt +49 -0
- data/README.rdoc +48 -0
- data/Rakefile +31 -0
- data/journey.gemspec +42 -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/strexp.rb +22 -0
- data/lib/journey/router/utils.rb +57 -0
- data/lib/journey/router.rb +138 -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/lib/journey.rb +5 -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 +38 -0
- metadata +171 -0
@@ -0,0 +1,134 @@
|
|
1
|
+
function tokenize(input, callback) {
|
2
|
+
while(input.length > 0) {
|
3
|
+
callback(input.match(/^[\/\.\?]|[^\/\.\?]+/)[0]);
|
4
|
+
input = input.replace(/^[\/\.\?]|[^\/\.\?]+/, '');
|
5
|
+
}
|
6
|
+
}
|
7
|
+
|
8
|
+
var graph = d3.select("#chart-2 svg");
|
9
|
+
var svg_edges = {};
|
10
|
+
var svg_nodes = {};
|
11
|
+
|
12
|
+
graph.selectAll("g.edge").each(function() {
|
13
|
+
var node = d3.select(this);
|
14
|
+
var index = node.select("title").text().split("->");
|
15
|
+
var left = parseInt(index[0]);
|
16
|
+
var right = parseInt(index[1]);
|
17
|
+
|
18
|
+
if(!svg_edges[left]) { svg_edges[left] = {} }
|
19
|
+
svg_edges[left][right] = node;
|
20
|
+
});
|
21
|
+
|
22
|
+
graph.selectAll("g.node").each(function() {
|
23
|
+
var node = d3.select(this);
|
24
|
+
var index = parseInt(node.select("title").text());
|
25
|
+
svg_nodes[index] = node;
|
26
|
+
});
|
27
|
+
|
28
|
+
function reset_graph() {
|
29
|
+
for(var key in svg_edges) {
|
30
|
+
for(var mkey in svg_edges[key]) {
|
31
|
+
var node = svg_edges[key][mkey];
|
32
|
+
var path = node.select("path");
|
33
|
+
var arrow = node.select("polygon");
|
34
|
+
path.style("stroke", "black");
|
35
|
+
arrow.style("stroke", "black").style("fill", "black");
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
for(var key in svg_nodes) {
|
40
|
+
var node = svg_nodes[key];
|
41
|
+
node.select('ellipse').style("fill", "white");
|
42
|
+
node.select('polygon').style("fill", "white");
|
43
|
+
}
|
44
|
+
return false;
|
45
|
+
}
|
46
|
+
|
47
|
+
function highlight_edge(from, to) {
|
48
|
+
var node = svg_edges[from][to];
|
49
|
+
var path = node.select("path");
|
50
|
+
var arrow = node.select("polygon");
|
51
|
+
|
52
|
+
path
|
53
|
+
.transition().duration(500)
|
54
|
+
.style("stroke", "green");
|
55
|
+
|
56
|
+
arrow
|
57
|
+
.transition().duration(500)
|
58
|
+
.style("stroke", "green").style("fill", "green");
|
59
|
+
}
|
60
|
+
|
61
|
+
function highlight_state(index, color) {
|
62
|
+
if(!color) { color = "green"; }
|
63
|
+
|
64
|
+
svg_nodes[index].select('ellipse')
|
65
|
+
.style("fill", "white")
|
66
|
+
.transition().duration(500)
|
67
|
+
.style("fill", color);
|
68
|
+
}
|
69
|
+
|
70
|
+
function highlight_finish(index) {
|
71
|
+
svg_nodes[index].select('polygon')
|
72
|
+
.style("fill", "while")
|
73
|
+
.transition().duration(500)
|
74
|
+
.style("fill", "blue");
|
75
|
+
}
|
76
|
+
|
77
|
+
function match(input) {
|
78
|
+
reset_graph();
|
79
|
+
var table = tt();
|
80
|
+
var states = [0];
|
81
|
+
var regexp_states = table['regexp_states'];
|
82
|
+
var string_states = table['string_states'];
|
83
|
+
var accepting = table['accepting'];
|
84
|
+
|
85
|
+
highlight_state(0);
|
86
|
+
|
87
|
+
tokenize(input, function(token) {
|
88
|
+
var new_states = [];
|
89
|
+
for(var key in states) {
|
90
|
+
var state = states[key];
|
91
|
+
|
92
|
+
if(string_states[state] && string_states[state][token]) {
|
93
|
+
var new_state = string_states[state][token];
|
94
|
+
highlight_edge(state, new_state);
|
95
|
+
highlight_state(new_state);
|
96
|
+
new_states.push(new_state);
|
97
|
+
}
|
98
|
+
|
99
|
+
if(regexp_states[state]) {
|
100
|
+
for(var key in regexp_states[state]) {
|
101
|
+
var re = new RegExp("^" + key + "$");
|
102
|
+
if(re.test(token)) {
|
103
|
+
var new_state = regexp_states[state][key];
|
104
|
+
highlight_edge(state, new_state);
|
105
|
+
highlight_state(new_state);
|
106
|
+
new_states.push(new_state);
|
107
|
+
}
|
108
|
+
}
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
if(new_states.length == 0) {
|
113
|
+
return;
|
114
|
+
}
|
115
|
+
states = new_states;
|
116
|
+
});
|
117
|
+
|
118
|
+
for(var key in states) {
|
119
|
+
var state = states[key];
|
120
|
+
if(accepting[state]) {
|
121
|
+
for(var mkey in svg_edges[state]) {
|
122
|
+
if(!regexp_states[mkey] && !string_states[mkey]) {
|
123
|
+
highlight_edge(state, mkey);
|
124
|
+
highlight_finish(mkey);
|
125
|
+
}
|
126
|
+
}
|
127
|
+
} else {
|
128
|
+
highlight_state(state, "red");
|
129
|
+
}
|
130
|
+
}
|
131
|
+
|
132
|
+
return false;
|
133
|
+
}
|
134
|
+
|
@@ -0,0 +1,50 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title><%= title %></title>
|
5
|
+
<style>
|
6
|
+
<% stylesheets.each do |style| %>
|
7
|
+
<%= style %>
|
8
|
+
<% end %>
|
9
|
+
</style>
|
10
|
+
</head>
|
11
|
+
<body>
|
12
|
+
<div id="wrapper">
|
13
|
+
<h1>Routes FSM with NFA simulation</h1>
|
14
|
+
<div class="instruction form">
|
15
|
+
<p>
|
16
|
+
Type a route in to the box and click "simulate".
|
17
|
+
</p>
|
18
|
+
<form onsubmit="return match(this.route.value);">
|
19
|
+
<input type="text" size="30" name="route" value="/articles/new" />
|
20
|
+
<button>simulate</button>
|
21
|
+
<input type="reset" value="reset" onclick="return reset_graph();"/>
|
22
|
+
</form>
|
23
|
+
<p class="fun_routes">
|
24
|
+
Some fun routes to try:
|
25
|
+
<% fun_routes.each do |path| %>
|
26
|
+
<a href="#" onclick="document.forms[0].elements[0].value=this.text.replace(/^\s+|\s+$/g,''); return match(this.text.replace(/^\s+|\s+$/g,''));">
|
27
|
+
<%= path %>
|
28
|
+
</a>
|
29
|
+
<% end %>
|
30
|
+
</p>
|
31
|
+
</div>
|
32
|
+
<div class='chart' id='chart-2'>
|
33
|
+
<%= svg %>
|
34
|
+
</div>
|
35
|
+
<div class="instruction">
|
36
|
+
<p>
|
37
|
+
This is a FSM for a system that has the following routes:
|
38
|
+
</p>
|
39
|
+
<ul>
|
40
|
+
<% paths.each do |route| %>
|
41
|
+
<li><%= route %></li>
|
42
|
+
<% end %>
|
43
|
+
</ul>
|
44
|
+
</div>
|
45
|
+
</div>
|
46
|
+
<% javascripts.each do |js| %>
|
47
|
+
<script><%= js %></script>
|
48
|
+
<% end %>
|
49
|
+
</body>
|
50
|
+
</html>
|
@@ -0,0 +1,48 @@
|
|
1
|
+
/* http://meyerweb.com/eric/tools/css/reset/
|
2
|
+
v2.0 | 20110126
|
3
|
+
License: none (public domain)
|
4
|
+
*/
|
5
|
+
|
6
|
+
html, body, div, span, applet, object, iframe,
|
7
|
+
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
8
|
+
a, abbr, acronym, address, big, cite, code,
|
9
|
+
del, dfn, em, img, ins, kbd, q, s, samp,
|
10
|
+
small, strike, strong, sub, sup, tt, var,
|
11
|
+
b, u, i, center,
|
12
|
+
dl, dt, dd, ol, ul, li,
|
13
|
+
fieldset, form, label, legend,
|
14
|
+
table, caption, tbody, tfoot, thead, tr, th, td,
|
15
|
+
article, aside, canvas, details, embed,
|
16
|
+
figure, figcaption, footer, header, hgroup,
|
17
|
+
menu, nav, output, ruby, section, summary,
|
18
|
+
time, mark, audio, video {
|
19
|
+
margin: 0;
|
20
|
+
padding: 0;
|
21
|
+
border: 0;
|
22
|
+
font-size: 100%;
|
23
|
+
font: inherit;
|
24
|
+
vertical-align: baseline;
|
25
|
+
}
|
26
|
+
/* HTML5 display-role reset for older browsers */
|
27
|
+
article, aside, details, figcaption, figure,
|
28
|
+
footer, header, hgroup, menu, nav, section {
|
29
|
+
display: block;
|
30
|
+
}
|
31
|
+
body {
|
32
|
+
line-height: 1;
|
33
|
+
}
|
34
|
+
ol, ul {
|
35
|
+
list-style: none;
|
36
|
+
}
|
37
|
+
blockquote, q {
|
38
|
+
quotes: none;
|
39
|
+
}
|
40
|
+
blockquote:before, blockquote:after,
|
41
|
+
q:before, q:after {
|
42
|
+
content: '';
|
43
|
+
content: none;
|
44
|
+
}
|
45
|
+
table {
|
46
|
+
border-collapse: collapse;
|
47
|
+
border-spacing: 0;
|
48
|
+
}
|
data/lib/journey.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module Journey
|
4
|
+
module GTG
|
5
|
+
class TestBuilder < MiniTest::Unit::TestCase
|
6
|
+
def test_following_states_multi
|
7
|
+
table = tt ['a|a']
|
8
|
+
assert_equal 1, table.move(0, 'a').length
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_following_states_multi_regexp
|
12
|
+
table = tt [':a|b']
|
13
|
+
assert_equal 1, table.move(0, 'fooo').length
|
14
|
+
assert_equal 2, table.move(0, 'b').length
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_multi_path
|
18
|
+
table = tt ['/:a/d', '/b/c']
|
19
|
+
|
20
|
+
[
|
21
|
+
[1, '/'],
|
22
|
+
[2, 'b'],
|
23
|
+
[2, '/'],
|
24
|
+
[1, 'c'],
|
25
|
+
].inject(0) { |state, (exp, sym)|
|
26
|
+
new = table.move(state, sym)
|
27
|
+
assert_equal exp, new.length
|
28
|
+
new
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_match_data_ambiguous
|
33
|
+
table = tt %w{
|
34
|
+
/articles(.:format)
|
35
|
+
/articles/new(.:format)
|
36
|
+
/articles/:id/edit(.:format)
|
37
|
+
/articles/:id(.:format)
|
38
|
+
}
|
39
|
+
|
40
|
+
sim = NFA::Simulator.new table
|
41
|
+
|
42
|
+
match = sim.match '/articles/new'
|
43
|
+
assert_equal 2, match.memos.length
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# Identical Routes may have different restrictions.
|
48
|
+
def test_match_same_paths
|
49
|
+
table = tt %w{
|
50
|
+
/articles/new(.:format)
|
51
|
+
/articles/new(.:format)
|
52
|
+
}
|
53
|
+
|
54
|
+
sim = NFA::Simulator.new table
|
55
|
+
|
56
|
+
match = sim.match '/articles/new'
|
57
|
+
assert_equal 2, match.memos.length
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
def ast strings
|
62
|
+
parser = Journey::Parser.new
|
63
|
+
asts = strings.map { |string|
|
64
|
+
memo = Object.new
|
65
|
+
ast = parser.parse string
|
66
|
+
ast.each { |n| n.memo = memo }
|
67
|
+
ast
|
68
|
+
}
|
69
|
+
Nodes::Or.new asts
|
70
|
+
end
|
71
|
+
|
72
|
+
def tt strings
|
73
|
+
Builder.new(ast(strings)).transition_table
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Journey
|
5
|
+
module GTG
|
6
|
+
class TestGeneralizedTable < MiniTest::Unit::TestCase
|
7
|
+
def test_to_json
|
8
|
+
table = tt %w{
|
9
|
+
/articles(.:format)
|
10
|
+
/articles/new(.:format)
|
11
|
+
/articles/:id/edit(.:format)
|
12
|
+
/articles/:id(.:format)
|
13
|
+
}
|
14
|
+
|
15
|
+
json = JSON.load table.to_json
|
16
|
+
assert json['regexp_states']
|
17
|
+
assert json['string_states']
|
18
|
+
assert json['accepting']
|
19
|
+
end
|
20
|
+
|
21
|
+
if system("dot -V 2>/dev/null")
|
22
|
+
def test_to_svg
|
23
|
+
table = tt %w{
|
24
|
+
/articles(.:format)
|
25
|
+
/articles/new(.:format)
|
26
|
+
/articles/:id/edit(.:format)
|
27
|
+
/articles/:id(.:format)
|
28
|
+
}
|
29
|
+
svg = table.to_svg
|
30
|
+
assert svg
|
31
|
+
refute_match(/DOCTYPE/, svg)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_simulate_gt
|
36
|
+
sim = simulator_for ['/foo', '/bar']
|
37
|
+
assert_match sim, '/foo'
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_simulate_gt_regexp
|
41
|
+
sim = simulator_for [':foo']
|
42
|
+
assert_match sim, 'foo'
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_simulate_gt_regexp_mix
|
46
|
+
sim = simulator_for ['/get', '/:method/foo']
|
47
|
+
assert_match sim, '/get'
|
48
|
+
assert_match sim, '/get/foo'
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_simulate_optional
|
52
|
+
sim = simulator_for ['/foo(/bar)']
|
53
|
+
assert_match sim, '/foo'
|
54
|
+
assert_match sim, '/foo/bar'
|
55
|
+
refute_match sim, '/foo/'
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_match_data
|
59
|
+
path_asts = asts %w{ /get /:method/foo }
|
60
|
+
paths = path_asts.dup
|
61
|
+
|
62
|
+
builder = GTG::Builder.new Nodes::Or.new path_asts
|
63
|
+
tt = builder.transition_table
|
64
|
+
|
65
|
+
sim = GTG::Simulator.new tt
|
66
|
+
|
67
|
+
match = sim.match '/get'
|
68
|
+
assert_equal [paths.first], match.memos
|
69
|
+
|
70
|
+
match = sim.match '/get/foo'
|
71
|
+
assert_equal [paths.last], match.memos
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_match_data_ambiguous
|
75
|
+
path_asts = asts %w{
|
76
|
+
/articles(.:format)
|
77
|
+
/articles/new(.:format)
|
78
|
+
/articles/:id/edit(.:format)
|
79
|
+
/articles/:id(.:format)
|
80
|
+
}
|
81
|
+
|
82
|
+
paths = path_asts.dup
|
83
|
+
ast = Nodes::Or.new path_asts
|
84
|
+
|
85
|
+
builder = GTG::Builder.new ast
|
86
|
+
sim = GTG::Simulator.new builder.transition_table
|
87
|
+
|
88
|
+
match = sim.match '/articles/new'
|
89
|
+
assert_equal [paths[1], paths[3]], match.memos
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
def asts paths
|
94
|
+
parser = Journey::Parser.new
|
95
|
+
paths.map { |x|
|
96
|
+
ast = parser.parse x
|
97
|
+
ast.each { |n| n.memo = ast}
|
98
|
+
ast
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
def tt paths
|
103
|
+
x = asts paths
|
104
|
+
builder = GTG::Builder.new Nodes::Or.new x
|
105
|
+
builder.transition_table
|
106
|
+
end
|
107
|
+
|
108
|
+
def simulator_for paths
|
109
|
+
GTG::Simulator.new tt(paths)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module Journey
|
4
|
+
module NFA
|
5
|
+
class TestSimulator < MiniTest::Unit::TestCase
|
6
|
+
def test_simulate_simple
|
7
|
+
sim = simulator_for ['/foo']
|
8
|
+
assert_match sim, '/foo'
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_simulate_simple_no_match
|
12
|
+
sim = simulator_for ['/foo']
|
13
|
+
refute_match sim, 'foo'
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_simulate_simple_no_match_too_long
|
17
|
+
sim = simulator_for ['/foo']
|
18
|
+
refute_match sim, '/foo/bar'
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_simulate_simple_no_match_wrong_string
|
22
|
+
sim = simulator_for ['/foo']
|
23
|
+
refute_match sim, '/bar'
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_simulate_regex
|
27
|
+
sim = simulator_for ['/:foo/bar']
|
28
|
+
assert_match sim, '/bar/bar'
|
29
|
+
assert_match sim, '/foo/bar'
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_simulate_or
|
33
|
+
sim = simulator_for ['/foo', '/bar']
|
34
|
+
assert_match sim, '/bar'
|
35
|
+
assert_match sim, '/foo'
|
36
|
+
refute_match sim, '/baz'
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_simulate_optional
|
40
|
+
sim = simulator_for ['/foo(/bar)']
|
41
|
+
assert_match sim, '/foo'
|
42
|
+
assert_match sim, '/foo/bar'
|
43
|
+
refute_match sim, '/foo/'
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_matchdata_has_memos
|
47
|
+
paths = %w{ /foo /bar }
|
48
|
+
parser = Journey::Parser.new
|
49
|
+
asts = paths.map { |x|
|
50
|
+
ast = parser.parse x
|
51
|
+
ast.each { |n| n.memo = ast}
|
52
|
+
ast
|
53
|
+
}
|
54
|
+
|
55
|
+
expected = asts.first
|
56
|
+
|
57
|
+
builder = Builder.new Nodes::Or.new asts
|
58
|
+
|
59
|
+
sim = Simulator.new builder.transition_table
|
60
|
+
|
61
|
+
md = sim.match '/foo'
|
62
|
+
assert_equal [expected], md.memos
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_matchdata_memos_on_merge
|
66
|
+
parser = Journey::Parser.new
|
67
|
+
routes = [
|
68
|
+
'/articles(.:format)',
|
69
|
+
'/articles/new(.:format)',
|
70
|
+
'/articles/:id/edit(.:format)',
|
71
|
+
'/articles/:id(.:format)',
|
72
|
+
].map { |path|
|
73
|
+
ast = parser.parse path
|
74
|
+
ast.each { |n| n.memo = ast }
|
75
|
+
ast
|
76
|
+
}
|
77
|
+
|
78
|
+
asts = routes.dup
|
79
|
+
|
80
|
+
ast = Nodes::Or.new routes
|
81
|
+
|
82
|
+
nfa = Journey::NFA::Builder.new ast
|
83
|
+
sim = Simulator.new nfa.transition_table
|
84
|
+
md = sim.match '/articles'
|
85
|
+
assert_equal [asts.first], md.memos
|
86
|
+
end
|
87
|
+
|
88
|
+
def simulator_for paths
|
89
|
+
parser = Journey::Parser.new
|
90
|
+
asts = paths.map { |x| parser.parse x }
|
91
|
+
builder = Builder.new Nodes::Or.new asts
|
92
|
+
Simulator.new builder.transition_table
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module Journey
|
4
|
+
module NFA
|
5
|
+
class TestTransitionTable < MiniTest::Unit::TestCase
|
6
|
+
def setup
|
7
|
+
@parser = Journey::Parser.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_eclosure
|
11
|
+
table = tt '/'
|
12
|
+
assert_equal [0], table.eclosure(0)
|
13
|
+
|
14
|
+
table = tt ':a|:b'
|
15
|
+
assert_equal 3, table.eclosure(0).length
|
16
|
+
|
17
|
+
table = tt '(:a|:b)'
|
18
|
+
assert_equal 5, table.eclosure(0).length
|
19
|
+
assert_equal 5, table.eclosure([0]).length
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_following_states_one
|
23
|
+
table = tt '/'
|
24
|
+
|
25
|
+
assert_equal [1], table.following_states(0, '/')
|
26
|
+
assert_equal [1], table.following_states([0], '/')
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_following_states_group
|
30
|
+
table = tt 'a|b'
|
31
|
+
states = table.eclosure 0
|
32
|
+
|
33
|
+
assert_equal 1, table.following_states(states, 'a').length
|
34
|
+
assert_equal 1, table.following_states(states, 'b').length
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_following_states_multi
|
38
|
+
table = tt 'a|a'
|
39
|
+
states = table.eclosure 0
|
40
|
+
|
41
|
+
assert_equal 2, table.following_states(states, 'a').length
|
42
|
+
assert_equal 0, table.following_states(states, 'b').length
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_following_states_regexp
|
46
|
+
table = tt 'a|:a'
|
47
|
+
states = table.eclosure 0
|
48
|
+
|
49
|
+
assert_equal 1, table.following_states(states, 'a').length
|
50
|
+
assert_equal 1, table.following_states(states, /[^\.\/\?]+/).length
|
51
|
+
assert_equal 0, table.following_states(states, 'b').length
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_alphabet
|
55
|
+
table = tt 'a|:a'
|
56
|
+
assert_equal ['a', /[^\.\/\?]+/], table.alphabet
|
57
|
+
|
58
|
+
table = tt 'a|a'
|
59
|
+
assert_equal ['a'], table.alphabet
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
def tt string
|
64
|
+
ast = @parser.parse string
|
65
|
+
builder = Builder.new ast
|
66
|
+
builder.transition_table
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module Journey
|
4
|
+
module Nodes
|
5
|
+
class TestSymbol < MiniTest::Unit::TestCase
|
6
|
+
def test_default_regexp?
|
7
|
+
sym = Symbol.new nil
|
8
|
+
assert sym.default_regexp?
|
9
|
+
|
10
|
+
sym.regexp = nil
|
11
|
+
refute sym.default_regexp?
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|