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
data/.autotest
ADDED
data/.gemtest
ADDED
File without changes
|
data/CHANGELOG.rdoc
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
# DO NOT EDIT THIS FILE. Instead, edit Rakefile, and run `rake bundler:gemfile`.
|
4
|
+
|
5
|
+
source :gemcutter
|
6
|
+
|
7
|
+
|
8
|
+
gem "minitest", "~>2.5", :group => [:development, :test]
|
9
|
+
gem "hoe", "~>2.10", :group => [:development, :test]
|
10
|
+
|
11
|
+
# vim: syntax=ruby
|
data/Manifest.txt
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
.autotest
|
2
|
+
CHANGELOG.rdoc
|
3
|
+
Gemfile
|
4
|
+
Manifest.txt
|
5
|
+
README.rdoc
|
6
|
+
Rakefile
|
7
|
+
journey.gemspec
|
8
|
+
lib/journey.rb
|
9
|
+
lib/journey/backwards.rb
|
10
|
+
lib/journey/core-ext/hash.rb
|
11
|
+
lib/journey/formatter.rb
|
12
|
+
lib/journey/gtg/builder.rb
|
13
|
+
lib/journey/gtg/simulator.rb
|
14
|
+
lib/journey/gtg/transition_table.rb
|
15
|
+
lib/journey/nfa/builder.rb
|
16
|
+
lib/journey/nfa/dot.rb
|
17
|
+
lib/journey/nfa/simulator.rb
|
18
|
+
lib/journey/nfa/transition_table.rb
|
19
|
+
lib/journey/nodes/node.rb
|
20
|
+
lib/journey/parser.rb
|
21
|
+
lib/journey/parser.y
|
22
|
+
lib/journey/parser_extras.rb
|
23
|
+
lib/journey/path/pattern.rb
|
24
|
+
lib/journey/route.rb
|
25
|
+
lib/journey/router.rb
|
26
|
+
lib/journey/router/strexp.rb
|
27
|
+
lib/journey/router/utils.rb
|
28
|
+
lib/journey/routes.rb
|
29
|
+
lib/journey/scanner.rb
|
30
|
+
lib/journey/visitors.rb
|
31
|
+
lib/journey/visualizer/d3.min.js
|
32
|
+
lib/journey/visualizer/fsm.css
|
33
|
+
lib/journey/visualizer/fsm.js
|
34
|
+
lib/journey/visualizer/index.html.erb
|
35
|
+
lib/journey/visualizer/reset.css
|
36
|
+
test/gtg/test_builder.rb
|
37
|
+
test/gtg/test_transition_table.rb
|
38
|
+
test/helper.rb
|
39
|
+
test/nfa/test_simulator.rb
|
40
|
+
test/nfa/test_transition_table.rb
|
41
|
+
test/nodes/test_symbol.rb
|
42
|
+
test/path/test_pattern.rb
|
43
|
+
test/route/definition/test_parser.rb
|
44
|
+
test/route/definition/test_scanner.rb
|
45
|
+
test/router/test_strexp.rb
|
46
|
+
test/router/test_utils.rb
|
47
|
+
test/test_route.rb
|
48
|
+
test/test_router.rb
|
49
|
+
test/test_routes.rb
|
data/README.rdoc
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
= Journey
|
2
|
+
|
3
|
+
* http://github.com/tenderlove/journey
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
Journey is a router. It routes requests.
|
8
|
+
|
9
|
+
== FEATURES/PROBLEMS:
|
10
|
+
|
11
|
+
* Designed for rails
|
12
|
+
|
13
|
+
== SYNOPSIS:
|
14
|
+
|
15
|
+
Too complex right now. :(
|
16
|
+
|
17
|
+
== REQUIREMENTS:
|
18
|
+
|
19
|
+
* None
|
20
|
+
|
21
|
+
== INSTALL:
|
22
|
+
|
23
|
+
* gem install journey
|
24
|
+
|
25
|
+
== LICENSE:
|
26
|
+
|
27
|
+
(The MIT License)
|
28
|
+
|
29
|
+
Copyright (c) 2011 Aaron Patterson
|
30
|
+
|
31
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
32
|
+
a copy of this software and associated documentation files (the
|
33
|
+
'Software'), to deal in the Software without restriction, including
|
34
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
35
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
36
|
+
permit persons to whom the Software is furnished to do so, subject to
|
37
|
+
the following conditions:
|
38
|
+
|
39
|
+
The above copyright notice and this permission notice shall be
|
40
|
+
included in all copies or substantial portions of the Software.
|
41
|
+
|
42
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
43
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
44
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
45
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
46
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
47
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
48
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hoe'
|
5
|
+
|
6
|
+
Hoe.plugins.delete :rubyforge
|
7
|
+
Hoe.plugin :minitest
|
8
|
+
Hoe.plugin :gemspec # `gem install hoe-gemspec`
|
9
|
+
Hoe.plugin :git # `gem install hoe-git`
|
10
|
+
Hoe.plugin :bundler # `gem install hoe-bundler`
|
11
|
+
|
12
|
+
Hoe.spec 'andyjeffries-journey' do
|
13
|
+
developer('Aaron Patterson', 'aaron@tenderlovemaking.com')
|
14
|
+
self.readme_file = 'README.rdoc'
|
15
|
+
self.history_file = 'CHANGELOG.rdoc'
|
16
|
+
self.extra_rdoc_files = FileList['*.rdoc']
|
17
|
+
self.extra_dev_deps += [
|
18
|
+
["racc", ">= 1.4.6"],
|
19
|
+
["json"],
|
20
|
+
]
|
21
|
+
end
|
22
|
+
|
23
|
+
rule '.rb' => '.y' do |t|
|
24
|
+
sh "racc -l -o #{t.name} #{t.source}"
|
25
|
+
end
|
26
|
+
|
27
|
+
task :compile => "lib/journey/parser.rb"
|
28
|
+
|
29
|
+
Rake::Task[:test].prerequisites.unshift "lib/journey/parser.rb"
|
30
|
+
|
31
|
+
# vim: syntax=ruby
|
data/journey.gemspec
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "andyjeffries-journey"
|
5
|
+
s.version = "1.0.0.20111022124133"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Aaron Patterson", "Andy Jeffries"]
|
9
|
+
s.date = "2011-10-22"
|
10
|
+
s.description = "Journey is a router. It routes requests."
|
11
|
+
s.email = ["aaron@tenderlovemaking.com"]
|
12
|
+
s.extra_rdoc_files = ["Manifest.txt", "CHANGELOG.rdoc", "README.rdoc"]
|
13
|
+
s.files = [".autotest", "CHANGELOG.rdoc", "Gemfile", "Manifest.txt", "README.rdoc", "Rakefile", "journey.gemspec", "lib/journey.rb", "lib/journey/backwards.rb", "lib/journey/core-ext/hash.rb", "lib/journey/formatter.rb", "lib/journey/gtg/builder.rb", "lib/journey/gtg/simulator.rb", "lib/journey/gtg/transition_table.rb", "lib/journey/nfa/builder.rb", "lib/journey/nfa/dot.rb", "lib/journey/nfa/simulator.rb", "lib/journey/nfa/transition_table.rb", "lib/journey/nodes/node.rb", "lib/journey/parser.rb", "lib/journey/parser.y", "lib/journey/parser_extras.rb", "lib/journey/path/pattern.rb", "lib/journey/route.rb", "lib/journey/router.rb", "lib/journey/router/strexp.rb", "lib/journey/router/utils.rb", "lib/journey/routes.rb", "lib/journey/scanner.rb", "lib/journey/visitors.rb", "lib/journey/visualizer/d3.min.js", "lib/journey/visualizer/fsm.css", "lib/journey/visualizer/fsm.js", "lib/journey/visualizer/index.html.erb", "lib/journey/visualizer/reset.css", "test/gtg/test_builder.rb", "test/gtg/test_transition_table.rb", "test/helper.rb", "test/nfa/test_simulator.rb", "test/nfa/test_transition_table.rb", "test/nodes/test_symbol.rb", "test/path/test_pattern.rb", "test/route/definition/test_parser.rb", "test/route/definition/test_scanner.rb", "test/router/test_strexp.rb", "test/router/test_utils.rb", "test/test_route.rb", "test/test_router.rb", "test/test_routes.rb", ".gemtest"]
|
14
|
+
s.homepage = "http://github.com/tenderlove/journey"
|
15
|
+
s.rdoc_options = ["--main", "README.rdoc"]
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
s.rubyforge_project = "journey"
|
18
|
+
s.rubygems_version = "1.8.11"
|
19
|
+
s.summary = "Journey is a router"
|
20
|
+
s.test_files = ["test/gtg/test_builder.rb", "test/gtg/test_transition_table.rb", "test/nfa/test_simulator.rb", "test/nfa/test_transition_table.rb", "test/nodes/test_symbol.rb", "test/path/test_pattern.rb", "test/route/definition/test_parser.rb", "test/route/definition/test_scanner.rb", "test/router/test_strexp.rb", "test/router/test_utils.rb", "test/test_route.rb", "test/test_router.rb", "test/test_routes.rb"]
|
21
|
+
|
22
|
+
if s.respond_to? :specification_version then
|
23
|
+
s.specification_version = 3
|
24
|
+
|
25
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
26
|
+
s.add_development_dependency(%q<minitest>, ["~> 2.6"])
|
27
|
+
s.add_development_dependency(%q<racc>, [">= 1.4.6"])
|
28
|
+
s.add_development_dependency(%q<json>, [">= 0"])
|
29
|
+
s.add_development_dependency(%q<hoe>, ["~> 2.12"])
|
30
|
+
else
|
31
|
+
s.add_dependency(%q<minitest>, ["~> 2.6"])
|
32
|
+
s.add_dependency(%q<racc>, [">= 1.4.6"])
|
33
|
+
s.add_dependency(%q<json>, [">= 0"])
|
34
|
+
s.add_dependency(%q<hoe>, ["~> 2.12"])
|
35
|
+
end
|
36
|
+
else
|
37
|
+
s.add_dependency(%q<minitest>, ["~> 2.6"])
|
38
|
+
s.add_dependency(%q<racc>, [">= 1.4.6"])
|
39
|
+
s.add_dependency(%q<json>, [">= 0"])
|
40
|
+
s.add_dependency(%q<hoe>, ["~> 2.12"])
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module Journey
|
2
|
+
###
|
3
|
+
# The Formatter class is used for formatting URLs. For example, parameters
|
4
|
+
# passed to +url_for+ in rails will eventually call Formatter#generate
|
5
|
+
class Formatter
|
6
|
+
attr_reader :routes
|
7
|
+
|
8
|
+
def initialize routes
|
9
|
+
@routes = routes
|
10
|
+
@cache = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def generate key, name, options, recall = {}, parameterize = nil
|
14
|
+
constraints = recall.merge options
|
15
|
+
|
16
|
+
match_route(name, constraints) do |route|
|
17
|
+
data = constraints.dup
|
18
|
+
|
19
|
+
keys_to_keep = route.parts.reverse.drop_while { |part|
|
20
|
+
!options.key?(part) || (options[part] || recall[part]).nil?
|
21
|
+
} | route.required_parts
|
22
|
+
|
23
|
+
(data.keys - keys_to_keep).each do |bad_key|
|
24
|
+
data.delete bad_key
|
25
|
+
end
|
26
|
+
|
27
|
+
parameterized_parts = data.dup
|
28
|
+
|
29
|
+
if parameterize
|
30
|
+
parameterized_parts.each do |k,v|
|
31
|
+
parameterized_parts[k] = parameterize.call(k, v)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
parameterized_parts.keep_if { |_,v| v }
|
36
|
+
|
37
|
+
next unless verify_required_parts!(route, parameterized_parts)
|
38
|
+
|
39
|
+
z = Hash[options.to_a - data.to_a - route.defaults.to_a]
|
40
|
+
|
41
|
+
return [route.format(parameterized_parts), z]
|
42
|
+
end
|
43
|
+
|
44
|
+
raise Router::RoutingError
|
45
|
+
end
|
46
|
+
|
47
|
+
def clear
|
48
|
+
@cache = nil
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
def named_routes
|
53
|
+
routes.named_routes
|
54
|
+
end
|
55
|
+
|
56
|
+
def match_route name, options
|
57
|
+
if named_routes.key? name
|
58
|
+
yield named_routes[name]
|
59
|
+
else
|
60
|
+
#routes = possibles(@cache, options.to_a)
|
61
|
+
routes = non_recursive(cache, options.to_a)
|
62
|
+
|
63
|
+
hash = routes.group_by { |_, r|
|
64
|
+
r.score options
|
65
|
+
}
|
66
|
+
|
67
|
+
hash.keys.sort.reverse_each do |score|
|
68
|
+
next if score < 0
|
69
|
+
|
70
|
+
hash[score].sort_by { |i,_| i }.each do |_,route|
|
71
|
+
yield route
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def non_recursive cache, options
|
78
|
+
routes = []
|
79
|
+
stack = [cache]
|
80
|
+
|
81
|
+
while stack.any?
|
82
|
+
c = stack.shift
|
83
|
+
routes.concat c[:___routes] if c.key? :___routes
|
84
|
+
|
85
|
+
options.each do |pair|
|
86
|
+
stack << c[pair] if c.key? pair
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
routes
|
91
|
+
end
|
92
|
+
|
93
|
+
def possibles cache, options, depth = 0
|
94
|
+
cache.fetch(:___routes) { [] } + options.find_all { |pair|
|
95
|
+
cache.key? pair
|
96
|
+
}.map { |pair|
|
97
|
+
possibles(cache[pair], options, depth + 1)
|
98
|
+
}.flatten(1)
|
99
|
+
end
|
100
|
+
|
101
|
+
def verify_required_parts! route, parts
|
102
|
+
tests = route.path.requirements
|
103
|
+
route.required_parts.all? { |key|
|
104
|
+
if tests.key? key
|
105
|
+
/\A#{tests[key]}\Z/ === parts[key]
|
106
|
+
else
|
107
|
+
true
|
108
|
+
end
|
109
|
+
}
|
110
|
+
end
|
111
|
+
|
112
|
+
def build_cache
|
113
|
+
kash = {}
|
114
|
+
routes.each_with_index do |route, i|
|
115
|
+
money = kash
|
116
|
+
route.required_defaults.each do |tuple|
|
117
|
+
hash = (money[tuple] ||= {})
|
118
|
+
money = hash
|
119
|
+
end
|
120
|
+
(money[:___routes] ||= []) << [i, route]
|
121
|
+
end
|
122
|
+
kash
|
123
|
+
end
|
124
|
+
|
125
|
+
def cache
|
126
|
+
@cache ||= build_cache
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'journey/gtg/transition_table'
|
2
|
+
|
3
|
+
module Journey
|
4
|
+
module GTG
|
5
|
+
class Builder
|
6
|
+
DUMMY = Nodes::Literal.new Object.new # :nodoc:
|
7
|
+
|
8
|
+
attr_reader :root, :ast, :endpoints
|
9
|
+
|
10
|
+
def initialize root
|
11
|
+
@root = root
|
12
|
+
@ast = Nodes::Cat.new root, DUMMY
|
13
|
+
@followpos = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def transition_table
|
17
|
+
dtrans = TransitionTable.new
|
18
|
+
marked = {}
|
19
|
+
state_id = Hash.new { |h,k| h[k] = h.length }
|
20
|
+
|
21
|
+
start = firstpos(root)
|
22
|
+
dstates = [start]
|
23
|
+
until dstates.empty?
|
24
|
+
s = dstates.shift
|
25
|
+
next if marked[s]
|
26
|
+
marked[s] = true # mark s
|
27
|
+
|
28
|
+
s.group_by { |state| symbol(state) }.each do |sym, ps|
|
29
|
+
u = ps.map { |l| followpos(l) }.flatten
|
30
|
+
next if u.empty?
|
31
|
+
|
32
|
+
if u.uniq == [DUMMY]
|
33
|
+
from = state_id[s]
|
34
|
+
to = state_id[Object.new]
|
35
|
+
dtrans[from, to] = sym
|
36
|
+
|
37
|
+
dtrans.add_accepting to
|
38
|
+
ps.each { |state| dtrans.add_memo to, state.memo }
|
39
|
+
else
|
40
|
+
dtrans[state_id[s], state_id[u]] = sym
|
41
|
+
|
42
|
+
if u.include? DUMMY
|
43
|
+
to = state_id[u]
|
44
|
+
|
45
|
+
accepting = ps.find_all { |l| followpos(l).include? DUMMY }
|
46
|
+
|
47
|
+
accepting.each { |accepting_state|
|
48
|
+
dtrans.add_memo to, accepting_state.memo
|
49
|
+
}
|
50
|
+
|
51
|
+
dtrans.add_accepting state_id[u]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
dstates << u
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
dtrans
|
60
|
+
end
|
61
|
+
|
62
|
+
def nullable? node
|
63
|
+
case node
|
64
|
+
when Nodes::Group
|
65
|
+
true
|
66
|
+
when Nodes::Star
|
67
|
+
true
|
68
|
+
when Nodes::Or
|
69
|
+
node.children.any? { |c| nullable?(c) }
|
70
|
+
when Nodes::Cat
|
71
|
+
nullable?(node.left) && nullable?(node.right)
|
72
|
+
when Nodes::Terminal
|
73
|
+
!node.left
|
74
|
+
when Nodes::Unary
|
75
|
+
nullable? node.left
|
76
|
+
else
|
77
|
+
raise ArgumentError, 'unknown nullable: %s' % node.class.name
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def firstpos node
|
82
|
+
case node
|
83
|
+
when Nodes::Star
|
84
|
+
firstpos(node.left)
|
85
|
+
when Nodes::Cat
|
86
|
+
if nullable? node.left
|
87
|
+
firstpos(node.left) | firstpos(node.right)
|
88
|
+
else
|
89
|
+
firstpos(node.left)
|
90
|
+
end
|
91
|
+
when Nodes::Or
|
92
|
+
node.children.map { |c| firstpos(c) }.flatten.uniq
|
93
|
+
when Nodes::Unary
|
94
|
+
firstpos(node.left)
|
95
|
+
when Nodes::Terminal
|
96
|
+
nullable?(node) ? [] : [node]
|
97
|
+
else
|
98
|
+
raise ArgumentError, 'unknown firstpos: %s' % node.class.name
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def lastpos node
|
103
|
+
case node
|
104
|
+
when Nodes::Star
|
105
|
+
firstpos(node.left)
|
106
|
+
when Nodes::Or
|
107
|
+
node.children.map { |c| lastpos(c) }.flatten.uniq
|
108
|
+
when Nodes::Cat
|
109
|
+
if nullable? node.right
|
110
|
+
lastpos(node.left) | lastpos(node.right)
|
111
|
+
else
|
112
|
+
lastpos(node.right)
|
113
|
+
end
|
114
|
+
when Nodes::Terminal
|
115
|
+
nullable?(node) ? [] : [node]
|
116
|
+
when Nodes::Unary
|
117
|
+
lastpos(node.left)
|
118
|
+
else
|
119
|
+
raise ArgumentError, 'unknown lastpos: %s' % node.class.name
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def followpos node
|
124
|
+
followpos_table[node]
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
def followpos_table
|
129
|
+
@followpos ||= build_followpos
|
130
|
+
end
|
131
|
+
|
132
|
+
def build_followpos
|
133
|
+
table = Hash.new { |h,k| h[k] = [] }
|
134
|
+
@ast.each do |n|
|
135
|
+
case n
|
136
|
+
when Nodes::Cat
|
137
|
+
lastpos(n.left).each do |i|
|
138
|
+
table[i] += firstpos(n.right)
|
139
|
+
end
|
140
|
+
when Nodes::Star
|
141
|
+
lastpos(n).each do |i|
|
142
|
+
table[i] += firstpos(n)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
table
|
147
|
+
end
|
148
|
+
|
149
|
+
def symbol edge
|
150
|
+
case edge
|
151
|
+
when Journey::Nodes::Symbol
|
152
|
+
edge.regexp
|
153
|
+
else
|
154
|
+
edge.left
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
|
3
|
+
module Journey
|
4
|
+
module GTG
|
5
|
+
class MatchData
|
6
|
+
attr_reader :memos
|
7
|
+
|
8
|
+
def initialize memos
|
9
|
+
@memos = memos
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Simulator
|
14
|
+
attr_reader :tt
|
15
|
+
|
16
|
+
def initialize transition_table
|
17
|
+
@tt = transition_table
|
18
|
+
end
|
19
|
+
|
20
|
+
def simulate string
|
21
|
+
input = StringScanner.new string
|
22
|
+
state = [0]
|
23
|
+
until input.eos?
|
24
|
+
sym = input.scan(/[\/\.\?]|[^\/\.\?]+/)
|
25
|
+
state = tt.move(state, sym)
|
26
|
+
end
|
27
|
+
|
28
|
+
acceptance_states = state.find_all { |s|
|
29
|
+
tt.accepting? s
|
30
|
+
}
|
31
|
+
|
32
|
+
return if acceptance_states.empty?
|
33
|
+
|
34
|
+
memos = acceptance_states.map { |x| tt.memo x }.flatten.compact
|
35
|
+
|
36
|
+
MatchData.new memos
|
37
|
+
end
|
38
|
+
|
39
|
+
alias :=~ :simulate
|
40
|
+
alias :match :simulate
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|