llip 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/History.txt +4 -0
  2. data/MIT-LICENSE +21 -0
  3. data/Manifest.txt +45 -0
  4. data/README.txt +148 -0
  5. data/Rakefile +66 -0
  6. data/examples/ariteval/ariteval.rb +132 -0
  7. data/examples/ariteval/evaluator.rb +61 -0
  8. data/examples/ariteval/exp.rb +104 -0
  9. data/lib/llip.rb +6 -0
  10. data/lib/llip/abstract_parser.rb +170 -0
  11. data/lib/llip/abstract_scanner.rb +83 -0
  12. data/lib/llip/buffer.rb +35 -0
  13. data/lib/llip/llip_error.rb +43 -0
  14. data/lib/llip/parser.rb +93 -0
  15. data/lib/llip/production_compiler.rb +168 -0
  16. data/lib/llip/production_specification.rb +79 -0
  17. data/lib/llip/recursive_production_compiler.rb +35 -0
  18. data/lib/llip/regexp_abstract_scanner.rb +116 -0
  19. data/lib/llip/regexp_parser.rb +197 -0
  20. data/lib/llip/regexp_scanner.rb +33 -0
  21. data/lib/llip/regexp_specification.rb +210 -0
  22. data/lib/llip/token.rb +47 -0
  23. data/lib/llip/visitable.rb +37 -0
  24. data/spec/ariteval/ariteval_spec.rb +111 -0
  25. data/spec/ariteval/evaluator_spec.rb +106 -0
  26. data/spec/ariteval/exp_spec.rb +232 -0
  27. data/spec/llip/abstract_parser_spec.rb +273 -0
  28. data/spec/llip/abstract_scanner_spec.rb +152 -0
  29. data/spec/llip/buffer_spec.rb +60 -0
  30. data/spec/llip/llip_error_spec.rb +77 -0
  31. data/spec/llip/parser_spec.rb +163 -0
  32. data/spec/llip/production_compiler_spec.rb +271 -0
  33. data/spec/llip/production_specification_spec.rb +75 -0
  34. data/spec/llip/recursive_production_compiler_spec.rb +86 -0
  35. data/spec/llip/regexp_abstract_scanner_spec.rb +320 -0
  36. data/spec/llip/regexp_parser_spec.rb +265 -0
  37. data/spec/llip/regexp_scanner_spec.rb +40 -0
  38. data/spec/llip/regexp_specification_spec.rb +734 -0
  39. data/spec/llip/token_spec.rb +70 -0
  40. data/spec/llip/visitable_spec.rb +38 -0
  41. data/spec/spec_helper.rb +10 -0
  42. metadata +110 -0
@@ -0,0 +1,265 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ require 'regexp_parser'
3
+ require 'regexp_specification'
4
+ require 'token'
5
+
6
+ module RegexpMockScannerBuilder
7
+
8
+ def mock_scanner(*tokens)
9
+ @scanner = mock "Scanner"
10
+ tokens.map! do |t|
11
+ if t =~ /[\.\+\*\|\(\)\\]/
12
+ Token.new(:symbol,t)
13
+ else
14
+ Token.new(:char,t)
15
+ end
16
+ end
17
+ @tokens = tokens
18
+ @tokens << nil unless tokens[-1].nil?
19
+ t = nil
20
+ @scanner.should_receive(:next).exactly(tokens.size).and_return { t = @tokens.shift }
21
+ @scanner.should_receive(:current).any_number_of_times.and_return { t }
22
+ @scanner
23
+ end
24
+
25
+ end
26
+
27
+ describe "A RegexpParser should parse" do
28
+
29
+ include RegexpMockScannerBuilder
30
+
31
+ before(:each) do
32
+ @parser = RegexpParser.new
33
+ @parser.should respond_to(:parse)
34
+ end
35
+
36
+ it "'a'" do
37
+ @scanner = mock_scanner('a')
38
+ regexp = @parser.parse(@scanner)
39
+
40
+ regexp.should_not be_nil
41
+ regexp.should be_kind_of(RegexpSpecification)
42
+ regexp.init.should_not be_nil
43
+ regexp.init.should_not be_final
44
+ regexp.init.should be_kind_of(RegexpSpecification::State)
45
+ regexp.init['a'].should be_kind_of(RegexpSpecification::State)
46
+ regexp.init['a'].final?.should == true
47
+ end
48
+
49
+ it "'abcde'" do
50
+ @scanner = mock_scanner('a','b','c','d','e')
51
+ regexp = @parser.parse(@scanner)
52
+
53
+ lambda { regexp.init['a']['b']['c']['d']['e'].should be_final }.should_not raise_error
54
+ end
55
+
56
+ it "'a.bcde'" do
57
+ @scanner = mock_scanner('a','.','b','c','d','e')
58
+ regexp = @parser.parse(@scanner)
59
+
60
+ lambda { regexp.init['a']['b']['c']['d']['e'].should be_final }.should_not raise_error
61
+ lambda { regexp.init['a']['f']['b']['c']['d']['e'].should be_final }.should_not raise_error
62
+ lambda { regexp.init['a']['f']['e'].should == :error }.should_not raise_error
63
+
64
+ regexp.init['a']['b'].should be_kind_of(RegexpSpecification::State)
65
+ regexp.init['a']['d']['b'].should == regexp.init['a']['b']
66
+ regexp.init['a'].error.should be_kind_of(RegexpSpecification::State)
67
+ end
68
+
69
+ it "'a...bcde'" do
70
+ @scanner = mock_scanner('a','.','.','.','b','c','d','e')
71
+ regexp = @parser.parse(@scanner)
72
+
73
+ regexp.init['a']['b']['c']['d']['e'].should be_final
74
+ regexp.init['a']['f']['b']['c']['d']['e'].should be_final
75
+ regexp.init['a']['f']['g']['b']['c']['d']['e'].should be_final
76
+ regexp.init['a']['f']['g']['h']['i'].should == :error
77
+ regexp.init['a']['f']['g']['h']['b']['c']['d']['e'].should be_final
78
+
79
+ regexp.init['a']['b'].should be_kind_of(RegexpSpecification::State)
80
+ regexp.init['a']['d']['b'].should == regexp.init['a']['b']
81
+ regexp.init['a'].error.should be_kind_of(RegexpSpecification::State)
82
+ regexp.init['a'].error.error.should be_kind_of(RegexpSpecification::State)
83
+ regexp.init['a'].error.error.error.should be_kind_of(RegexpSpecification::State)
84
+ regexp.init['a'].error.error.error['b'].should == regexp.init['a']['b']
85
+ end
86
+
87
+ it "'a\\.b'" do
88
+ @scanner = mock_scanner('a','\\','.','b')
89
+ regexp = @parser.parse(@scanner)
90
+
91
+ regexp.init['a']['.']['b'].should be_final
92
+ end
93
+
94
+ it "'a\\nb" do
95
+ @scanner = mock_scanner('a','\\','n','b')
96
+ regexp = @parser.parse(@scanner)
97
+
98
+ regexp.init['a']["\n"]['b'].should be_final
99
+ end
100
+
101
+ it "ab*c" do
102
+ @scanner = mock_scanner('a','b','*','c')
103
+ regexp = @parser.parse(@scanner)
104
+
105
+ regexp.init['a']['c'].should be_final
106
+ regexp.init['a']['b']['c'].should be_final
107
+ regexp.init['a']['b']['b'].should == regexp.init['a']['b']
108
+ end
109
+
110
+ it "ab+c" do
111
+ @scanner = mock_scanner('a','b','+','c')
112
+ regexp = @parser.parse(@scanner)
113
+
114
+ regexp.init['a']['c'].should == :error
115
+ regexp.init['a']['b']['c'].should be_final
116
+ regexp.init['a']['b']['b'].should == regexp.init['a']['b']
117
+ end
118
+
119
+ it "a.*c" do
120
+ @scanner = mock_scanner('a','.','*','c')
121
+ regexp = @parser.parse(@scanner)
122
+
123
+ regexp.init['a']['c'].should be_final
124
+ regexp.init['a']['e']['c'].should be_final
125
+ regexp.init['a']['f']['e']['c'].should == regexp.init['a']['c']
126
+ regexp.init['a'].error.error.should == regexp.init['a'].error
127
+ end
128
+
129
+ it "ac*" do
130
+ @scanner = mock_scanner('a','c','*')
131
+ regexp = @parser.parse(@scanner)
132
+
133
+ regexp.init['a']['c'].should be_final
134
+ regexp.init['a'].should be_final
135
+ regexp.init['a']['c']['c'].should be_final
136
+ regexp.init['a']['c']['c']['c'].should == regexp.init['a']['c']
137
+ end
138
+
139
+
140
+ it "a.+c" do
141
+ @scanner = mock_scanner('a','.','+','c')
142
+ regexp = @parser.parse(@scanner)
143
+
144
+ regexp.init['a']['c'].should_not be_final
145
+ regexp.init['a']['e']['c'].should be_final
146
+ regexp.init['a']['f']['e']['c'].should == regexp.init['a']['b']['c']
147
+ regexp.init['a'].error.error.error.should == regexp.init['a'].error.error
148
+ end
149
+
150
+ it "a|b|c" do
151
+ @scanner = mock_scanner('a','|','b','|','c')
152
+ regexp = @parser.parse(@scanner)
153
+
154
+ regexp.init.keys.should == ['a','b','c']
155
+ regexp.init.error.should == :error
156
+
157
+ regexp.init['a'].should be_final
158
+ regexp.init['b'].should be_final
159
+ regexp.init['c'].should be_final
160
+ end
161
+
162
+ it "a(b|c)" do
163
+ @scanner = mock_scanner("a","(","b","|","c",")")
164
+ regexp = @parser.parse(@scanner)
165
+
166
+ regexp.last.should == [regexp.init['a']['b'],regexp.init['a']['c']]
167
+
168
+ regexp.init.keys.should == ['a']
169
+ regexp.init['a'].keys.should == ['b','c']
170
+ regexp.init['a']['b'].should be_final
171
+ regexp.init['a']['c'].should be_final
172
+ regexp.init['a']['b'].should == regexp.init['a']['c']
173
+ end
174
+
175
+ it "a(b|c)d" do
176
+ @scanner = mock_scanner("a","(","b","|","c",")",'d')
177
+ regexp = @parser.parse(@scanner)
178
+
179
+ regexp.init['a'].keys.should == ['b','c']
180
+ regexp.init['a']['b']['d'].should be_final
181
+ regexp.init['a']['c']['d'].should be_final
182
+
183
+ regexp.last.should == [regexp.init['a']['b']['d']]
184
+ end
185
+
186
+ it "..(b|c)" do
187
+ @scanner = mock_scanner(".",".","(","b","|","c",")")
188
+ regexp = @parser.parse(@scanner)
189
+
190
+ regexp.last.should == [regexp.init.error.error['b'],regexp.error.error['c']]
191
+
192
+ regexp.init.error.error.keys.should == ['b','c']
193
+ regexp.init['a']['3']['b'].should be_final
194
+ regexp.init['a']['f']['c'].should be_final
195
+ regexp.init['e']['f']['b'].should == regexp.init['a']['h']['c']
196
+
197
+ regexp.last.should == [regexp.init.error.error['b'],regexp.error.error['c']]
198
+ end
199
+
200
+ it "a(b|ce)*d" do
201
+ @scanner = mock_scanner("a","(","b","|","c","e",")","*","d")
202
+ regexp = @parser.parse(@scanner)
203
+
204
+ regexp.init['a'].keys.should == ['b','c','d']
205
+
206
+ regexp['a']['b'].keys.should == ['b','c','d']
207
+ regexp['a']['c'].keys.should == ['e']
208
+ regexp['a']['c']['e'].keys.should == ['b','c','d']
209
+
210
+ regexp.init['a']['b']['d'].should be_final
211
+ regexp.init['a']['c']['e']['d'].should be_final
212
+ regexp.init['a']['d'].should be_final
213
+
214
+ regexp.init['a'].should == regexp.init['a']['b']
215
+ regexp.init['a']['b']['b'].should == regexp.init['a']['b']
216
+ regexp.init['a'].should == regexp.init['a']['c']['e']
217
+ regexp.init['a']['d'].should == regexp.init['a']['b']['d']
218
+
219
+ regexp['a']['b'].final?.should == false
220
+ regexp.init['a']['c'].final?.should == false
221
+ regexp.init['a']['c']['e'].final?.should == false
222
+ end
223
+
224
+ it "a(b|ce)+d" do
225
+ @scanner = mock_scanner("a","(","b","|","c","e",")","+","d")
226
+ regexp = @parser.parse(@scanner)
227
+
228
+ regexp.init['a'].keys.should == ['b','c']
229
+
230
+ regexp['a']['b'].keys.should == ['b','c','d']
231
+ regexp['a']['c'].keys.should == ['e']
232
+ regexp['a']['c']['e'].keys.should == ['b','c','d']
233
+
234
+ regexp.init['a']['b']['d'].should be_final
235
+ regexp.init['a']['c']['e']['d'].should be_final
236
+
237
+ regexp.init['a'].should_not == regexp.init['a']['b']
238
+ regexp.init['a'].should_not == regexp.init['a']['c']['e']
239
+
240
+ regexp.init['a']['b']['b'].should == regexp.init['a']['b']
241
+ regexp.init['a']['c']['e'].should == regexp.init['a']['b']
242
+ regexp.init['a']['c']['e']['c']['e'].should == regexp['a']['c']['e']
243
+
244
+ regexp['a']['b'].final?.should == false
245
+ regexp.init['a']['c'].final?.should == false
246
+ regexp.init['a']['c']['e'].final?.should == false
247
+ end
248
+
249
+ end
250
+
251
+ describe "A RegexpParser should not parse" do
252
+
253
+ include RegexpMockScannerBuilder
254
+
255
+ before(:each) do
256
+ @parser = RegexpParser.new
257
+ @parser.should respond_to(:parse)
258
+ end
259
+
260
+ it "a(cdef" do
261
+ @scanner = mock_scanner("a","(","b","c","d","e","f")
262
+ lambda { @parser.parse(@scanner) }.should raise_error(RuntimeError)
263
+ end
264
+
265
+ end
@@ -0,0 +1,40 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ require 'regexp_scanner'
3
+ require 'token'
4
+ require 'stringio'
5
+
6
+ describe "A RegexpScanner should scan" do
7
+
8
+ before(:each) do
9
+ @scanner = RegexpScanner.new
10
+ end
11
+
12
+ it "'abc'" do
13
+ lambda { @scanner.scan('abc') }.should_not raise_error
14
+ token = @scanner.next
15
+ token.should == 'a'
16
+ token.should == :char
17
+ @scanner.next.should == 'b'
18
+ @scanner.next.should == 'c'
19
+ end
20
+
21
+ it "'.*abc'"do
22
+ @scanner.scan('.*abc(d|e)+\\.')
23
+ token = @scanner.next
24
+ token.should == '.'
25
+ token.should == :symbol
26
+ @scanner.next.should == :symbol
27
+ @scanner.next.should == 'a'
28
+ @scanner.next.should == 'b'
29
+ @scanner.next.should == 'c'
30
+ @scanner.next.should == :symbol
31
+ @scanner.next.should == 'd'
32
+ @scanner.next.should == :symbol
33
+ @scanner.next.should == 'e'
34
+ @scanner.next.should == :symbol
35
+ @scanner.next.should == :symbol
36
+ @scanner.next.should == :symbol
37
+ @scanner.next.should == :symbol
38
+ @scanner.next.should be_nil
39
+ end
40
+ end
@@ -0,0 +1,734 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ require 'regexp_specification'
3
+
4
+ describe "A RegexpSpecification" do
5
+
6
+ before(:each) do
7
+ @instance = RegexpSpecification.new(:a_name)
8
+ end
9
+
10
+ it "should store the initialization parameter in the attribute name" do
11
+ @instance.name.should == :a_name
12
+ end
13
+
14
+ it "should have an read-write attribute name and its default should be nil" do
15
+ @instance.should respond_to(:name)
16
+ @instance.should respond_to(:name=)
17
+
18
+ @instance.name= :pippo
19
+ @instance.name.should == :pippo
20
+
21
+ lambda { @instance = RegexpSpecification.new }.should_not raise_error
22
+ @instance.name.should be_nil
23
+ end
24
+
25
+ it "should send :to_sym to the initialization parameter" do
26
+ m = mock("symbol")
27
+ m.should_receive(:to_sym).and_return(:a_sym)
28
+ @instance = RegexpSpecification.new(m)
29
+ @instance.name.should == :a_sym
30
+ end
31
+
32
+ it "should have a states attribute which must be an hash" do
33
+ @instance.should respond_to(:states)
34
+ @instance.states.should == {}
35
+ end
36
+
37
+ it "should redirect its (:[],:[]=,:keys,:values,:each,:error,:error=,:final?,:final=) methods to the #init ones" do
38
+ m = mock("State")
39
+ m.should_receive(:name).and_return(:a_name)
40
+ m.should_receive(:kind_of?).with(RegexpSpecification::State).and_return(true)
41
+ m.should_receive(:[]).with(:a).and_return(:uh)
42
+ m.should_receive(:[]=).with(:a,:buh).and_return(:buh)
43
+ m.should_receive(:keys)
44
+ m.should_receive(:values)
45
+ m.should_receive(:each)
46
+ m.should_receive(:error)
47
+ m.should_receive(:error=)
48
+ m.should_receive(:final=)
49
+ m.should_receive(:final?)
50
+ m.should_receive(:regexp=).with(@instance)
51
+ @instance.add_state(m)
52
+
53
+ @instance[:a].should == :uh
54
+ (@instance[:a] = :buh).should == :buh
55
+
56
+ lambda {
57
+ @instance.keys
58
+ @instance.values
59
+ @instance.each
60
+ @instance.error
61
+ @instance.error=
62
+ @instance.final=
63
+ @instance.final?
64
+ }.should_not raise_error
65
+
66
+ 2+2 # stupid hack because of stupid rcov
67
+ end
68
+
69
+ it "should have an :add_state method which add the value in the hash with a key that correspond at the value's name." do
70
+ m = mock("State")
71
+ m.should_receive(:name).and_return(1)
72
+ m.should_receive(:kind_of?).with(RegexpSpecification::State).and_return(false)
73
+ m.should_receive(:kind_of?).with(RegexpSpecification).and_return(true)
74
+ m.should_receive(:regexp=).with(@instance)
75
+ @instance.should respond_to(:add_state)
76
+ @instance.add_state(m).should == m
77
+ @instance.states[1].should == m
78
+ end
79
+
80
+ it "should have an :add_state method which create a new State based on the hash passed as a parameter" do
81
+ m = mock("hash")
82
+ m.should_receive(:[]).with(:final).and_return(true)
83
+ m.should_receive(:[]).with(:error).and_return(:self)
84
+
85
+ result = @instance.add_state(m)
86
+ result.should be_kind_of(RegexpSpecification::State)
87
+ result.regexp.should == @instance
88
+
89
+ lambda { @instance.add_state.should be_kind_of(RegexpSpecification::State) }.should_not raise_error
90
+ end
91
+
92
+ it "should have a :starting_chars method which returns the init State keys" do
93
+ @instance.should respond_to(:starting_chars)
94
+ @instance.starting_chars.should == []
95
+
96
+ @instance.add_state["a"] = :try
97
+ @instance.starting_chars.should == ["a"]
98
+ end
99
+
100
+ it "should have a :starting_chars method which returns :everything if the init State has an error which isn't a State" do
101
+ @instance.add_state.error = @instance.add_state
102
+ @instance.starting_chars.should == :everything
103
+ end
104
+ end
105
+
106
+ describe "A RegexpSpecificatio should have a :last method which is able to discover the last State of" do
107
+
108
+ before(:each) do
109
+ @instance = RegexpSpecification.new
110
+ @instance.should respond_to(:last)
111
+ @instance.last.should == []
112
+ end
113
+
114
+ it "'abc'" do
115
+ @a = @instance.add_state
116
+ @b = @instance.add_state
117
+ @c = @instance.add_state
118
+ @d = @instance.add_state(:final => true)
119
+ @a['a'] = @b
120
+ @b['b'] = @c
121
+ @c['c'] = @d
122
+
123
+ @instance.last.should == [@d]
124
+ end
125
+
126
+ it "'a(b|cd)*'" do
127
+ @a = @instance.add_state
128
+
129
+ @b = @instance.add_state(:final => true)
130
+
131
+ @c = @instance.add_state
132
+
133
+ @a['a'] = @b
134
+ @b['b'] = @b
135
+ @b['c'] = @c
136
+ @c['d'] = @b
137
+
138
+ @instance.last.should == [@b]
139
+ end
140
+
141
+ it "'a(b|ce)*d'" do
142
+ @a = @instance.add_state
143
+ @b = @instance.add_state
144
+ @c = @instance.add_state
145
+ @d = @instance.add_state
146
+ @e = @instance.add_state
147
+ @g = @instance.add_state(:final => true)
148
+
149
+ @a['a'] = @b
150
+
151
+ @b['b'] = @d
152
+ @b['c'] = @c
153
+ @b['d'] = @g
154
+
155
+ @d['b'] = @d
156
+ @d['c'] = @c
157
+ @d['d'] = @g
158
+
159
+ @c['e'] = @e
160
+
161
+ @e['b'] = @d
162
+ @e['c'] = @c
163
+ @e['d'] = @g
164
+
165
+ @instance.last.should == [@g]
166
+ end
167
+
168
+ it "'a(b|cd)'" do
169
+ @a = @instance.add_state
170
+ @b = @instance.add_state
171
+ @c = @instance.add_state
172
+ @d = @instance.add_state(:final => true)
173
+
174
+ @a['a'] = @b
175
+ @b['b'] = @d
176
+ @b['c'] = @c
177
+ @c['d'] = @d
178
+
179
+ @instance.last.should == [@d]
180
+ end
181
+
182
+ it "'a(.+|cd)b" do
183
+ @a = @instance.add_state
184
+ @f = @instance.add_state(:error => :self)
185
+ @b = @instance.add_state(:error => @f)
186
+ @c = @instance.add_state
187
+ @d = @instance.add_state
188
+ @e = @instance.add_state(:final => true)
189
+
190
+ @a['a'] = @b
191
+ @b['c'] = @c
192
+ @c['d'] = @d
193
+ @d['b'] = @e
194
+ @f['b'] = @e
195
+
196
+ @instance.last.should == [@e]
197
+ end
198
+
199
+ it "'a.b'" do
200
+ @a = @instance.add_state
201
+ @c = @instance.add_state
202
+ @b = @instance.add_state(:error => @c)
203
+ @d = @instance.add_state(:final => true)
204
+
205
+ @a['a'] = @b
206
+ @c['b'] = @d
207
+
208
+ @instance.last.should == [@d]
209
+ end
210
+
211
+ it "'a.*b'" do
212
+ @a = @instance.add_state
213
+ @c = @instance.add_state(:error => :self)
214
+ @b = @instance.add_state(:error => @c)
215
+ @d = @instance.add_state(:final => true)
216
+
217
+ @a['a'] = @b
218
+ @b['b'] = @d
219
+ @c['b'] = @d
220
+
221
+ @instance.last.should == [@d]
222
+ end
223
+
224
+ it "'ac*'" do
225
+ @a = @instance.add_state
226
+ @b = @instance.add_state(:final => true)
227
+
228
+ @a['c'] = @b
229
+ @b['c'] = @b
230
+
231
+ @instance.last.should == [@b]
232
+ end
233
+
234
+ it "'a.'" do
235
+ @a = @instance.add_state
236
+ @c = @instance.add_state(:final => true)
237
+ @b = @instance.add_state(:error => @c)
238
+
239
+ @a['a'] = @b
240
+
241
+ @instance.last.should == [@c]
242
+ end
243
+
244
+ it "'.a'" do
245
+ @a = @instance.add_state
246
+ @b = @instance.add_state
247
+ @c = @instance.add_state(:final => true)
248
+ @a.error = @b
249
+ @b['a'] = @c
250
+
251
+ @instance.last.should == [@c]
252
+ end
253
+
254
+ it "'..a'" do
255
+ @a = @instance.add_state
256
+ @b = @instance.add_state
257
+ @c = @instance.add_state
258
+ @d = @instance.add_state(:final => true)
259
+ @a.error = @b
260
+ @b.error = @c
261
+ @c['a'] = @d
262
+
263
+ @instance.last.should == [@d]
264
+ end
265
+
266
+ end
267
+
268
+ describe "A RegexpSpecification::State" do
269
+
270
+ before(:each) do
271
+ @instance = RegexpSpecification::State.new
272
+ end
273
+
274
+ it "should be a kind of hash" do
275
+ @instance.should be_kind_of(Hash)
276
+ end
277
+
278
+ it "should return :error if it's requested an unknown key" do
279
+ @instance[:unknown_key].should == :error
280
+ end
281
+
282
+ it "should admit to specify the error code" do
283
+ m = mock("sym")
284
+ m.should_receive(:to_sym).and_return(:a_different_error_code)
285
+ m.should_receive(:respond_to?).with(:to_sym).and_return(true)
286
+ @instance = RegexpSpecification::State.new(:error => m)
287
+ @instance[:unknown_key].should == :a_different_error_code
288
+ end
289
+
290
+ it "should set the error code to its name if the error code specified is :self" do
291
+ @instance = RegexpSpecification::State.new(:error => :self)
292
+ @instance[:unknown_key].should == @instance
293
+ end
294
+
295
+ it "should have a name which is a Numeric identifier" do
296
+ @instance.should respond_to(:name)
297
+ @instance.name.should_not be_nil
298
+ @instance.name.should be_kind_of(Numeric)
299
+
300
+ second = RegexpSpecification::State.new
301
+ second.name.should > @instance.name
302
+ end
303
+
304
+ it "should have a :final? method which is initialized by an hash-like initialization argument" do
305
+ @instance.should respond_to(:final?)
306
+ @instance.final?.should == false
307
+
308
+ lambda { @instance = RegexpSpecification::State.new(:final => true) }.should_not raise_error
309
+ @instance.final?.should == true
310
+ end
311
+
312
+ it "should have a :final= method which sets :final" do
313
+ @instance.should respond_to(:final=)
314
+ @instance.final= true
315
+ @instance.should be_final
316
+ end
317
+
318
+ it "should have as hash the hash of its name" do
319
+ @instance.hash.should == @instance.name.hash
320
+ end
321
+
322
+ it "should allow to set the error code through :error=" do
323
+ @instance.should respond_to(:error)
324
+ @instance.should respond_to(:error=)
325
+ @instance.error.should == @instance.default
326
+ @instance.error= :ghgh
327
+ @instance.error.should == :ghgh
328
+ @instance.error.should == @instance.default
329
+ @instance["huhu"].should == :ghgh
330
+ end
331
+
332
+ it "should not be equal to another State with same content but different error" do
333
+ @another = RegexpSpecification::State.new
334
+ @instance.error = RegexpSpecification::State.new
335
+
336
+ @instance.should_not == @another
337
+
338
+ @another.error = RegexpSpecification::State.new
339
+
340
+ @instance.should == @another
341
+
342
+ @another.error['p'] = :pluto
343
+
344
+ @instance.should_not == @another
345
+
346
+ @another.error = @instance.error
347
+ @instance.should == @another
348
+ end
349
+
350
+ it "should memorize its regexp internally" do
351
+ @instance.should respond_to(:regexp)
352
+ @instance.should respond_to(:regexp=)
353
+ @instance.regexp.should be_nil
354
+ @instance.regexp = :regexp
355
+ @instance.regexp.should == :regexp
356
+ end
357
+ end
358
+
359
+ describe "A RegexpSpecification should be able to represent" do
360
+
361
+ before(:each) do
362
+ @instance = RegexpSpecification.new(:a_regexp)
363
+ @state = RegexpSpecification::State
364
+ end
365
+
366
+ it "'abc'" do
367
+ @a = @instance.add_state
368
+ @b = @instance.add_state
369
+ @c = @instance.add_state
370
+ @d = @instance.add_state(:final => true)
371
+ @a['a'] = @b
372
+ @b['b'] = @c
373
+ @c['c'] = @d
374
+
375
+ scan(["a","b","c"]).should == true
376
+ scan(["a","b","c","d"]).should == false
377
+ end
378
+
379
+ it "'a.b'" do
380
+ @a = @instance.add_state
381
+ @c = @instance.add_state
382
+ @b = @instance.add_state(:error => @c)
383
+ @d = @instance.add_state(:final => true)
384
+
385
+ @a['a'] = @b
386
+ @c['b'] = @d
387
+
388
+ scan(["a","b"]).should == false
389
+ scan(["a","c","b"]).should == true
390
+ scan(["a","c","d"]).should == false
391
+ scan(["a","c","d","b"]).should == false
392
+ end
393
+
394
+ it "'a.+b'" do
395
+ @a = @instance.add_state
396
+ @c = @instance.add_state(:error => :self)
397
+ @b = @instance.add_state(:error => @c)
398
+ @d = @instance.add_state(:final => true)
399
+
400
+ @a['a'] = @b
401
+ @c['b'] = @d
402
+
403
+ scan(["a","b"]).should == false
404
+ scan(["a","c","b"]).should == true
405
+ scan(["a","c","d"]).should == false
406
+ scan(["a","c","d","b"]).should == true
407
+ end
408
+
409
+ it "'a.*b'" do
410
+ @a = @instance.add_state
411
+ @c = @instance.add_state(:error => :self)
412
+ @b = @instance.add_state(:error => @c)
413
+ @d = @instance.add_state(:final => true)
414
+
415
+ @a['a'] = @b
416
+ @b['b'] = @d
417
+ @c['b'] = @d
418
+
419
+ scan(["a","b"]).should == true
420
+ scan(["a","c","b"]).should == true
421
+ scan(["a","c","d"]).should == false
422
+ scan(["a","c","d","b"]).should == true
423
+ end
424
+
425
+ it "'a(b|cd)'" do
426
+ @a = @instance.add_state
427
+ @b = @instance.add_state
428
+ @c = @instance.add_state
429
+ @d = @instance.add_state(:final => true)
430
+
431
+ @a['a'] = @b
432
+ @b['b'] = @d
433
+ @b['c'] = @c
434
+ @c['d'] = @d
435
+
436
+ scan(["a","b"]).should == true
437
+ scan(["a","c","d"]).should == true
438
+ scan(["a","b","c","d"]).should == false
439
+ end
440
+
441
+ it "'a(b|cd)*'" do
442
+ @a = @instance.add_state
443
+
444
+ @b = @instance.add_state(:final => true)
445
+
446
+ @c = @instance.add_state
447
+
448
+ @a['a'] = @b
449
+ @b['b'] = @b
450
+ @b['c'] = @c
451
+ @c['d'] = @b
452
+
453
+
454
+ scan(["a"]).should == true
455
+ scan(["a","b"]).should == true
456
+ scan(["a","c","d"]).should == true
457
+ scan(["a","b","c","d"]).should == true
458
+ scan(["a","b","c","e"]).should == false
459
+ scan(["a","b","c","d","b"]).should == true
460
+ end
461
+
462
+ it "'a(b|cd)+'" do
463
+ @a = @instance.add_state
464
+ @b = @instance.add_state
465
+ @c = @instance.add_state
466
+ @d = @instance.add_state(:final => true)
467
+
468
+ @e = @instance.add_state
469
+ @f = @instance.add_state
470
+
471
+ @a['a'] = @b
472
+ @b['b'] = @d
473
+ @b['c'] = @c
474
+ @c['d'] = @d
475
+
476
+ @d['b'] = @d
477
+ @d['c'] = @e
478
+ @e['d'] = @d
479
+
480
+ scan(["a","b"]).should == true
481
+ scan(["a","c","d"]).should == true
482
+ scan(["a","b","c","d"]).should == true
483
+ scan(["a","b","c","d","b"]).should == true
484
+ end
485
+
486
+ it "'a(.+|cd)b" do
487
+ @a = @instance.add_state
488
+ @f = @instance.add_state(:error => :self)
489
+ @b = @instance.add_state(:error => @f)
490
+ @c = @instance.add_state
491
+ @d = @instance.add_state
492
+ @e = @instance.add_state(:final => true)
493
+
494
+ @a['a'] = @b
495
+ @b['c'] = @c
496
+ @c['d'] = @d
497
+ @d['b'] = @e
498
+ @f['b'] = @e
499
+
500
+
501
+ scan(["a","c","d","b"]).should == true
502
+ scan(["a","b","b"]).should == true
503
+ scan(["a","e","f","b"]).should == true
504
+ end
505
+
506
+ it "'a(b|ce)*d'" do
507
+ @a = @instance.add_state
508
+ @b = @instance.add_state
509
+ @c = @instance.add_state
510
+ @d = @instance.add_state
511
+ @e = @instance.add_state
512
+ @g = @instance.add_state(:final => true)
513
+
514
+ @a['a'] = @b
515
+
516
+ @b['b'] = @d
517
+ @b['c'] = @c
518
+ @b['d'] = @g
519
+
520
+ @d['b'] = @d
521
+ @d['c'] = @c
522
+ @d['d'] = @g
523
+
524
+ @c['e'] = @e
525
+
526
+ @e['b'] = @d
527
+ @e['c'] = @c
528
+ @e['d'] = @g
529
+
530
+ scan("a","d").should == true
531
+ scan("a",'b',"d").should == true
532
+ scan("a","b","b","d").should == true
533
+ scan("a","b","b","b","c","e","b","d").should == true
534
+ scan("a","b","c","e","b","c","e","b","d").should == true
535
+ scan("a","b","c","e","b","c","e","b").should == false
536
+ end
537
+
538
+ def scan(*chars)
539
+ chars.flatten!
540
+ next_state = @instance.init
541
+ chars.each do |c|
542
+ next_state = next_state[c]
543
+ return false if next_state == :error
544
+ end
545
+ next_state.final?
546
+ end
547
+ end
548
+
549
+ describe "The :mix method of the RegexpSpecification class" do
550
+
551
+ before(:each) do
552
+ RegexpSpecification.should respond_to(:mix)
553
+ end
554
+
555
+ it "should be able to mix: 'ab' and 'ac'" do
556
+ r1 = RegexpSpecification.new("first")
557
+ r2 = RegexpSpecification.new("second")
558
+
559
+ s1 = r1.add_state
560
+ s2 = r1.add_state
561
+ s3 = r1.add_state(:final => true)
562
+ s1['a'] = s2
563
+ s2['b'] = s3
564
+
565
+ s4 = r2.add_state
566
+ s5 = r2.add_state
567
+ s6 = r2.add_state(:final => true)
568
+ s4['a'] = s5
569
+ s5['c'] = s6
570
+
571
+ result = RegexpSpecification.mix(r1,r2)
572
+ result.name.should == :"mix between 'first' and 'second'"
573
+ second = result['a']
574
+ second.should_not == :error
575
+ second['b'].should == s3
576
+ second['c'].should == s6
577
+ end
578
+
579
+ it "should be able to mix: '.b' and '.c'" do
580
+ r1 = RegexpSpecification.new("first")
581
+ r2 = RegexpSpecification.new("second")
582
+
583
+ s1 = r1.add_state
584
+ s2 = r1.add_state
585
+ s3 = r1.add_state(:final => true)
586
+ s1.error = s2
587
+ s2['b'] = s3
588
+
589
+ s4 = r2.add_state
590
+ s5 = r2.add_state
591
+ s6 = r2.add_state(:final => true)
592
+ s4.error = s5
593
+ s5['c'] = s6
594
+
595
+ result = RegexpSpecification.mix(r1,r2)
596
+ result.name.should == :"mix between 'first' and 'second'"
597
+ second = result.init.error
598
+ second.should_not == :error
599
+ second['b'].should == s3
600
+ second['c'].should == s6
601
+ end
602
+
603
+ it "should be able to mix: 'a.b' and 'ac'" do
604
+ r1 = RegexpSpecification.new("first")
605
+ r2 = RegexpSpecification.new("second")
606
+
607
+ s1 = r1.add_state
608
+ s2 = r1.add_state
609
+ s3 = r1.add_state
610
+ s4 = r1.add_state(:final => true)
611
+ s1['a'] = s2
612
+ s2.error = s3
613
+ s3['b'] = s4
614
+
615
+ s4 = r2.add_state
616
+ s5 = r2.add_state
617
+ s6 = r2.add_state(:final => true)
618
+ s4['a'] = s5
619
+ s5['c'] = s6
620
+
621
+ result = RegexpSpecification.mix(r1,r2)
622
+ result.name.should == :"mix between 'first' and 'second'"
623
+ second = result.init
624
+ second.should_not == :error
625
+ second['a'].should_not == :error
626
+ second['a']['c'].should == s6
627
+ second['a'].error.should_not == :error
628
+ second['a'].error.should == s3
629
+
630
+ result = RegexpSpecification.mix(r2,r1)
631
+ result.name.should == :"mix between 'second' and 'first'"
632
+ second = result.init
633
+ second.should_not == :error
634
+ second['a'].should_not == :error
635
+ second['a']['c'].should == s6
636
+ second['a'].error.should_not == :error
637
+ second['a'].error.should == s3
638
+ end
639
+
640
+ it "should raise if trying to mix: 'ab' and 'abc'" do
641
+ r1 = RegexpSpecification.new("first")
642
+ r2 = RegexpSpecification.new("second")
643
+
644
+ s1 = r1.add_state
645
+ s2 = r1.add_state
646
+ s3 = r1.add_state(:final => true)
647
+ s1['a'] = s2
648
+ s2['b'] = s3
649
+
650
+ s4 = r2.add_state
651
+ s5 = r2.add_state
652
+ s6 = r2.add_state
653
+ s7 = r2.add_state(:final => true)
654
+ s4['a'] = s5
655
+ s5['b'] = s6
656
+ s6['c'] = s7
657
+
658
+ lambda { RegexpSpecification.mix(r1,r2) }.should raise_error(RuntimeError)
659
+ end
660
+
661
+ it "should be able to mix: '(a|b)c' and '(b|e)d'" do
662
+ r1 = RegexpSpecification.new("first")
663
+ r2 = RegexpSpecification.new("second")
664
+
665
+ s1 = r1.add_state
666
+ s2 = r1.add_state
667
+ s3 = r1.add_state(:final => true)
668
+ s4 = r1.add_state
669
+
670
+ s1['a'] = s2
671
+ s2['c'] = s3
672
+ s1['b'] = s4
673
+ s4['c'] = s3
674
+
675
+ s5 = r2.add_state
676
+ s6 = r2.add_state
677
+ s7 = r2.add_state(:final => true)
678
+ s8 = r2.add_state
679
+
680
+ s5['e'] = s6
681
+ s6['d'] = s7
682
+ s5['b'] = s8
683
+ s8['d'] = s7
684
+
685
+ result = RegexpSpecification.mix(r1,r2)
686
+ result.name.should == :"mix between 'first' and 'second'"
687
+ result['a'].should == s2
688
+ result['e'].should == s6
689
+ result['b'].should_not == :error
690
+ result['b']['c'].should == s3
691
+ result['b']['d'].should == s7
692
+ end
693
+
694
+ it "should be able to mix: '(a|b)+c' and '(b|e)+d'" do
695
+ r1 = RegexpSpecification.new("first")
696
+ r2 = RegexpSpecification.new("second")
697
+
698
+ s1 = r1.add_state
699
+ s2 = r1.add_state
700
+ s3 = r1.add_state(:final => true)
701
+
702
+ s1['a'] = s2
703
+ s1['b'] = s2
704
+ s2['a'] = s2
705
+ s2['b'] = s2
706
+ s2['c'] = s3
707
+
708
+ s4 = r2.add_state
709
+ s5 = r2.add_state
710
+ s6 = r2.add_state(:final => true)
711
+
712
+ s4['e'] = s5
713
+ s4['b'] = s5
714
+ s5['e'] = s5
715
+ s5['b'] = s5
716
+ s5['d'] = s6
717
+
718
+ result = RegexpSpecification.mix(r1,r2)
719
+ result.name.should == :"mix between 'first' and 'second'"
720
+ result['a'].should == s2
721
+ result['e'].should == s5
722
+ result['b'].should_not == :error
723
+ result['b'].should_not == s2
724
+ result['b'].should_not == s5
725
+ result['b']['b'].should_not == :error
726
+ result['b']['b'].should == result['b']['b']['b']
727
+ result['b']['a'].should == s2
728
+ result['b']['e'].should == s5
729
+ result['b']['b'].should_not == s2
730
+ result['b']['b'].should_not == s5
731
+ result['b']['b']['a'].should == s2
732
+ result['b']['b']['e'].should == s5
733
+ end
734
+ end