llip 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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