plurimath-parslet 3.0.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.
- checksums.yaml +7 -0
- data/HISTORY.txt +284 -0
- data/LICENSE +23 -0
- data/README.adoc +454 -0
- data/Rakefile +71 -0
- data/lib/parslet/accelerator/application.rb +62 -0
- data/lib/parslet/accelerator/engine.rb +112 -0
- data/lib/parslet/accelerator.rb +162 -0
- data/lib/parslet/atoms/alternative.rb +53 -0
- data/lib/parslet/atoms/base.rb +157 -0
- data/lib/parslet/atoms/can_flatten.rb +137 -0
- data/lib/parslet/atoms/capture.rb +38 -0
- data/lib/parslet/atoms/context.rb +103 -0
- data/lib/parslet/atoms/dsl.rb +112 -0
- data/lib/parslet/atoms/dynamic.rb +32 -0
- data/lib/parslet/atoms/entity.rb +45 -0
- data/lib/parslet/atoms/ignored.rb +26 -0
- data/lib/parslet/atoms/infix.rb +115 -0
- data/lib/parslet/atoms/lookahead.rb +52 -0
- data/lib/parslet/atoms/named.rb +32 -0
- data/lib/parslet/atoms/re.rb +41 -0
- data/lib/parslet/atoms/repetition.rb +87 -0
- data/lib/parslet/atoms/scope.rb +26 -0
- data/lib/parslet/atoms/sequence.rb +48 -0
- data/lib/parslet/atoms/str.rb +42 -0
- data/lib/parslet/atoms/visitor.rb +89 -0
- data/lib/parslet/atoms.rb +34 -0
- data/lib/parslet/cause.rb +101 -0
- data/lib/parslet/context.rb +21 -0
- data/lib/parslet/convenience.rb +33 -0
- data/lib/parslet/error_reporter/contextual.rb +120 -0
- data/lib/parslet/error_reporter/deepest.rb +100 -0
- data/lib/parslet/error_reporter/tree.rb +63 -0
- data/lib/parslet/error_reporter.rb +8 -0
- data/lib/parslet/export.rb +163 -0
- data/lib/parslet/expression/treetop.rb +92 -0
- data/lib/parslet/expression.rb +51 -0
- data/lib/parslet/graphviz.rb +97 -0
- data/lib/parslet/parser.rb +68 -0
- data/lib/parslet/pattern/binding.rb +49 -0
- data/lib/parslet/pattern.rb +113 -0
- data/lib/parslet/position.rb +21 -0
- data/lib/parslet/rig/rspec.rb +52 -0
- data/lib/parslet/scope.rb +42 -0
- data/lib/parslet/slice.rb +105 -0
- data/lib/parslet/source/line_cache.rb +99 -0
- data/lib/parslet/source.rb +96 -0
- data/lib/parslet/transform.rb +265 -0
- data/lib/parslet/version.rb +5 -0
- data/lib/parslet.rb +314 -0
- data/plurimath-parslet.gemspec +42 -0
- data/spec/acceptance/infix_parser_spec.rb +145 -0
- data/spec/acceptance/mixing_parsers_spec.rb +74 -0
- data/spec/acceptance/regression_spec.rb +329 -0
- data/spec/acceptance/repetition_and_maybe_spec.rb +44 -0
- data/spec/acceptance/unconsumed_input_spec.rb +21 -0
- data/spec/examples/boolean_algebra_spec.rb +257 -0
- data/spec/examples/calc_spec.rb +278 -0
- data/spec/examples/capture_spec.rb +137 -0
- data/spec/examples/comments_spec.rb +186 -0
- data/spec/examples/deepest_errors_spec.rb +420 -0
- data/spec/examples/documentation_spec.rb +205 -0
- data/spec/examples/email_parser_spec.rb +275 -0
- data/spec/examples/empty_spec.rb +37 -0
- data/spec/examples/erb_spec.rb +482 -0
- data/spec/examples/ip_address_spec.rb +153 -0
- data/spec/examples/json_spec.rb +413 -0
- data/spec/examples/local_spec.rb +302 -0
- data/spec/examples/mathn_spec.rb +151 -0
- data/spec/examples/minilisp_spec.rb +492 -0
- data/spec/examples/modularity_spec.rb +340 -0
- data/spec/examples/nested_errors_spec.rb +322 -0
- data/spec/examples/optimized_erb_spec.rb +299 -0
- data/spec/examples/parens_spec.rb +239 -0
- data/spec/examples/prec_calc_spec.rb +525 -0
- data/spec/examples/readme_spec.rb +228 -0
- data/spec/examples/scopes_spec.rb +187 -0
- data/spec/examples/seasons_spec.rb +196 -0
- data/spec/examples/sentence_spec.rb +119 -0
- data/spec/examples/simple_xml_spec.rb +250 -0
- data/spec/examples/string_parser_spec.rb +407 -0
- data/spec/fixtures/examples/boolean_algebra.rb +62 -0
- data/spec/fixtures/examples/calc.rb +86 -0
- data/spec/fixtures/examples/capture.rb +36 -0
- data/spec/fixtures/examples/comments.rb +22 -0
- data/spec/fixtures/examples/deepest_errors.rb +99 -0
- data/spec/fixtures/examples/documentation.rb +32 -0
- data/spec/fixtures/examples/email_parser.rb +42 -0
- data/spec/fixtures/examples/empty.rb +10 -0
- data/spec/fixtures/examples/erb.rb +39 -0
- data/spec/fixtures/examples/ip_address.rb +103 -0
- data/spec/fixtures/examples/json.rb +107 -0
- data/spec/fixtures/examples/local.rb +60 -0
- data/spec/fixtures/examples/mathn.rb +47 -0
- data/spec/fixtures/examples/minilisp.rb +75 -0
- data/spec/fixtures/examples/modularity.rb +60 -0
- data/spec/fixtures/examples/nested_errors.rb +95 -0
- data/spec/fixtures/examples/optimized_erb.rb +105 -0
- data/spec/fixtures/examples/parens.rb +25 -0
- data/spec/fixtures/examples/prec_calc.rb +71 -0
- data/spec/fixtures/examples/readme.rb +59 -0
- data/spec/fixtures/examples/scopes.rb +43 -0
- data/spec/fixtures/examples/seasons.rb +40 -0
- data/spec/fixtures/examples/sentence.rb +18 -0
- data/spec/fixtures/examples/simple_xml.rb +51 -0
- data/spec/fixtures/examples/string_parser.rb +77 -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 +127 -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 +7 -0
- data/spec/parslet/atoms/entity_spec.rb +77 -0
- data/spec/parslet/atoms/ignored_spec.rb +15 -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 +101 -0
- data/spec/parslet/atoms_spec.rb +488 -0
- data/spec/parslet/convenience_spec.rb +54 -0
- data/spec/parslet/error_reporter/contextual_spec.rb +118 -0
- data/spec/parslet/error_reporter/deepest_spec.rb +82 -0
- data/spec/parslet/error_reporter/tree_spec.rb +7 -0
- data/spec/parslet/export_spec.rb +40 -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 +36 -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 +186 -0
- data/spec/parslet/source/line_cache_spec.rb +74 -0
- data/spec/parslet/source_spec.rb +210 -0
- data/spec/parslet/transform/context_spec.rb +56 -0
- data/spec/parslet/transform_spec.rb +183 -0
- data/spec/spec_helper.rb +74 -0
- data/spec/support/opal.rb +8 -0
- data/spec/support/opal.rb.erb +14 -0
- data/spec/support/parslet_matchers.rb +96 -0
- metadata +240 -0
@@ -0,0 +1,488 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'timeout' unless RUBY_ENGINE == 'opal'
|
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)
|
15
|
+
Parslet::Source.new str
|
16
|
+
end
|
17
|
+
let(:context) { Parslet::Atoms::Context.new }
|
18
|
+
|
19
|
+
describe "match('[abc]')" do
|
20
|
+
attr_reader :parslet
|
21
|
+
|
22
|
+
before do
|
23
|
+
@parslet = Parslet.match('[abc]')
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'parses {a,b,c}' do
|
27
|
+
parslet.parse('a')
|
28
|
+
parslet.parse('b')
|
29
|
+
parslet.parse('c')
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'does not parse d' do
|
33
|
+
cause = catch_failed_parse do
|
34
|
+
parslet.parse('d')
|
35
|
+
end
|
36
|
+
expect(cause.to_s).to eq('Failed to match [abc] at line 1 char 1.')
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'prints as [abc]' do
|
40
|
+
parslet.inspect.should == '[abc]'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "match(['[a]').repeat(3)" do
|
45
|
+
attr_reader :parslet
|
46
|
+
|
47
|
+
before do
|
48
|
+
@parslet = Parslet.match('[a]').repeat(3)
|
49
|
+
end
|
50
|
+
|
51
|
+
context "when failing on input 'aa'" do
|
52
|
+
let!(:cause) do
|
53
|
+
catch_failed_parse { parslet.parse('aa') }
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'has a relevant cause' do
|
57
|
+
cause.to_s.should == 'Expected at least 3 of [a] at line 1 char 1.'
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'has a tree with 2 nodes' do
|
61
|
+
cause.children.size.should == 1
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
it "succeeds on 'aaa'" do
|
66
|
+
parslet.parse('aaa')
|
67
|
+
end
|
68
|
+
|
69
|
+
it "succeeds on many 'a'" do
|
70
|
+
parslet.parse('a' * 100)
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'inspects as [a]{3, }' do
|
74
|
+
parslet.inspect.should == '[a]{3, }'
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "str('foo')" do
|
79
|
+
attr_reader :parslet
|
80
|
+
|
81
|
+
before do
|
82
|
+
@parslet = str('foo')
|
83
|
+
end
|
84
|
+
|
85
|
+
it "parses 'foo'" do
|
86
|
+
parslet.parse('foo')
|
87
|
+
end
|
88
|
+
|
89
|
+
it "does not parse 'bar'" do
|
90
|
+
cause = catch_failed_parse { parslet.parse('bar') }
|
91
|
+
cause.to_s.should ==
|
92
|
+
'Expected "foo", but got "bar" at line 1 char 1.'
|
93
|
+
end
|
94
|
+
|
95
|
+
it "inspects as 'foo'" do
|
96
|
+
parslet.inspect.should == "'foo'"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe "str('foo').maybe" do
|
101
|
+
let(:parslet) { str('foo').maybe }
|
102
|
+
|
103
|
+
it 'parses a foo' do
|
104
|
+
parslet.parse('foo')
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'leaves pos untouched if there is no foo' do
|
108
|
+
source = src('bar')
|
109
|
+
parslet.apply(source, context)
|
110
|
+
source.pos.charpos.should == 0
|
111
|
+
end
|
112
|
+
|
113
|
+
it "inspects as 'foo'?" do
|
114
|
+
parslet.inspect.should == "'foo'?"
|
115
|
+
end
|
116
|
+
|
117
|
+
context "when parsing 'foo'" do
|
118
|
+
subject { parslet.parse('foo') }
|
119
|
+
|
120
|
+
it { is_expected.to eq('foo') }
|
121
|
+
end
|
122
|
+
|
123
|
+
context "when parsing ''" do
|
124
|
+
subject { parslet.parse('') }
|
125
|
+
|
126
|
+
it { is_expected.to eq('') }
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe "str('foo') >> str('bar')" do
|
131
|
+
let(:parslet) { str('foo') >> str('bar') }
|
132
|
+
|
133
|
+
context "when it fails on input 'foobaz'" do
|
134
|
+
let!(:cause) do
|
135
|
+
catch_failed_parse { parslet.parse('foobaz') }
|
136
|
+
end
|
137
|
+
|
138
|
+
it "does not parse 'foobaz'" do
|
139
|
+
cause.to_s.should == "Failed to match sequence ('foo' 'bar') at line 1 char 4."
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'has 2 nodes in error tree' do
|
143
|
+
cause.children.size.should == 1
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
it "parses 'foobar'" do
|
148
|
+
parslet.parse('foobar')
|
149
|
+
end
|
150
|
+
|
151
|
+
it "inspects as ('foo' 'bar')" do
|
152
|
+
parslet.inspect.should == "'foo' 'bar'"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
describe "str('foo') | str('bar')" do
|
157
|
+
attr_reader :parslet
|
158
|
+
|
159
|
+
before do
|
160
|
+
@parslet = str('foo') | str('bar')
|
161
|
+
end
|
162
|
+
|
163
|
+
context "when failing on input 'baz'" do
|
164
|
+
let!(:cause) do
|
165
|
+
catch_failed_parse { parslet.parse('baz') }
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'has a sensible cause' do
|
169
|
+
cause.to_s.should == "Expected one of ['foo', 'bar'] at line 1 char 1."
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'has an error tree with 3 nodes' do
|
173
|
+
cause.children.size.should == 2
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
it "accepts 'foo'" do
|
178
|
+
parslet.parse('foo')
|
179
|
+
end
|
180
|
+
|
181
|
+
it "accepts 'bar'" do
|
182
|
+
parslet.parse('bar')
|
183
|
+
end
|
184
|
+
|
185
|
+
it "inspects as ('foo' / 'bar')" do
|
186
|
+
parslet.inspect.should == "'foo' / 'bar'"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
describe "str('foo').present? (positive lookahead)" do
|
191
|
+
attr_reader :parslet
|
192
|
+
|
193
|
+
before do
|
194
|
+
@parslet = str('foo').present?
|
195
|
+
end
|
196
|
+
|
197
|
+
it "inspects as &'foo'" do
|
198
|
+
parslet.inspect.should == "&'foo'"
|
199
|
+
end
|
200
|
+
|
201
|
+
context "when fed 'foo'" do
|
202
|
+
it 'parses' do
|
203
|
+
success, = parslet.apply(src('foo'), context)
|
204
|
+
success.should == true
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'does not change input position' do
|
208
|
+
source = src('foo')
|
209
|
+
parslet.apply(source, context)
|
210
|
+
source.pos.charpos.should == 0
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
context "when fed 'bar'" do
|
215
|
+
it 'does not parse' do
|
216
|
+
-> { parslet.parse('bar') }.should not_parse
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
describe '<- #parse' do
|
221
|
+
it 'returns nil' do
|
222
|
+
parslet.apply(src('foo'), context).should == [true, nil]
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
describe "str('foo').absent? (negative lookahead)" do
|
228
|
+
attr_reader :parslet
|
229
|
+
|
230
|
+
before do
|
231
|
+
@parslet = str('foo').absent?
|
232
|
+
end
|
233
|
+
|
234
|
+
it "inspects as !'foo'" do
|
235
|
+
parslet.inspect.should == "!'foo'"
|
236
|
+
end
|
237
|
+
|
238
|
+
context "when fed 'bar'" do
|
239
|
+
it 'parses' do
|
240
|
+
parslet.apply(src('bar'), context).should == [true, nil]
|
241
|
+
end
|
242
|
+
|
243
|
+
it 'does not change input position' do
|
244
|
+
source = src('bar')
|
245
|
+
parslet.apply(source, context)
|
246
|
+
source.pos.charpos.should == 0
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
context "when fed 'foo'" do
|
251
|
+
it 'does not parse' do
|
252
|
+
-> { parslet.parse('foo') }.should not_parse
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
describe 'non greedy matcher combined with greedy matcher (possible loop)' do
|
258
|
+
attr_reader :parslet
|
259
|
+
|
260
|
+
before do
|
261
|
+
# repeat will always succeed, since it has a minimum of 0. It will not
|
262
|
+
# modify input position in that case. absent? will, depending on
|
263
|
+
# implementation, match as much as possible and call its inner element
|
264
|
+
# again. This leads to an infinite loop. This example tests for the
|
265
|
+
# absence of that loop.
|
266
|
+
@parslet = str('foo').repeat.maybe
|
267
|
+
end
|
268
|
+
|
269
|
+
unless RUBY_ENGINE == 'opal'
|
270
|
+
it 'does not loop infinitely' do
|
271
|
+
lambda {
|
272
|
+
Timeout.timeout(1) { parslet.parse('bar') }
|
273
|
+
}.should raise_error(Parslet::ParseFailed)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
describe 'any' do
|
279
|
+
attr_reader :parslet
|
280
|
+
|
281
|
+
before do
|
282
|
+
@parslet = any
|
283
|
+
end
|
284
|
+
|
285
|
+
it 'matches' do
|
286
|
+
parslet.parse('.')
|
287
|
+
end
|
288
|
+
|
289
|
+
it 'consumes one char' do
|
290
|
+
source = src('foo')
|
291
|
+
parslet.apply(source, context)
|
292
|
+
source.pos.charpos.should == 1
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
describe 'eof behaviour' do
|
297
|
+
context "when the pattern just doesn't consume the input" do
|
298
|
+
let(:parslet) { any }
|
299
|
+
|
300
|
+
it 'fails the parse' do
|
301
|
+
cause = catch_failed_parse { parslet.parse('..') }
|
302
|
+
cause.to_s.should == "Don't know what to do with \".\" at line 1 char 2."
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
context "when the pattern doesn't match the input" do
|
307
|
+
let(:parslet) { (str('a')).repeat(1) }
|
308
|
+
|
309
|
+
attr_reader :exception
|
310
|
+
|
311
|
+
before do
|
312
|
+
parslet.parse('a.')
|
313
|
+
rescue StandardError => e
|
314
|
+
@exception = e
|
315
|
+
end
|
316
|
+
|
317
|
+
it 'raises Parslet::ParseFailed' do
|
318
|
+
# ParseFailed here, because the input doesn't match the parser grammar.
|
319
|
+
exception.should be_kind_of(Parslet::ParseFailed)
|
320
|
+
end
|
321
|
+
|
322
|
+
it 'has the correct error message' do
|
323
|
+
exception.message.should == \
|
324
|
+
'Extra input after last repetition at line 1 char 2.'
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
describe '<- #as(name)' do
|
330
|
+
context "str('foo').as(:bar)" do
|
331
|
+
it "returns :bar => 'foo'" do
|
332
|
+
str('foo').as(:bar).parse('foo').should == { bar: 'foo' }
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
context "match('[abc]').as(:name)" do
|
337
|
+
it "returns :name => 'b'" do
|
338
|
+
Parslet.match('[abc]').as(:name).parse('b').should == { name: 'b' }
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
context "match('[abc]').repeat.as(:name)" do
|
343
|
+
it "returns collated result ('abc')" do
|
344
|
+
Parslet.match('[abc]').repeat.as(:name)
|
345
|
+
.parse('abc').should == { name: 'abc' }
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
context "(str('a').as(:a) >> str('b').as(:b)).as(:c)" do
|
350
|
+
it 'returns a hash of hashes' do
|
351
|
+
(str('a').as(:a) >> str('b').as(:b)).as(:c)
|
352
|
+
.parse('ab').should == {
|
353
|
+
c: {
|
354
|
+
a: 'a',
|
355
|
+
b: 'b',
|
356
|
+
},
|
357
|
+
}
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
context "(str('a').as(:a) >> str('ignore') >> str('b').as(:b))" do
|
362
|
+
it "correctlies flatten (leaving out 'ignore')" do
|
363
|
+
(str('a').as(:a) >> str('ignore') >> str('b').as(:b))
|
364
|
+
.parse('aignoreb').should ==
|
365
|
+
{
|
366
|
+
a: 'a',
|
367
|
+
b: 'b',
|
368
|
+
}
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
context "(str('a') >> str('ignore') >> str('b')) (no .as(...))" do
|
373
|
+
it 'returns simply the original string' do
|
374
|
+
(str('a') >> str('ignore') >> str('b'))
|
375
|
+
.parse('aignoreb').should == 'aignoreb'
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
context "str('a').as(:a) >> str('b').as(:a)" do
|
380
|
+
attr_reader :parslet
|
381
|
+
|
382
|
+
before do
|
383
|
+
@parslet = str('a').as(:a) >> str('b').as(:a)
|
384
|
+
end
|
385
|
+
|
386
|
+
it 'issues a warning that a key is being overwritten in merge' do
|
387
|
+
expect(parslet).to receive(:warn).once
|
388
|
+
parslet.parse('ab').should == { a: 'b' }
|
389
|
+
end
|
390
|
+
|
391
|
+
it "returns :a => 'b'" do
|
392
|
+
expect(parslet).to receive(:warn)
|
393
|
+
|
394
|
+
parslet.parse('ab').should == { a: 'b' }
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
context "str('a').absent?" do
|
399
|
+
it 'returns something in merge, even though it is nil' do
|
400
|
+
(str('a').absent? >> str('b').as(:b))
|
401
|
+
.parse('b').should == { b: 'b' }
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
context "str('a').as(:a).repeat" do
|
406
|
+
it 'returns an array of subtrees' do
|
407
|
+
expect(str('a').as(:a).repeat.parse('aa')).to eq([{ a: 'a' }, { a: 'a' }])
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
describe '<- #flatten(val)' do
|
413
|
+
def call(val)
|
414
|
+
dummy = str('a')
|
415
|
+
allow(dummy).to receive(:warn)
|
416
|
+
dummy.flatten(val)
|
417
|
+
end
|
418
|
+
|
419
|
+
[
|
420
|
+
# In absence of named subtrees: ----------------------------------------
|
421
|
+
# Sequence or Repetition
|
422
|
+
[[:sequence, 'a', 'b'], 'ab'],
|
423
|
+
[[:repetition, 'a', 'a'], 'aa'],
|
424
|
+
|
425
|
+
# Nested inside another node
|
426
|
+
[[:sequence, [:sequence, 'a', 'b']], 'ab'],
|
427
|
+
# Combined with lookahead (nil)
|
428
|
+
[[:sequence, nil, 'a'], 'a'],
|
429
|
+
|
430
|
+
# Including named subtrees ---------------------------------------------
|
431
|
+
# Atom: A named subtree
|
432
|
+
[{ a: 'a' }, { a: 'a' }],
|
433
|
+
# Composition of subtrees
|
434
|
+
[[:sequence, { a: 'a' }, { b: 'b' }], { a: 'a', b: 'b' }],
|
435
|
+
# Mixed subtrees :sequence of :repetition yields []
|
436
|
+
[[:sequence, [:repetition, { a: 'a' }], { a: 'a' }], [{ a: 'a' }, { a: 'a' }]],
|
437
|
+
[[:sequence, { a: 'a' }, [:repetition, { a: 'a' }]], [{ a: 'a' }, { a: 'a' }]],
|
438
|
+
[[:sequence, [:repetition, { a: 'a' }], [:repetition, { a: 'a' }]], [{ a: 'a' }, { a: 'a' }]],
|
439
|
+
# Repetition
|
440
|
+
[[:repetition, [:repetition, { a: 'a' }], [:repetition, { a: 'a' }]],
|
441
|
+
[{ a: 'a' }, { a: 'a' }]],
|
442
|
+
[[:repetition, { a: 'a' }, 'a', { a: 'a' }], [{ a: 'a' }, { a: 'a' }]],
|
443
|
+
[[:repetition, { a: 'a' }, [:repetition, { b: 'b' }]], [{ a: 'a' }]],
|
444
|
+
|
445
|
+
# Some random samples --------------------------------------------------
|
446
|
+
[[:sequence, { a: :b, b: :c }], { a: :b, b: :c }],
|
447
|
+
[[:sequence, { a: :b }, 'a', { c: :d }], { a: :b, c: :d }],
|
448
|
+
[[:repetition, { a: :b }, 'a', { c: :d }], [{ a: :b }, { c: :d }]],
|
449
|
+
[[:sequence, { a: :b }, { a: :d }], { a: :d }],
|
450
|
+
[[:sequence, { a: :b }, [:sequence, [:sequence, "\n", nil]]], { a: :b }],
|
451
|
+
[[:sequence, nil, ' '], ' '],
|
452
|
+
].each do |input, output|
|
453
|
+
it "transforms #{input.inspect} to #{output.inspect}" do
|
454
|
+
call(input).should == output
|
455
|
+
end
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
describe 'combinations thereof (regression)' do
|
460
|
+
[
|
461
|
+
[(str('a').repeat >> str('b').repeat), 'aaabbb'],
|
462
|
+
].each do |(parslet, input)|
|
463
|
+
describe "#{parslet.inspect} applied to #{input.inspect}" do
|
464
|
+
it 'parses successfully' do
|
465
|
+
parslet.parse(input)
|
466
|
+
end
|
467
|
+
end
|
468
|
+
end
|
469
|
+
|
470
|
+
[
|
471
|
+
[str('a'), "'a'"],
|
472
|
+
[(str('a') | str('b')).maybe, "('a' / 'b')?"],
|
473
|
+
[(str('a') >> str('b')).maybe, "('a' 'b')?"],
|
474
|
+
[str('a').maybe.maybe, "'a'??"],
|
475
|
+
[(str('a') >> str('b')).maybe.maybe, "('a' 'b')??"],
|
476
|
+
[(str('a') >> (str('b') | str('c'))), "'a' ('b' / 'c')"],
|
477
|
+
|
478
|
+
[str('a') >> str('b').repeat, "'a' 'b'{0, }"],
|
479
|
+
[(str('a') >> str('b')).repeat, "('a' 'b'){0, }"],
|
480
|
+
].each do |(parslet, inspect_output)|
|
481
|
+
context "regression for #{parslet.inspect}" do
|
482
|
+
it "inspects correctly as #{inspect_output}" do
|
483
|
+
parslet.inspect.should == inspect_output
|
484
|
+
end
|
485
|
+
end
|
486
|
+
end
|
487
|
+
end
|
488
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'parslet/convenience' do
|
4
|
+
require 'parslet/convenience'
|
5
|
+
include Parslet
|
6
|
+
|
7
|
+
class FooParser < Parslet::Parser
|
8
|
+
rule(:foo) { str('foo') }
|
9
|
+
root(:foo)
|
10
|
+
end
|
11
|
+
|
12
|
+
describe 'parse_with_debug' do
|
13
|
+
let(:parser) { FooParser.new }
|
14
|
+
|
15
|
+
context 'internal' do
|
16
|
+
before do
|
17
|
+
# Suppress output.
|
18
|
+
#
|
19
|
+
allow(parser).to receive(:puts)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'exists' do
|
23
|
+
-> { parser.parse_with_debug('anything') }.should_not raise_error
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'catches ParseFailed exceptions' do
|
27
|
+
-> { parser.parse_with_debug('bar') }.should_not raise_error
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'parses correct input like #parse' do
|
31
|
+
-> { parser.parse_with_debug('foo') }.should_not raise_error
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'output' do
|
36
|
+
it 'putses once for tree output' do
|
37
|
+
expect(parser).to receive(:puts).once
|
38
|
+
|
39
|
+
parser.parse_with_debug('incorrect')
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'putses once for the error on unconsumed input' do
|
43
|
+
expect(parser).to receive(:puts).once
|
44
|
+
|
45
|
+
parser.parse_with_debug('foobar')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'works for all parslets' do
|
50
|
+
str('foo').parse_with_debug('foo')
|
51
|
+
Parslet.match['bar'].parse_with_debug('a')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Parslet::ErrorReporter::Contextual do
|
4
|
+
let(:reporter) { described_class.new }
|
5
|
+
let(:fake_source) { double('source') }
|
6
|
+
let(:fake_atom) { double('atom') }
|
7
|
+
let(:fake_cause) { double('cause') }
|
8
|
+
|
9
|
+
describe '#err' do
|
10
|
+
before do
|
11
|
+
allow(fake_source).to receive(:pos).and_return(13)
|
12
|
+
allow(fake_source).to receive(:line_and_column).and_return([1, 1])
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'returns the deepest cause' do
|
16
|
+
expect(reporter).to receive(:deepest).and_return(:deepest)
|
17
|
+
expect(reporter.err('parslet', fake_source, 'message')).to eq(:deepest)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#err_at' do
|
22
|
+
before do
|
23
|
+
allow(fake_source).to receive(:pos).and_return(13)
|
24
|
+
allow(fake_source).to receive(:line_and_column).and_return([1, 1])
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'returns the deepest cause' do
|
28
|
+
expect(reporter).to receive(:deepest).and_return(:deepest)
|
29
|
+
expect(reporter.err('parslet', fake_source, 'message', 13)).to eq(:deepest)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#deepest(cause)' do
|
34
|
+
def fake_cause(pos = 13, children = nil)
|
35
|
+
double('cause' + pos.to_s, pos: pos, children: children)
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'when there is no deepest cause yet' do
|
39
|
+
let(:cause) { fake_cause }
|
40
|
+
|
41
|
+
it 'returns the given cause' do
|
42
|
+
reporter.deepest(cause).should == cause
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'when the previous cause is deeper (no relationship)' do
|
47
|
+
let(:previous) { fake_cause }
|
48
|
+
|
49
|
+
before do
|
50
|
+
reporter.deepest(previous)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'returns the previous cause' do
|
54
|
+
reporter.deepest(fake_cause(12))
|
55
|
+
.should == previous
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'when the previous cause is deeper (child)' do
|
60
|
+
let(:previous) { fake_cause }
|
61
|
+
|
62
|
+
before do
|
63
|
+
reporter.deepest(previous)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'returns the given cause' do
|
67
|
+
given = fake_cause(12, [previous])
|
68
|
+
reporter.deepest(given).should == given
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'when the previous cause is shallower' do
|
73
|
+
before do
|
74
|
+
reporter.deepest(fake_cause)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'stores the cause as deepest' do
|
78
|
+
deeper = fake_cause(14)
|
79
|
+
reporter.deepest(deeper)
|
80
|
+
reporter.deepest_cause.should == deeper
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe '#reset' do
|
86
|
+
before do
|
87
|
+
allow(fake_source).to receive(:pos).and_return(Parslet::Position.new('source', 13))
|
88
|
+
allow(fake_source).to receive(:line_and_column).and_return([1, 1])
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'resets deepest cause on success of sibling expression' do
|
92
|
+
expect(reporter).to receive(:deepest).and_return(:deepest)
|
93
|
+
expect(reporter.err('parslet', fake_source, 'message')).to eq(:deepest)
|
94
|
+
expect(reporter).to receive(:reset).once
|
95
|
+
reporter.succ(fake_source)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe 'label' do
|
100
|
+
before do
|
101
|
+
allow(fake_source).to receive(:pos).and_return(Parslet::Position.new('source', 13))
|
102
|
+
allow(fake_source).to receive(:line_and_column).and_return([1, 1])
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'sets label if atom has one' do
|
106
|
+
expect(fake_atom).to receive(:label).once.and_return('label')
|
107
|
+
expect(fake_cause).to receive(:set_label).once
|
108
|
+
expect(reporter).to receive(:deepest).and_return(fake_cause)
|
109
|
+
expect(reporter.err(fake_atom, fake_source, 'message')).to eq(fake_cause)
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'does not set label if atom does not have one' do
|
113
|
+
expect(reporter).to receive(:deepest).and_return(:deepest)
|
114
|
+
expect(fake_atom).not_to receive(:update_label)
|
115
|
+
expect(reporter.err(fake_atom, fake_source, 'message')).to eq(:deepest)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|