llip 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/MIT-LICENSE +21 -0
- data/Manifest.txt +45 -0
- data/README.txt +148 -0
- data/Rakefile +66 -0
- data/examples/ariteval/ariteval.rb +132 -0
- data/examples/ariteval/evaluator.rb +61 -0
- data/examples/ariteval/exp.rb +104 -0
- data/lib/llip.rb +6 -0
- data/lib/llip/abstract_parser.rb +170 -0
- data/lib/llip/abstract_scanner.rb +83 -0
- data/lib/llip/buffer.rb +35 -0
- data/lib/llip/llip_error.rb +43 -0
- data/lib/llip/parser.rb +93 -0
- data/lib/llip/production_compiler.rb +168 -0
- data/lib/llip/production_specification.rb +79 -0
- data/lib/llip/recursive_production_compiler.rb +35 -0
- data/lib/llip/regexp_abstract_scanner.rb +116 -0
- data/lib/llip/regexp_parser.rb +197 -0
- data/lib/llip/regexp_scanner.rb +33 -0
- data/lib/llip/regexp_specification.rb +210 -0
- data/lib/llip/token.rb +47 -0
- data/lib/llip/visitable.rb +37 -0
- data/spec/ariteval/ariteval_spec.rb +111 -0
- data/spec/ariteval/evaluator_spec.rb +106 -0
- data/spec/ariteval/exp_spec.rb +232 -0
- data/spec/llip/abstract_parser_spec.rb +273 -0
- data/spec/llip/abstract_scanner_spec.rb +152 -0
- data/spec/llip/buffer_spec.rb +60 -0
- data/spec/llip/llip_error_spec.rb +77 -0
- data/spec/llip/parser_spec.rb +163 -0
- data/spec/llip/production_compiler_spec.rb +271 -0
- data/spec/llip/production_specification_spec.rb +75 -0
- data/spec/llip/recursive_production_compiler_spec.rb +86 -0
- data/spec/llip/regexp_abstract_scanner_spec.rb +320 -0
- data/spec/llip/regexp_parser_spec.rb +265 -0
- data/spec/llip/regexp_scanner_spec.rb +40 -0
- data/spec/llip/regexp_specification_spec.rb +734 -0
- data/spec/llip/token_spec.rb +70 -0
- data/spec/llip/visitable_spec.rb +38 -0
- data/spec/spec_helper.rb +10 -0
- 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
|