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,271 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ require 'production_compiler'
3
+
4
+ describe "A ProductionCompiler" do
5
+
6
+ before(:each) do
7
+ @compiler = ProductionCompiler.new
8
+ end
9
+
10
+ it "should have a code method which returns a String" do
11
+ @compiler.should respond_to(:code)
12
+ @compiler.code.should == ""
13
+ end
14
+
15
+ it "should have a start method which accept a name and build the definition of a method" do
16
+ @compiler.should respond_to(:start)
17
+ @compiler.start(:new_production)
18
+ @compiler.code.strip.should =~ /^def parse_new_production$/
19
+ @compiler.code.should =~ /^( |\t|\n)*result = productions\[:"new_production"\].default.call\(@scanner,self\)\n*$/
20
+ end
21
+
22
+ it "should have an end method which close the definition of a method" do
23
+ @compiler.should respond_to(:end)
24
+ @compiler.start(:new_production)
25
+ @compiler.end
26
+ @compiler.code.strip.should =~ /^def parse_new_production$/
27
+ @compiler.code.should =~ /^( |\t|\n)*result = productions\[:"new_production"\].default.call\(@scanner,self\)\n*$/
28
+ @compiler.code.should =~ /^( |\t|\n)*return result\n*$/
29
+ @compiler.code.strip.should =~ /^( |\t|\n)*end$/
30
+ end
31
+
32
+ it "should build an 'if'..'elsif' block for a sequence of tokens" do
33
+
34
+ @compiler.start(:new_production)
35
+ @compiler.token(:first_token)
36
+ @compiler.token(:second_token)
37
+ @compiler.code.strip.should =~ /^( |\t|\n)*if @scanner.current == :"first_token"$/
38
+ @compiler.code.strip.should =~ /^( |\t|\n)*result = productions\[:"new_production"\].tokens\[:"first_token"\].call\(result,@scanner,self\)$/
39
+ @compiler.code.strip.should =~ /^( |\t|\n)*elsif @scanner.current == :"second_token"$/
40
+ @compiler.code.strip.should =~ /^( |\t|\n)*result = productions\[:"new_production"\].tokens\[:"second_token"\].call\(result,@scanner,self\)$/
41
+ end
42
+
43
+ it "should build an 'if'..'elsif' block for a sequence of tokens" do
44
+ @compiler.start(:new_production)
45
+ @compiler.token("token")
46
+ @compiler.code.strip.should =~ /^( |\t|\n)*if @scanner.current == 'token'$/
47
+ @compiler.code.strip.should =~ /^( |\t|\n)*result = productions\[:"new_production"\].tokens\['token'\].call\(result,@scanner,self\)$/
48
+ end
49
+
50
+ it "should close the 'if' block with an appropriate raising 'else' clause as a default" do
51
+ @compiler.start(:new_production)
52
+ @compiler.token(:first_token)
53
+
54
+ lambda { @compiler.end }.should_not raise_error
55
+
56
+ @compiler.code.strip.should =~ /^( |\t|\n)*if @scanner.current == :"first_token"$/
57
+ @compiler.code.strip.should =~ /^( |\t|\n)*result = productions\[:"new_production"\].tokens\[:"first_token"\].call\(result,@scanner,self\)$/
58
+ @compiler.code.strip.should =~ /^( |\t|\n)*else$/
59
+ @compiler.code.strip.should =~ /^( |\t|\n)*raise NotAllowedTokenError.new\(@scanner.current,:"new_production"\)( |\t|\n)*end$/
60
+ end
61
+
62
+ it "should close the 'if' block without an 'else' clause if it's specified so" do
63
+ @compiler.start(:new_production)
64
+ @compiler.token(:first_token)
65
+
66
+ lambda { @compiler.end(false) }.should_not raise_error
67
+
68
+ @compiler.code.strip.should =~ /^( |\t|\n)*if @scanner.current == :"first_token"$/
69
+ @compiler.code.strip.should =~ /^( |\t|\n)*result = productions\[:"new_production"\].tokens\[:"first_token"\].call\(result,@scanner,self\)( |\t|\n)*end$/
70
+ end
71
+
72
+ it "should produce an 'if'..'elsif' block containing lookaheads" do
73
+ @compiler.start(:new_production)
74
+ @compiler.token(["token","first","second"])
75
+ @compiler.token(["token","first"])
76
+ @compiler.token(["token","second"])
77
+ @compiler.code.strip.should =~ /^( |\t|\n)*if @scanner.current == 'token' and @scanner.lookahead\(1\) == 'first' and @scanner.lookahead\(2\) == 'second'$/
78
+ @compiler.code.strip.should =~ /^( |\t|\n)*result = productions\[:"new_production"\].tokens\[\['token','first','second'\]\].call\(result,@scanner,self\)$/
79
+ @compiler.code.strip.should =~ /^( |\t|\n)*elsif @scanner.current == 'token' and @scanner.lookahead\(1\) == 'first'$/
80
+ @compiler.code.strip.should =~ /^( |\t|\n)*result = productions\[:"new_production"\].tokens\[\['token','first'\]\].call\(result,@scanner,self\)$/
81
+ @compiler.code.strip.should =~ /^( |\t|\n)*elsif @scanner.current == 'token' and @scanner.lookahead\(1\) == 'second'$/
82
+ @compiler.code.strip.should =~ /^( |\t|\n)*result = productions\[:"new_production"\].tokens\[\['token','second'\]\].call\(result,@scanner,self\)$/
83
+ end
84
+
85
+ it "should not modify the lookaheads tokens" do
86
+ @compiler.start(:new_production)
87
+ lookahead = ["token","first","second"]
88
+ @compiler.token(lookahead)
89
+ lookahead.should == ["token","first","second"]
90
+ end
91
+
92
+ it "should produce code that doesn't raise Exceptions if evaluated" do
93
+ @compiler.start(:fake)
94
+ @compiler.token(:first)
95
+ @compiler.token(:second)
96
+ @compiler.token(:third)
97
+ @compiler.token([:fourth,:lk])
98
+ @compiler.token(:fourth)
99
+ @compiler.end
100
+
101
+ @class = Class.new
102
+ lambda { @class.class_eval(@compiler.code) }.should_not raise_error
103
+ end
104
+
105
+ it "should have a reset method which clean the code" do
106
+ @compiler.should respond_to(:reset)
107
+ @compiler.code.should == ""
108
+ @compiler.start(:fake)
109
+ @compiler.token(:first)
110
+ @compiler.token(:second)
111
+ @compiler.token(:third)
112
+ @compiler.end
113
+ @compiler.code.should_not == ""
114
+ @compiler.reset
115
+ @compiler.code.should == ""
116
+ end
117
+
118
+ it "should have a compile method which accept a ProductionSpecification and create the code" do
119
+ @compiler.should respond_to(:compile)
120
+
121
+ @production = mock "ProductionSpecification"
122
+ @production.should_receive(:name).and_return(:fake)
123
+ @production.should_receive(:tokens).and_return({ :first => nil, :second => nil, :third => nil})
124
+ @production.should_receive(:raise_on_error).and_return(false)
125
+
126
+ @compiler.should_receive(:start).with(:fake)
127
+ @compiler.should_receive(:token).with(:first)
128
+ @compiler.should_receive(:token).with(:second)
129
+ @compiler.should_receive(:token).with(:third)
130
+ @compiler.should_receive(:end).with(false)
131
+
132
+ @compiler.compile(@production)
133
+ end
134
+
135
+ it "should compile a lookahead production correctly" do
136
+ @compiler.should respond_to(:compile)
137
+
138
+ @production = mock "Production"
139
+ @production.should_receive(:name).and_return(:fake)
140
+ @production.should_receive(:tokens).and_return({ [:look,"3"] => nil, [:look,"1"] => nil, :look => nil})
141
+ @production.should_receive(:raise_on_error).and_return(true)
142
+
143
+ @compiler.should_receive(:start).with(:fake)
144
+ @compiler.should_receive(:token).with([:look,"3"])
145
+ @compiler.should_receive(:token).with([:look,"1"])
146
+ @compiler.should_receive(:token).with(:look)
147
+ @compiler.should_receive(:end).with(true)
148
+
149
+ @compiler.compile(@production)
150
+ end
151
+
152
+ it "should have a method to sort the productions correctly" do
153
+ @compiler.should respond_to(:sort_production)
154
+
155
+ @production = mock "Production"
156
+ @production.should_receive(:tokens).and_return({ [:look,"1"] => nil, [:look,"3","2"] => nil, :everything => nil, :look => nil})
157
+
158
+ @compiler.sort_production(@production).should == [[:look,"3","2"],[:look,"1"], :look, :everything]
159
+ end
160
+ end
161
+
162
+
163
+ describe "The code produced by a ProductionCompiler without lookaheads" do
164
+
165
+ before(:each) do
166
+ @compiler = ProductionCompiler.new
167
+ @compiler.start(:fake)
168
+ @compiler.token(:first)
169
+ @compiler.token(:second)
170
+ @compiler.token(:third)
171
+ @compiler.token("+")
172
+ @compiler.end
173
+
174
+ @class = Class.new
175
+ lambda { @class.class_eval(@compiler.code) }.should_not raise_error
176
+ @class.class_eval <<-CODE
177
+ attr_accessor :productions
178
+ attr_accessor :scanner
179
+ CODE
180
+ @instance = @class.new
181
+ @instance.productions = {
182
+ :fake => mock("ProductionSpecification")
183
+ }
184
+ @tokens = {
185
+ :first => mock('first'),
186
+ :second => mock('second'),
187
+ :third => mock('third'),
188
+ "+" => mock('+')
189
+ }
190
+ @instance.productions[:fake].should_receive(:tokens).any_number_of_times.and_return(@tokens)
191
+ @instance.scanner = mock 'scanner'
192
+ @instance.scanner.should_receive(:next).any_number_of_times
193
+
194
+ @default = mock "default"
195
+ @instance.productions[:fake].should_receive(:default).any_number_of_times.and_return(@default)
196
+ @default.should_receive(:call).and_return(nil)
197
+ end
198
+
199
+ it "should call the specified block when received the correct token" do
200
+ @instance.scanner.should_receive(:current).and_return(:first)
201
+ @tokens[:first].should_receive(:call).with(nil,@instance.scanner,@instance)
202
+ @instance.parse_fake
203
+ end
204
+
205
+ it "should call the specified block even if the token is a string" do
206
+ @instance.scanner.should_receive(:current).exactly(4).and_return("+")
207
+ @tokens["+"].should_receive(:call).with(nil,@instance.scanner,@instance)
208
+ @instance.parse_fake
209
+ end
210
+
211
+ it "should call the specified block and return the correct value" do
212
+ @tokens[:second] = lambda { |result,scanner,parser| :ok }
213
+ @instance.scanner.should_receive(:current).twice.and_return(:second)
214
+ @instance.parse_fake.should == :ok
215
+ end
216
+
217
+ it "should raise an exception if the token isn't recognized" do
218
+ token = mock "Token"
219
+ token.should_receive(:value).and_return("value")
220
+ token.should_receive(:name).and_return(:a_name)
221
+ @instance.scanner.should_receive(:current).exactly(@tokens.size + 1).times.and_return(token)
222
+ lambda { @instance.parse_fake }.should raise_error(NotAllowedTokenError)
223
+ end
224
+ end
225
+
226
+ describe "The code produced by a ProductionCompiler with lookaheads" do
227
+
228
+ before(:each) do
229
+ @compiler = ProductionCompiler.new
230
+ @compiler.start(:fake)
231
+ @compiler.token([:token,:first,:third])
232
+ @compiler.token([:token,:second])
233
+ @compiler.token([:token,:third])
234
+ @compiler.token(:token)
235
+ @compiler.end
236
+
237
+ @class = Class.new
238
+ lambda { @class.class_eval(@compiler.code) }.should_not raise_error
239
+ @class.class_eval <<-CODE
240
+ attr_accessor :productions
241
+ attr_accessor :scanner
242
+ CODE
243
+ @instance = @class.new
244
+ @instance.productions = {
245
+ :fake => mock("ProductionSpecification")
246
+ }
247
+ @tokens = {
248
+ [:token,:first,:third] => mock('first'),
249
+ [:token,:second] => mock('second'),
250
+ [:token,:third] => mock('third'),
251
+ :token => mock('token')
252
+ }
253
+ @instance.productions[:fake].should_receive(:tokens).any_number_of_times.and_return(@tokens)
254
+ @instance.scanner = mock 'scanner'
255
+ @scanner = @instance.scanner
256
+
257
+ @default = mock "default"
258
+ @instance.productions[:fake].should_receive(:default).any_number_of_times.and_return(@default)
259
+ @default.should_receive(:call).and_return(nil)
260
+ end
261
+
262
+ it "should recognize the sequence [:token,:first,:third]" do
263
+ @scanner.should_receive(:current).once.and_return(:token)
264
+ @scanner.should_receive(:lookahead).with(1).once.and_return(:first)
265
+ @scanner.should_receive(:lookahead).with(2).once.and_return(:third)
266
+
267
+ @tokens[[:token,:first,:third]].should_receive(:call).with(nil,@scanner,@instance)
268
+
269
+ @instance.parse_fake
270
+ end
271
+ end
@@ -0,0 +1,75 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ require 'production_specification'
3
+
4
+ describe "A ProductionSpecification" do
5
+
6
+ before(:each) do
7
+ @production = ProductionSpecification.new(:test_production)
8
+ end
9
+
10
+ it "should have a name" do
11
+ @production.should respond_to(:name)
12
+ @production.name.should == :test_production
13
+ end
14
+
15
+ it "should allow to specify a recognized token with a block parameter" do
16
+ @production.should respond_to(:token)
17
+ lambda { @production.token(:token_name) { "hello" } }.should_not raise_error
18
+ end
19
+
20
+ it "should have some tokens" do
21
+ @production.should respond_to(:tokens)
22
+ @production.tokens.should == {}
23
+
24
+ first_block = lambda { :first_block }
25
+ second_block = lambda { :second_block }
26
+ @production.token(:first_token,&first_block)
27
+ @production.token(:second_token,second_block)
28
+
29
+ @production.tokens[:first_token].should == first_block
30
+ @production.tokens[:second_token].should == second_block
31
+ end
32
+
33
+ it "should have a 'mode' attribute with a default (:single)" do
34
+ @production.should respond_to(:mode)
35
+ @production.mode.should == :single
36
+
37
+ @production.should respond_to(:mode=)
38
+ @production.mode= :recursive
39
+ @production.mode.should == :recursive
40
+ end
41
+
42
+ it "should have a default result" do
43
+ @production.should respond_to(:default)
44
+ @production.default.should respond_to(:call)
45
+ @production.default.call.should be_nil
46
+
47
+ called = false
48
+ @production.default { called = true }
49
+ @production.default.call
50
+ called.should == true
51
+
52
+ block = mock "block"
53
+ block.should_receive(:call)
54
+ @production.default(block)
55
+ @production.default.call
56
+ end
57
+
58
+ it "should accept as a token an array" do
59
+ @production.token([:first,:second])
60
+ @production.tokens.has_key?([:first,:second]).should be_true
61
+ end
62
+
63
+ it "should assume that a lot of symbols as arguments are an array" do
64
+ @production.token :first, :second
65
+ @production.tokens.has_key?([:first,:second]).should be_true
66
+ end
67
+
68
+ it "should have a :raise_on_error attribute with true as a default" do
69
+ @production.should respond_to(:raise_on_error)
70
+ @production.should respond_to(:raise_on_error=)
71
+ @production.raise_on_error.should == true
72
+ @production.raise_on_error=false
73
+ @production.raise_on_error.should == false
74
+ end
75
+ end
@@ -0,0 +1,86 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ require 'recursive_production_compiler'
3
+
4
+ describe "A RecursiveProductionCompiler" do
5
+
6
+ before(:each) do
7
+ @compiler = RecursiveProductionCompiler.new
8
+ end
9
+
10
+ it "should add to the 'start' and 'end' method a behaviour that add a while cycle to the code" do
11
+ @compiler.should respond_to(:start)
12
+ @compiler.start(:new_production)
13
+ @compiler.end
14
+
15
+ @compiler.code.strip.should =~ /^def parse_new_production$/
16
+ @compiler.code.should =~ /^( |\t|\n)*result = productions\[:"new_production"\].default.call\(@scanner,self\)\n*$/
17
+ @compiler.code.should =~ /^( |\t|\n)*while not @scanner\.current\.nil\?\n(.|\t|\n)*end$/
18
+ @compiler.code.should =~ /^( |\t|\n)*return result\n*$/
19
+ @compiler.code.strip.should =~ /^( |\t|\n)*end$/
20
+ end
21
+
22
+ it "should modify the 'else' behaviour to break, instead of raise" do
23
+ @compiler.start(:new_production)
24
+ @compiler.token(:first_token)
25
+
26
+ lambda { @compiler.send(:build_else) }.should_not raise_error
27
+
28
+ @compiler.code.strip.should =~ /^( |\t|\n)*if @scanner.current == :"first_token"$/
29
+ @compiler.code.strip.should =~ /^( |\t|\n)*result = productions\[:"new_production"\].tokens\[:"first_token"\].call\(result,@scanner,self\)$/
30
+ @compiler.code.strip.should =~ /^( |\t|\n)*else$/
31
+ @compiler.code.strip.should =~ /^( |\t|\n)*break\n( |\t|\n)*end$/
32
+ end
33
+
34
+ it "should produce code that doesn't raise Exceptions if evaluated" do
35
+ @compiler.start(:fake)
36
+ @compiler.token(:first)
37
+ @compiler.token(:second)
38
+ @compiler.token(:third)
39
+ @compiler.end
40
+
41
+ @class = Class.new
42
+ lambda { @class.class_eval(@compiler.code) }.should_not raise_error
43
+ end
44
+ end
45
+
46
+ describe "The code produced by RecursiveProductionCompiler" do
47
+
48
+ before(:each) do
49
+ @compiler = RecursiveProductionCompiler.new
50
+ @compiler.start(:fake)
51
+ @compiler.token(:first)
52
+ @compiler.token(:second)
53
+ @compiler.token(:third)
54
+ @compiler.end
55
+
56
+ @class = Class.new
57
+ lambda { @class.class_eval(@compiler.code) }.should_not raise_error
58
+ @class.class_eval <<-CODE
59
+ attr_accessor :productions
60
+ attr_accessor :scanner
61
+ CODE
62
+ @instance = @class.new
63
+ @instance.productions = {
64
+ :fake => mock("ProductionSpecification")
65
+ }
66
+ @tokens = {
67
+ :first => mock('first'),
68
+ :second => mock('second'),
69
+ :third => mock('third')
70
+ }
71
+ @instance.productions[:fake].should_receive(:tokens).any_number_of_times.and_return(@tokens)
72
+ @instance.scanner = mock 'scanner'
73
+ @instance.scanner.should_receive(:next).any_number_of_times
74
+
75
+ @default = mock "default"
76
+ @instance.productions[:fake].should_receive(:default).any_number_of_times.and_return(@default)
77
+ @default.should_receive(:call).and_return(nil)
78
+ end
79
+
80
+ it "should call the specified block the number of times the token is given" do
81
+ @instance.scanner.should_receive(:current).exactly(7).times.and_return(*([:first]*6 + [nil]))
82
+ @tokens[:first].should_receive(:call).exactly(3).times.and_return("hello")
83
+ lambda { @instance.parse_fake.should == "hello" }.should_not raise_error
84
+ end
85
+ end
86
+
@@ -0,0 +1,320 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ require 'regexp_abstract_scanner'
3
+
4
+ describe "A class descending from RegexpAbstractScanner" do
5
+
6
+ before(:each) do
7
+ @class = Class.new(RegexpAbstractScanner)
8
+ @scanner = @class.new
9
+ @class.should_receive(:build).any_number_of_times
10
+ end
11
+
12
+ it "should respond to next and return Token.new if it isn't scanning a string" do
13
+ @scanner.should respond_to(:next)
14
+ @scanner.next.should be_kind_of(Token)
15
+ @scanner.next.should be_nil
16
+ end
17
+
18
+ it "should allow to add some regexp adding them to its scanning table" do
19
+ @class.should respond_to(:add_regexp)
20
+ r = mock "regexp"
21
+ r.should_receive(:starting_chars).and_return(["a","b"])
22
+ lambda { @class.add_regexp(r) }.should_not raise_error
23
+ @class.scanning_table['a'].should == r
24
+ @class.scanning_table['b'].should == r
25
+
26
+ r2 = mock "regexp2"
27
+ r2.should_receive(:starting_chars).and_return(["c","d"])
28
+ @class.add_regexp(r2)
29
+ @class.scanning_table['c'].should == r2
30
+ @class.scanning_table['d'].should == r2
31
+ end
32
+
33
+ it "should handle correctly if two regexp ('ab', 'ac') have some starting chars in common" do
34
+ r1 = mock "regexp1"
35
+ s1 = mock "state1"
36
+ s2 = mock "state2"
37
+ s3 = mock "state3"
38
+ r1.should_receive(:starting_chars).any_number_of_times.and_return(["a"])
39
+ r1.should_receive(:name).any_number_of_times.and_return("r1")
40
+ r1.should_receive(:init).any_number_of_times.and_return(s1)
41
+ s1.should_receive(:keys).any_number_of_times.and_return(["a"])
42
+ s1.should_receive(:[]).any_number_of_times.with("a").and_return(s2)
43
+ s1.should_receive(:final?).any_number_of_times.and_return(false)
44
+ s2.should_receive(:keys).any_number_of_times.and_return(['b'])
45
+ s2.should_receive(:[]).with('b').and_return(s3)
46
+ s2.should_receive(:final?).any_number_of_times.and_return(false)
47
+ s3.should_receive(:keys).any_number_of_times.and_return([])
48
+ s3.should_receive(:final?).any_number_of_times.and_return(true)
49
+ s1.should_receive(:error).any_number_of_times.and_return(:error)
50
+ s2.should_receive(:error).any_number_of_times.and_return(:error)
51
+ s3.should_receive(:error).any_number_of_times.and_return(:error)
52
+ s1.should_receive(:regexp).any_number_of_times.and_return(r1)
53
+ s2.should_receive(:regexp).any_number_of_times.and_return(r1)
54
+ s3.should_receive(:regexp).any_number_of_times.and_return(r1)
55
+
56
+ r2 = mock "regexp2"
57
+ s4 = mock "state4"
58
+ s5 = mock "state5"
59
+ s6 = mock "state6"
60
+ r2.should_receive(:starting_chars).any_number_of_times.and_return(["a"])
61
+ r2.should_receive(:name).any_number_of_times.and_return("r2")
62
+ r2.should_receive(:init).any_number_of_times.and_return(s4)
63
+ s4.should_receive(:keys).any_number_of_times.and_return(["a"])
64
+ s4.should_receive(:[]).any_number_of_times.with("a").and_return(s5)
65
+ s4.should_receive(:final?).any_number_of_times.and_return(false)
66
+ s5.should_receive(:keys).any_number_of_times.and_return(['c'])
67
+ s5.should_receive(:[]).with('c').and_return(s6)
68
+ s5.should_receive(:final?).any_number_of_times.and_return(false)
69
+ s6.should_receive(:keys).any_number_of_times.and_return([])
70
+ s6.should_receive(:final?).any_number_of_times.and_return(true)
71
+ s4.should_receive(:error).any_number_of_times.and_return(:error)
72
+ s5.should_receive(:error).any_number_of_times.and_return(:error)
73
+ s6.should_receive(:error).any_number_of_times.and_return(:error)
74
+ s4.should_receive(:regexp).any_number_of_times.and_return(r2)
75
+ s5.should_receive(:regexp).any_number_of_times.and_return(r2)
76
+ s6.should_receive(:regexp).any_number_of_times.and_return(r2)
77
+
78
+ @class.add_regexp(r1)
79
+ lambda { @class.add_regexp(r2) }.should_not raise_error
80
+ @class.scanning_table['a'].should be_kind_of(RegexpSpecification)
81
+ init = @class.scanning_table['a'].init
82
+ second = init['a']
83
+ second.keys.should == ['b','c']
84
+ second['b'].should == s3
85
+ second['c'].should == s6
86
+ end
87
+
88
+ it "should set the default value of the scanning table to the regexp that have starting_chars == :everything" do
89
+ r = mock "regexp"
90
+ r.should_receive(:starting_chars).and_return(:everything)
91
+ lambda { @class.add_regexp(r) }.should_not raise_error
92
+ lambda { @class.scanning_table['a'].should == r }.should_not raise_error
93
+ end
94
+
95
+ it "should expose its scanning table" do
96
+ @class.should respond_to(:scanning_table)
97
+ @class.scanning_table.should be_kind_of(Hash)
98
+ lambda { @class.scanning_table['a'] }.should_not raise_error
99
+ end
100
+
101
+ it "should be able to scan a simple regexp" do
102
+ r = mock "regexp"
103
+ s1 = mock "state1"
104
+ s2 = mock "state"
105
+ r.should_receive(:init).and_return(s1)
106
+ r.should_receive(:starting_chars).and_return(["a"])
107
+ r.should_receive(:name).and_return(:a_name)
108
+ s1.should_receive(:[]).twice.with('a').and_return(s2)
109
+ s2.should_receive(:[]).and_return(:error)
110
+ s2.should_receive(:final?).and_return(true)
111
+ s2.should_receive(:regexp).and_return(r)
112
+
113
+ @class.add_regexp(r)
114
+ @scanner.scan("a")
115
+ token = @scanner.next
116
+ token.should be_kind_of(Token)
117
+ token.should == "a"
118
+ token.should == :a_name
119
+ token.line.should == 0
120
+ token.char.should == 0
121
+
122
+ @scanner.scan("b")
123
+ lambda { @scanner.next }.should raise_error(LLIPError)
124
+ end
125
+
126
+ it "should be able to scan '.*b' and 'eb' " do
127
+ r1 = mock "first"
128
+ s1 = mock "state1"
129
+ s2 = mock "state2"
130
+ r1.should_receive(:init).any_number_of_times.and_return(s1)
131
+ r1.should_receive(:starting_chars).and_return(:everything)
132
+ r1.should_receive(:name).any_number_of_times.and_return(:first_name)
133
+ s1.should_receive(:[]).any_number_of_times.with('b').and_return(s2)
134
+ s1.should_receive(:[]).any_number_of_times.and_return(s1)
135
+ s1.should_receive(:final?).and_return(false)
136
+ s2.should_receive(:[]).any_number_of_times.and_return(:error)
137
+ s2.should_receive(:final?).any_number_of_times.and_return(true)
138
+ s1.should_receive(:regexp).any_number_of_times.and_return(r1)
139
+ s2.should_receive(:regexp).any_number_of_times.and_return(r1)
140
+
141
+ r2 = mock "second"
142
+ s3 = mock "state3"
143
+ s4 = mock "state4"
144
+ s5 = mock "state5"
145
+ r2.should_receive(:init).any_number_of_times.and_return(s3)
146
+ r2.should_receive(:starting_chars).and_return(["e"])
147
+ r2.should_receive(:name).any_number_of_times.and_return(:second_name)
148
+ s3.should_receive(:[]).any_number_of_times.with('e').and_return(s4)
149
+ s3.should_receive(:final?).any_number_of_times.and_return(false)
150
+ s4.should_receive(:[]).any_number_of_times.with('b').and_return(s5)
151
+ s5.should_receive(:[]).any_number_of_times.and_return(:error)
152
+ s5.should_receive(:final?).any_number_of_times.and_return(true)
153
+ s3.should_receive(:regexp).any_number_of_times.and_return(r2)
154
+ s4.should_receive(:regexp).any_number_of_times.and_return(r2)
155
+ s5.should_receive(:regexp).any_number_of_times.and_return(r2)
156
+
157
+ @class.add_regexp(r1)
158
+ @class.add_regexp(r2)
159
+
160
+ @scanner.scan("acdb \nb abd")
161
+ @scanner.next.should == "acdb"
162
+ @scanner.current.char == 0
163
+ @scanner.current.line == 0
164
+ @scanner.current.name == :first_name
165
+
166
+ @scanner.current.should == "acdb"
167
+ @scanner.next.should == " \nb"
168
+ @scanner.current.char == 4
169
+ @scanner.current.line == 0
170
+ @scanner.next.should == " ab"
171
+ @scanner.current.line == 1
172
+ @scanner.current.char == 0
173
+ lambda { @scanner.next }.should raise_error(UnvalidTokenError)
174
+
175
+ @scanner.scan("b")
176
+ @scanner.next
177
+ token = @scanner.next
178
+ token.should be_nil
179
+ token.line.should == 2
180
+
181
+ @scanner.scan("eb")
182
+ token = @scanner.next
183
+ token.should == "eb"
184
+ token.name.should == :second_name
185
+ end
186
+
187
+
188
+ it "should be able to scan '\+ *'" do
189
+ r = mock "regexp"
190
+ s1 = mock "state1"
191
+ s2 = mock "state"
192
+ r.should_receive(:init).any_number_of_times.and_return(s1)
193
+ r.should_receive(:starting_chars).and_return(['+'])
194
+ r.should_receive(:name).any_number_of_times.and_return(:a_name)
195
+ s1.should_receive(:[]).any_number_of_times.with('+').and_return(s2)
196
+ s2.should_receive(:[]).any_number_of_times.with(' ').and_return(s2)
197
+ s2.should_receive(:[]).any_number_of_times.and_return(:error)
198
+ s2.should_receive(:final?).any_number_of_times.and_return(true)
199
+ s1.should_receive(:regexp).any_number_of_times.and_return(r)
200
+ s2.should_receive(:regexp).any_number_of_times.and_return(r)
201
+
202
+ @class.add_regexp(r)
203
+ @scanner.scan("+")
204
+ @scanner.next.should == "+"
205
+ @scanner.current.should == "+"
206
+ @scanner.next.should be_nil
207
+
208
+ @scanner.scan("+ ")
209
+ @scanner.next.should == "+ "
210
+ @scanner.current.should == "+ "
211
+ @scanner.next.should be_nil
212
+
213
+ @scanner.scan("+ ")
214
+ @scanner.next.should == "+ "
215
+ @scanner.current.should == "+ "
216
+ @scanner.next.should be_nil
217
+ end
218
+
219
+ end
220
+
221
+ describe "A class descending from RegexpAbstractScanner with the build method exposed" do
222
+
223
+ before(:each) do
224
+ @class = Class.new(RegexpAbstractScanner)
225
+ end
226
+
227
+ it "should be able to scan '.+' and 'eb' " do
228
+ r1 = mock "first"
229
+ s1 = mock "state1"
230
+ r1.should_receive(:init).any_number_of_times.and_return(s1)
231
+ r1.should_receive(:starting_chars).and_return(:everything)
232
+ r1.should_receive(:name).any_number_of_times.and_return(:first_name)
233
+ r1.should_receive(:last).any_number_of_times.and_return([s1])
234
+ s1.should_receive(:[]=).with("e",:error)
235
+ s1.should_receive(:[]).with("e").and_return(:error)
236
+ s1.should_receive(:[]).any_number_of_times.and_return(s1)
237
+ s1.should_receive(:final?).any_number_of_times.and_return(true)
238
+ s1.should_receive(:error).any_number_of_times.and_return(s1)
239
+ s1.should_receive(:regexp).any_number_of_times.and_return(r1)
240
+
241
+ r2 = mock "second"
242
+ s3 = mock "state3"
243
+ s4 = mock "state4"
244
+ s5 = mock "state5"
245
+ r2.should_receive(:init).any_number_of_times.and_return(s3)
246
+ r2.should_receive(:starting_chars).and_return(["e"])
247
+ r2.should_receive(:name).any_number_of_times.and_return(:second_name)
248
+ r2.should_receive(:last).any_number_of_times.and_return([s5])
249
+ s3.should_receive(:[]).any_number_of_times.with('e').and_return(s4)
250
+ s3.should_receive(:final?).any_number_of_times.and_return(false)
251
+ s4.should_receive(:[]).any_number_of_times.with('b').and_return(s5)
252
+ s5.should_receive(:[]).any_number_of_times.and_return(:error)
253
+ s5.should_receive(:final?).any_number_of_times.and_return(true)
254
+ s5.should_receive(:error).any_number_of_times.and_return(nil)
255
+ s3.should_receive(:regexp).any_number_of_times.and_return(r2)
256
+ s4.should_receive(:regexp).any_number_of_times.and_return(r2)
257
+ s5.should_receive(:regexp).any_number_of_times.and_return(r2)
258
+
259
+ @class.add_regexp(r1)
260
+ @class.add_regexp(r2)
261
+
262
+ @scanner = @class.new
263
+
264
+ @scanner.scan("acdb \nb abd")
265
+ token = @scanner.next
266
+ token.should == "acdb \nb abd"
267
+
268
+ @scanner.scan("aeb")
269
+ token = @scanner.next
270
+ token.should == "a"
271
+ token.name.should == :first_name
272
+ token = @scanner.next
273
+ token.should == "eb"
274
+ token.name.should == :second_name
275
+
276
+ @scanner.scan("eb")
277
+ token = @scanner.next
278
+ token.should == "eb"
279
+ token.name.should == :second_name
280
+ end
281
+
282
+ it "should recall the build method if a new regexp is added and the scanner has already been built" do
283
+ @class.built?.should == false
284
+ @scanner = @class.new
285
+ @class.built?.should == true
286
+ @class.should_receive(:build)
287
+ r = mock "regexp"
288
+ r.should_receive(:starting_chars).and_return(["a"])
289
+ @class.add_regexp(r)
290
+ end
291
+
292
+ it "should have a build method which sets :error for every final state which ends with '.*' or '.+'" do
293
+ @class.should respond_to(:build)
294
+ @class.should respond_to(:built?)
295
+
296
+ r1 = mock "normal"
297
+ s1 = mock "state1"
298
+ s2 = mock "state2"
299
+ r1.should_receive(:init).any_number_of_times.and_return(s1)
300
+ r1.should_receive(:starting_chars).and_return(["a"])
301
+ r1.should_receive(:last).and_return([s2])
302
+ s2.should_receive(:error).any_number_of_times.and_return(:error)
303
+
304
+ r2 = mock "everything"
305
+ s3 = mock "state3"
306
+ r2.should_receive(:init).any_number_of_times.and_return(s3)
307
+ r2.should_receive(:starting_chars).and_return(:everything)
308
+ r2.should_receive(:last).and_return([s3])
309
+ s3.should_receive(:error).any_number_of_times.and_return(s3)
310
+ s3.should_receive(:[]=).with("a",:error)
311
+
312
+ @class.add_regexp(r1)
313
+ @class.add_regexp(r2)
314
+
315
+ @class.built?.should == false
316
+
317
+ @class.build.should == @class
318
+ @class.built?.should == true
319
+ end
320
+ end