parslet 1.7.0 → 1.7.1
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.
- checksums.yaml +4 -4
- data/README +1 -1
- data/lib/parslet/context.rb +1 -1
- data/lib/parslet/parser.rb +1 -1
- data/parslet.gemspec +18 -0
- data/spec/acceptance/examples_spec.rb +37 -0
- data/spec/acceptance/infix_parser_spec.rb +112 -0
- data/spec/acceptance/regression_spec.rb +314 -0
- data/spec/acceptance/repetition_and_maybe_spec.rb +42 -0
- data/spec/acceptance/unconsumed_input_spec.rb +21 -0
- data/spec/parslet/atom_results_spec.rb +39 -0
- data/spec/parslet/atoms/alternative_spec.rb +26 -0
- data/spec/parslet/atoms/base_spec.rb +126 -0
- data/spec/parslet/atoms/capture_spec.rb +21 -0
- data/spec/parslet/atoms/combinations_spec.rb +5 -0
- data/spec/parslet/atoms/dsl_spec.rb +25 -0
- data/spec/parslet/atoms/entity_spec.rb +77 -0
- data/spec/parslet/atoms/infix_spec.rb +5 -0
- data/spec/parslet/atoms/lookahead_spec.rb +22 -0
- data/spec/parslet/atoms/named_spec.rb +4 -0
- data/spec/parslet/atoms/re_spec.rb +14 -0
- data/spec/parslet/atoms/repetition_spec.rb +24 -0
- data/spec/parslet/atoms/scope_spec.rb +26 -0
- data/spec/parslet/atoms/sequence_spec.rb +28 -0
- data/spec/parslet/atoms/str_spec.rb +15 -0
- data/spec/parslet/atoms/visitor_spec.rb +80 -0
- data/spec/parslet/atoms_spec.rb +429 -0
- data/spec/parslet/convenience_spec.rb +48 -0
- data/spec/parslet/error_reporter/contextual_spec.rb +115 -0
- data/spec/parslet/error_reporter/deepest_spec.rb +73 -0
- data/spec/parslet/error_reporter/tree_spec.rb +7 -0
- data/spec/parslet/export_spec.rb +67 -0
- data/spec/parslet/expression/treetop_spec.rb +74 -0
- data/spec/parslet/minilisp.citrus +29 -0
- data/spec/parslet/minilisp.tt +29 -0
- data/spec/parslet/parser_spec.rb +31 -0
- data/spec/parslet/parslet_spec.rb +38 -0
- data/spec/parslet/pattern_spec.rb +272 -0
- data/spec/parslet/position_spec.rb +14 -0
- data/spec/parslet/rig/rspec_spec.rb +54 -0
- data/spec/parslet/scope_spec.rb +45 -0
- data/spec/parslet/slice_spec.rb +144 -0
- data/spec/parslet/source/line_cache_spec.rb +74 -0
- data/spec/parslet/source_spec.rb +168 -0
- data/spec/parslet/transform/context_spec.rb +35 -0
- data/spec/parslet/transform_spec.rb +165 -0
- data/spec/spec_helper.rb +38 -0
- metadata +46 -4
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Parslet::Atoms::Scope do
|
4
|
+
include Parslet
|
5
|
+
include Parslet::Atoms::DSL
|
6
|
+
|
7
|
+
|
8
|
+
let(:context) { Parslet::Atoms::Context.new(nil) }
|
9
|
+
let(:captures) { context.captures }
|
10
|
+
|
11
|
+
def inject string, parser
|
12
|
+
source = Parslet::Source.new(string)
|
13
|
+
parser.apply(source, context, true)
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:aabb) {
|
17
|
+
scope {
|
18
|
+
match['ab'].capture(:f) >> dynamic { |s,c| str(c.captures[:f]) }
|
19
|
+
}
|
20
|
+
}
|
21
|
+
it "keeps values of captures outside" do
|
22
|
+
captures[:f] = 'old_value'
|
23
|
+
inject 'aa', aabb
|
24
|
+
captures[:f].should == 'old_value'
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Parslet::Atoms::Sequence do
|
4
|
+
include Parslet
|
5
|
+
|
6
|
+
let(:sequence) { described_class.new }
|
7
|
+
|
8
|
+
describe '>> shortcut' do
|
9
|
+
let(:sequence) { str('a') >> str('b') }
|
10
|
+
|
11
|
+
context "when chained with different atoms" do
|
12
|
+
before(:each) {
|
13
|
+
# Chain something else to the sequence parslet. If it modifies the
|
14
|
+
# parslet atom in place, we'll notice:
|
15
|
+
|
16
|
+
sequence >> str('d')
|
17
|
+
}
|
18
|
+
let!(:chained) { sequence >> str('c') }
|
19
|
+
|
20
|
+
|
21
|
+
it "is side-effect free" do
|
22
|
+
chained.should parse('abc')
|
23
|
+
chained.should_not parse('abdc')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# Encoding: UTF-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Parslet::Atoms::Str do
|
6
|
+
def str(s)
|
7
|
+
described_class.new(s)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe 'regression #1: multibyte characters' do
|
11
|
+
it "parses successfully (length check works)" do
|
12
|
+
str('あああ').should parse('あああ')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Parslet::Atoms do
|
4
|
+
include Parslet
|
5
|
+
let(:visitor) { flexmock(:visitor) }
|
6
|
+
|
7
|
+
describe Parslet::Atoms::Str do
|
8
|
+
let(:parslet) { str('foo') }
|
9
|
+
it "should call back visitor" do
|
10
|
+
visitor.should_receive(:visit_str).with('foo').once
|
11
|
+
|
12
|
+
parslet.accept(visitor)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
describe Parslet::Atoms::Re do
|
16
|
+
let(:parslet) { match['abc'] }
|
17
|
+
it "should call back visitor" do
|
18
|
+
visitor.should_receive(:visit_re).with('[abc]').once
|
19
|
+
|
20
|
+
parslet.accept(visitor)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
describe Parslet::Atoms::Sequence do
|
24
|
+
let(:parslet) { str('a') >> str('b') }
|
25
|
+
it "should call back visitor" do
|
26
|
+
visitor.should_receive(:visit_sequence).with(Array).once
|
27
|
+
|
28
|
+
parslet.accept(visitor)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
describe Parslet::Atoms::Repetition do
|
32
|
+
let(:parslet) { str('a').repeat(1,2) }
|
33
|
+
it "should call back visitor" do
|
34
|
+
visitor.should_receive(:visit_repetition).with(:repetition, 1, 2, Parslet::Atoms::Base).once
|
35
|
+
|
36
|
+
parslet.accept(visitor)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
describe Parslet::Atoms::Alternative do
|
40
|
+
let(:parslet) { str('a') | str('b') }
|
41
|
+
it "should call back visitor" do
|
42
|
+
visitor.should_receive(:visit_alternative).with(Array).once
|
43
|
+
|
44
|
+
parslet.accept(visitor)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
describe Parslet::Atoms::Named do
|
48
|
+
let(:parslet) { str('a').as(:a) }
|
49
|
+
it "should call back visitor" do
|
50
|
+
visitor.should_receive(:visit_named).with(:a, Parslet::Atoms::Base).once
|
51
|
+
|
52
|
+
parslet.accept(visitor)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
describe Parslet::Atoms::Entity do
|
56
|
+
let(:parslet) { Parslet::Atoms::Entity.new('foo', &lambda {}) }
|
57
|
+
it "should call back visitor" do
|
58
|
+
visitor.should_receive(:visit_entity).with('foo', Proc).once
|
59
|
+
|
60
|
+
parslet.accept(visitor)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
describe Parslet::Atoms::Lookahead do
|
64
|
+
let(:parslet) { str('a').absent? }
|
65
|
+
it "should call back visitor" do
|
66
|
+
visitor.should_receive(:visit_lookahead).with(false, Parslet::Atoms::Base).once
|
67
|
+
|
68
|
+
parslet.accept(visitor)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
describe "< Parslet::Parser" do
|
72
|
+
let(:parslet) { Parslet::Parser.new }
|
73
|
+
it "calls back to visitor" do
|
74
|
+
visitor.should_receive(:visit_parser).with(:root).once
|
75
|
+
|
76
|
+
flexmock(parslet, :root => :root)
|
77
|
+
parslet.accept(visitor)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,429 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'timeout'
|
4
|
+
require 'parslet'
|
5
|
+
|
6
|
+
describe Parslet do
|
7
|
+
def not_parse
|
8
|
+
raise_error(Parslet::ParseFailed)
|
9
|
+
end
|
10
|
+
|
11
|
+
include Parslet
|
12
|
+
extend Parslet
|
13
|
+
|
14
|
+
def src(str); Parslet::Source.new str; end
|
15
|
+
let(:context) { Parslet::Atoms::Context.new }
|
16
|
+
|
17
|
+
describe "match('[abc]')" do
|
18
|
+
attr_reader :parslet
|
19
|
+
before(:each) do
|
20
|
+
@parslet = match('[abc]')
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should parse {a,b,c}" do
|
24
|
+
parslet.parse('a')
|
25
|
+
parslet.parse('b')
|
26
|
+
parslet.parse('c')
|
27
|
+
end
|
28
|
+
it "should not parse d" do
|
29
|
+
cause = catch_failed_parse {
|
30
|
+
parslet.parse('d')
|
31
|
+
}
|
32
|
+
cause.to_s.should == "Failed to match [abc] at line 1 char 1."
|
33
|
+
end
|
34
|
+
it "should print as [abc]" do
|
35
|
+
parslet.inspect.should == "[abc]"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
describe "match(['[a]').repeat(3)" do
|
39
|
+
attr_reader :parslet
|
40
|
+
before(:each) do
|
41
|
+
@parslet = match('[a]').repeat(3)
|
42
|
+
end
|
43
|
+
|
44
|
+
context "when failing on input 'aa'" do
|
45
|
+
let!(:cause) {
|
46
|
+
catch_failed_parse { parslet.parse('aa') }
|
47
|
+
}
|
48
|
+
it "should have a relevant cause" do
|
49
|
+
cause.to_s.should == "Expected at least 3 of [a] at line 1 char 1."
|
50
|
+
end
|
51
|
+
it "should have a tree with 2 nodes" do
|
52
|
+
cause.children.size.should == 1
|
53
|
+
end
|
54
|
+
end
|
55
|
+
it "should succeed on 'aaa'" do
|
56
|
+
parslet.parse('aaa')
|
57
|
+
end
|
58
|
+
it "should succeed on many 'a'" do
|
59
|
+
parslet.parse('a'*100)
|
60
|
+
end
|
61
|
+
it "should inspect as [a]{3, }" do
|
62
|
+
parslet.inspect.should == "[a]{3, }"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
describe "str('foo')" do
|
66
|
+
attr_reader :parslet
|
67
|
+
before(:each) do
|
68
|
+
@parslet = str('foo')
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should parse 'foo'" do
|
72
|
+
parslet.parse('foo')
|
73
|
+
end
|
74
|
+
it "should not parse 'bar'" do
|
75
|
+
cause = catch_failed_parse { parslet.parse('bar') }
|
76
|
+
cause.to_s.should ==
|
77
|
+
"Expected \"foo\", but got \"bar\" at line 1 char 1."
|
78
|
+
end
|
79
|
+
it "should inspect as 'foo'" do
|
80
|
+
parslet.inspect.should == "'foo'"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
describe "str('foo').maybe" do
|
84
|
+
let(:parslet) { str('foo').maybe }
|
85
|
+
|
86
|
+
it "should parse a foo" do
|
87
|
+
parslet.parse('foo')
|
88
|
+
end
|
89
|
+
it "should leave pos untouched if there is no foo" do
|
90
|
+
source = src('bar')
|
91
|
+
parslet.apply(source, context)
|
92
|
+
source.pos.charpos.should == 0
|
93
|
+
end
|
94
|
+
it "should inspect as 'foo'?" do
|
95
|
+
parslet.inspect.should == "'foo'?"
|
96
|
+
end
|
97
|
+
context "when parsing 'foo'" do
|
98
|
+
subject { parslet.parse('foo') }
|
99
|
+
|
100
|
+
it { should == 'foo' }
|
101
|
+
end
|
102
|
+
context "when parsing ''" do
|
103
|
+
subject { parslet.parse('') }
|
104
|
+
|
105
|
+
it { should == '' }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
describe "str('foo') >> str('bar')" do
|
109
|
+
let(:parslet) { str('foo') >> str('bar') }
|
110
|
+
|
111
|
+
context "when it fails on input 'foobaz'" do
|
112
|
+
let!(:cause) {
|
113
|
+
catch_failed_parse { parslet.parse('foobaz') }
|
114
|
+
}
|
115
|
+
|
116
|
+
it "should not parse 'foobaz'" do
|
117
|
+
cause.to_s.should == "Failed to match sequence ('foo' 'bar') at line 1 char 4."
|
118
|
+
end
|
119
|
+
it "should have 2 nodes in error tree" do
|
120
|
+
cause.children.size.should == 1
|
121
|
+
end
|
122
|
+
end
|
123
|
+
it "should parse 'foobar'" do
|
124
|
+
parslet.parse('foobar')
|
125
|
+
end
|
126
|
+
it "should inspect as ('foo' 'bar')" do
|
127
|
+
parslet.inspect.should == "'foo' 'bar'"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
describe "str('foo') | str('bar')" do
|
131
|
+
attr_reader :parslet
|
132
|
+
before(:each) do
|
133
|
+
@parslet = str('foo') | str('bar')
|
134
|
+
end
|
135
|
+
|
136
|
+
context "when failing on input 'baz'" do
|
137
|
+
let!(:cause) {
|
138
|
+
catch_failed_parse { parslet.parse('baz') }
|
139
|
+
}
|
140
|
+
|
141
|
+
it "should have a sensible cause" do
|
142
|
+
cause.to_s.should == "Expected one of ['foo', 'bar'] at line 1 char 1."
|
143
|
+
end
|
144
|
+
it "should have an error tree with 3 nodes" do
|
145
|
+
cause.children.size.should == 2
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
it "should accept 'foo'" do
|
150
|
+
parslet.parse('foo')
|
151
|
+
end
|
152
|
+
it "should accept 'bar'" do
|
153
|
+
parslet.parse('bar')
|
154
|
+
end
|
155
|
+
it "should inspect as ('foo' / 'bar')" do
|
156
|
+
parslet.inspect.should == "'foo' / 'bar'"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
describe "str('foo').present? (positive lookahead)" do
|
160
|
+
attr_reader :parslet
|
161
|
+
before(:each) do
|
162
|
+
@parslet = str('foo').present?
|
163
|
+
end
|
164
|
+
|
165
|
+
it "should inspect as &'foo'" do
|
166
|
+
parslet.inspect.should == "&'foo'"
|
167
|
+
end
|
168
|
+
context "when fed 'foo'" do
|
169
|
+
it "should parse" do
|
170
|
+
success, _ = parslet.apply(src('foo'), context)
|
171
|
+
success.should == true
|
172
|
+
end
|
173
|
+
it "should not change input position" do
|
174
|
+
source = src('foo')
|
175
|
+
parslet.apply(source, context)
|
176
|
+
source.pos.charpos.should == 0
|
177
|
+
end
|
178
|
+
end
|
179
|
+
context "when fed 'bar'" do
|
180
|
+
it "should not parse" do
|
181
|
+
lambda { parslet.parse('bar') }.should not_parse
|
182
|
+
end
|
183
|
+
end
|
184
|
+
describe "<- #parse" do
|
185
|
+
it "should return nil" do
|
186
|
+
parslet.apply(src('foo'), context).should == [true, nil]
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
describe "str('foo').absent? (negative lookahead)" do
|
191
|
+
attr_reader :parslet
|
192
|
+
before(:each) do
|
193
|
+
@parslet = str('foo').absent?
|
194
|
+
end
|
195
|
+
|
196
|
+
it "should inspect as !'foo'" do
|
197
|
+
parslet.inspect.should == "!'foo'"
|
198
|
+
end
|
199
|
+
context "when fed 'bar'" do
|
200
|
+
it "should parse" do
|
201
|
+
parslet.apply(src('bar'), context).should == [true, nil]
|
202
|
+
end
|
203
|
+
it "should not change input position" do
|
204
|
+
source = src('bar')
|
205
|
+
parslet.apply(source, context)
|
206
|
+
source.pos.charpos.should == 0
|
207
|
+
end
|
208
|
+
end
|
209
|
+
context "when fed 'foo'" do
|
210
|
+
it "should not parse" do
|
211
|
+
lambda { parslet.parse('foo') }.should not_parse
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
describe "non greedy matcher combined with greedy matcher (possible loop)" do
|
216
|
+
attr_reader :parslet
|
217
|
+
before(:each) do
|
218
|
+
# repeat will always succeed, since it has a minimum of 0. It will not
|
219
|
+
# modify input position in that case. absent? will, depending on
|
220
|
+
# implementation, match as much as possible and call its inner element
|
221
|
+
# again. This leads to an infinite loop. This example tests for the
|
222
|
+
# absence of that loop.
|
223
|
+
@parslet = str('foo').repeat.maybe
|
224
|
+
end
|
225
|
+
|
226
|
+
it "should not loop infinitely" do
|
227
|
+
lambda {
|
228
|
+
timeout(1) { parslet.parse('bar') }
|
229
|
+
}.should raise_error(Parslet::ParseFailed)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
describe "any" do
|
233
|
+
attr_reader :parslet
|
234
|
+
before(:each) do
|
235
|
+
@parslet = any
|
236
|
+
end
|
237
|
+
|
238
|
+
it "should match" do
|
239
|
+
parslet.parse('.')
|
240
|
+
end
|
241
|
+
it "should consume one char" do
|
242
|
+
source = src('foo')
|
243
|
+
parslet.apply(source, context)
|
244
|
+
source.pos.charpos.should == 1
|
245
|
+
end
|
246
|
+
end
|
247
|
+
describe "eof behaviour" do
|
248
|
+
context "when the pattern just doesn't consume the input" do
|
249
|
+
let (:parslet) { any }
|
250
|
+
|
251
|
+
it "should fail the parse" do
|
252
|
+
cause = catch_failed_parse { parslet.parse('..') }
|
253
|
+
cause.to_s.should == "Don't know what to do with \".\" at line 1 char 2."
|
254
|
+
end
|
255
|
+
end
|
256
|
+
context "when the pattern doesn't match the input" do
|
257
|
+
let (:parslet) { (str('a')).repeat(1) }
|
258
|
+
attr_reader :exception
|
259
|
+
before(:each) do
|
260
|
+
begin
|
261
|
+
parslet.parse('a.')
|
262
|
+
rescue => @exception
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
it "raises Parslet::ParseFailed" do
|
267
|
+
# ParseFailed here, because the input doesn't match the parser grammar.
|
268
|
+
exception.should be_kind_of(Parslet::ParseFailed)
|
269
|
+
end
|
270
|
+
it "has the correct error message" do
|
271
|
+
exception.message.should == \
|
272
|
+
"Extra input after last repetition at line 1 char 2."
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
describe "<- #as(name)" do
|
278
|
+
context "str('foo').as(:bar)" do
|
279
|
+
it "should return :bar => 'foo'" do
|
280
|
+
str('foo').as(:bar).parse('foo').should == { :bar => 'foo' }
|
281
|
+
end
|
282
|
+
end
|
283
|
+
context "match('[abc]').as(:name)" do
|
284
|
+
it "should return :name => 'b'" do
|
285
|
+
match('[abc]').as(:name).parse('b').should == { :name => 'b' }
|
286
|
+
end
|
287
|
+
end
|
288
|
+
context "match('[abc]').repeat.as(:name)" do
|
289
|
+
it "should return collated result ('abc')" do
|
290
|
+
match('[abc]').repeat.as(:name).
|
291
|
+
parse('abc').should == { :name => 'abc' }
|
292
|
+
end
|
293
|
+
end
|
294
|
+
context "(str('a').as(:a) >> str('b').as(:b)).as(:c)" do
|
295
|
+
it "should return a hash of hashes" do
|
296
|
+
(str('a').as(:a) >> str('b').as(:b)).as(:c).
|
297
|
+
parse('ab').should == {
|
298
|
+
:c => {
|
299
|
+
:a => 'a',
|
300
|
+
:b => 'b'
|
301
|
+
}
|
302
|
+
}
|
303
|
+
end
|
304
|
+
end
|
305
|
+
context "(str('a').as(:a) >> str('ignore') >> str('b').as(:b))" do
|
306
|
+
it "should correctly flatten (leaving out 'ignore')" do
|
307
|
+
(str('a').as(:a) >> str('ignore') >> str('b').as(:b)).
|
308
|
+
parse('aignoreb').should ==
|
309
|
+
{
|
310
|
+
:a => 'a',
|
311
|
+
:b => 'b'
|
312
|
+
}
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
context "(str('a') >> str('ignore') >> str('b')) (no .as(...))" do
|
317
|
+
it "should return simply the original string" do
|
318
|
+
(str('a') >> str('ignore') >> str('b')).
|
319
|
+
parse('aignoreb').should == 'aignoreb'
|
320
|
+
end
|
321
|
+
end
|
322
|
+
context "str('a').as(:a) >> str('b').as(:a)" do
|
323
|
+
attr_reader :parslet
|
324
|
+
before(:each) do
|
325
|
+
@parslet = str('a').as(:a) >> str('b').as(:a)
|
326
|
+
end
|
327
|
+
|
328
|
+
it "should issue a warning that a key is being overwritten in merge" do
|
329
|
+
flexmock(parslet).
|
330
|
+
should_receive(:warn).once
|
331
|
+
parslet.parse('ab').should == { :a => 'b' }
|
332
|
+
end
|
333
|
+
it "should return :a => 'b'" do
|
334
|
+
flexmock(parslet).
|
335
|
+
should_receive(:warn)
|
336
|
+
|
337
|
+
parslet.parse('ab').should == { :a => 'b' }
|
338
|
+
end
|
339
|
+
end
|
340
|
+
context "str('a').absent?" do
|
341
|
+
it "should return something in merge, even though it is nil" do
|
342
|
+
(str('a').absent? >> str('b').as(:b)).
|
343
|
+
parse('b').should == {:b => 'b'}
|
344
|
+
end
|
345
|
+
end
|
346
|
+
context "str('a').as(:a).repeat" do
|
347
|
+
it "should return an array of subtrees" do
|
348
|
+
str('a').as(:a).repeat.
|
349
|
+
parse('aa').should == [{:a=>'a'}, {:a=>'a'}]
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
353
|
+
describe "<- #flatten(val)" do
|
354
|
+
def call(val)
|
355
|
+
dummy = str('a')
|
356
|
+
flexmock(dummy, :warn => nil)
|
357
|
+
dummy.flatten(val)
|
358
|
+
end
|
359
|
+
|
360
|
+
[
|
361
|
+
# In absence of named subtrees: ----------------------------------------
|
362
|
+
# Sequence or Repetition
|
363
|
+
[ [:sequence, 'a', 'b'], 'ab' ],
|
364
|
+
[ [:repetition, 'a', 'a'], 'aa' ],
|
365
|
+
|
366
|
+
# Nested inside another node
|
367
|
+
[ [:sequence, [:sequence, 'a', 'b']], 'ab' ],
|
368
|
+
# Combined with lookahead (nil)
|
369
|
+
[ [:sequence, nil, 'a'], 'a' ],
|
370
|
+
|
371
|
+
# Including named subtrees ---------------------------------------------
|
372
|
+
# Atom: A named subtree
|
373
|
+
[ {:a=>'a'}, {:a=>'a'} ],
|
374
|
+
# Composition of subtrees
|
375
|
+
[ [:sequence, {:a=>'a'},{:b=>'b'}], {:a=>'a',:b=>'b'} ],
|
376
|
+
# Mixed subtrees :sequence of :repetition yields []
|
377
|
+
[ [:sequence, [:repetition, {:a => 'a'}], {:a => 'a'} ], [{:a=>'a'}, {:a=>'a'}]],
|
378
|
+
[ [:sequence, {:a => 'a'},[:repetition, {:a => 'a'}] ], [{:a=>'a'}, {:a=>'a'}]],
|
379
|
+
[ [:sequence, [:repetition, {:a => 'a'}],[:repetition, {:a => 'a'}] ], [{:a=>'a'}, {:a=>'a'}]],
|
380
|
+
# Repetition
|
381
|
+
[ [:repetition, [:repetition, {:a=>'a'}], [:repetition, {:a=>'a'}]],
|
382
|
+
[{:a => 'a'}, {:a => 'a'}]],
|
383
|
+
[ [:repetition, {:a=>'a'}, 'a', {:a=>'a'}], [{:a=>'a'}, {:a=>'a'}]],
|
384
|
+
[ [:repetition, {:a=>'a'}, [:repetition, {:b=>'b'}]], [{:a=>'a'}] ],
|
385
|
+
|
386
|
+
# Some random samples --------------------------------------------------
|
387
|
+
[ [:sequence, {:a => :b, :b => :c}], {:a=>:b, :b=>:c} ],
|
388
|
+
[ [:sequence, {:a => :b}, 'a', {:c=>:d}], {:a => :b, :c=>:d} ],
|
389
|
+
[ [:repetition, {:a => :b}, 'a', {:c=>:d}], [{:a => :b}, {:c=>:d}] ],
|
390
|
+
[ [:sequence, {:a => :b}, {:a=>:d}], {:a => :d} ],
|
391
|
+
[ [:sequence, {:a=>:b}, [:sequence, [:sequence, "\n", nil]]], {:a=>:b} ],
|
392
|
+
[ [:sequence, nil, " "], ' ' ],
|
393
|
+
].each do |input, output|
|
394
|
+
it "should transform #{input.inspect} to #{output.inspect}" do
|
395
|
+
call(input).should == output
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
describe "combinations thereof (regression)" do
|
401
|
+
success=[
|
402
|
+
[(str('a').repeat >> str('b').repeat), 'aaabbb']
|
403
|
+
].each do |(parslet, input)|
|
404
|
+
describe "#{parslet.inspect} applied to #{input.inspect}" do
|
405
|
+
it "should parse successfully" do
|
406
|
+
parslet.parse(input)
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
inspection=[
|
412
|
+
[str('a'), "'a'" ],
|
413
|
+
[(str('a') | str('b')).maybe, "('a' / 'b')?" ],
|
414
|
+
[(str('a') >> str('b')).maybe, "('a' 'b')?" ],
|
415
|
+
[str('a').maybe.maybe, "'a'??" ],
|
416
|
+
[(str('a')>>str('b')).maybe.maybe, "('a' 'b')??" ],
|
417
|
+
[(str('a') >> (str('b') | str('c'))), "'a' ('b' / 'c')"],
|
418
|
+
|
419
|
+
[str('a') >> str('b').repeat, "'a' 'b'{0, }" ],
|
420
|
+
[(str('a')>>str('b')).repeat, "('a' 'b'){0, }" ]
|
421
|
+
].each do |(parslet, inspect_output)|
|
422
|
+
context "regression for #{parslet.inspect}" do
|
423
|
+
it "should inspect correctly as #{inspect_output}" do
|
424
|
+
parslet.inspect.should == inspect_output
|
425
|
+
end
|
426
|
+
end
|
427
|
+
end
|
428
|
+
end
|
429
|
+
end
|