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.
Files changed (148) hide show
  1. checksums.yaml +7 -0
  2. data/HISTORY.txt +284 -0
  3. data/LICENSE +23 -0
  4. data/README.adoc +454 -0
  5. data/Rakefile +71 -0
  6. data/lib/parslet/accelerator/application.rb +62 -0
  7. data/lib/parslet/accelerator/engine.rb +112 -0
  8. data/lib/parslet/accelerator.rb +162 -0
  9. data/lib/parslet/atoms/alternative.rb +53 -0
  10. data/lib/parslet/atoms/base.rb +157 -0
  11. data/lib/parslet/atoms/can_flatten.rb +137 -0
  12. data/lib/parslet/atoms/capture.rb +38 -0
  13. data/lib/parslet/atoms/context.rb +103 -0
  14. data/lib/parslet/atoms/dsl.rb +112 -0
  15. data/lib/parslet/atoms/dynamic.rb +32 -0
  16. data/lib/parslet/atoms/entity.rb +45 -0
  17. data/lib/parslet/atoms/ignored.rb +26 -0
  18. data/lib/parslet/atoms/infix.rb +115 -0
  19. data/lib/parslet/atoms/lookahead.rb +52 -0
  20. data/lib/parslet/atoms/named.rb +32 -0
  21. data/lib/parslet/atoms/re.rb +41 -0
  22. data/lib/parslet/atoms/repetition.rb +87 -0
  23. data/lib/parslet/atoms/scope.rb +26 -0
  24. data/lib/parslet/atoms/sequence.rb +48 -0
  25. data/lib/parslet/atoms/str.rb +42 -0
  26. data/lib/parslet/atoms/visitor.rb +89 -0
  27. data/lib/parslet/atoms.rb +34 -0
  28. data/lib/parslet/cause.rb +101 -0
  29. data/lib/parslet/context.rb +21 -0
  30. data/lib/parslet/convenience.rb +33 -0
  31. data/lib/parslet/error_reporter/contextual.rb +120 -0
  32. data/lib/parslet/error_reporter/deepest.rb +100 -0
  33. data/lib/parslet/error_reporter/tree.rb +63 -0
  34. data/lib/parslet/error_reporter.rb +8 -0
  35. data/lib/parslet/export.rb +163 -0
  36. data/lib/parslet/expression/treetop.rb +92 -0
  37. data/lib/parslet/expression.rb +51 -0
  38. data/lib/parslet/graphviz.rb +97 -0
  39. data/lib/parslet/parser.rb +68 -0
  40. data/lib/parslet/pattern/binding.rb +49 -0
  41. data/lib/parslet/pattern.rb +113 -0
  42. data/lib/parslet/position.rb +21 -0
  43. data/lib/parslet/rig/rspec.rb +52 -0
  44. data/lib/parslet/scope.rb +42 -0
  45. data/lib/parslet/slice.rb +105 -0
  46. data/lib/parslet/source/line_cache.rb +99 -0
  47. data/lib/parslet/source.rb +96 -0
  48. data/lib/parslet/transform.rb +265 -0
  49. data/lib/parslet/version.rb +5 -0
  50. data/lib/parslet.rb +314 -0
  51. data/plurimath-parslet.gemspec +42 -0
  52. data/spec/acceptance/infix_parser_spec.rb +145 -0
  53. data/spec/acceptance/mixing_parsers_spec.rb +74 -0
  54. data/spec/acceptance/regression_spec.rb +329 -0
  55. data/spec/acceptance/repetition_and_maybe_spec.rb +44 -0
  56. data/spec/acceptance/unconsumed_input_spec.rb +21 -0
  57. data/spec/examples/boolean_algebra_spec.rb +257 -0
  58. data/spec/examples/calc_spec.rb +278 -0
  59. data/spec/examples/capture_spec.rb +137 -0
  60. data/spec/examples/comments_spec.rb +186 -0
  61. data/spec/examples/deepest_errors_spec.rb +420 -0
  62. data/spec/examples/documentation_spec.rb +205 -0
  63. data/spec/examples/email_parser_spec.rb +275 -0
  64. data/spec/examples/empty_spec.rb +37 -0
  65. data/spec/examples/erb_spec.rb +482 -0
  66. data/spec/examples/ip_address_spec.rb +153 -0
  67. data/spec/examples/json_spec.rb +413 -0
  68. data/spec/examples/local_spec.rb +302 -0
  69. data/spec/examples/mathn_spec.rb +151 -0
  70. data/spec/examples/minilisp_spec.rb +492 -0
  71. data/spec/examples/modularity_spec.rb +340 -0
  72. data/spec/examples/nested_errors_spec.rb +322 -0
  73. data/spec/examples/optimized_erb_spec.rb +299 -0
  74. data/spec/examples/parens_spec.rb +239 -0
  75. data/spec/examples/prec_calc_spec.rb +525 -0
  76. data/spec/examples/readme_spec.rb +228 -0
  77. data/spec/examples/scopes_spec.rb +187 -0
  78. data/spec/examples/seasons_spec.rb +196 -0
  79. data/spec/examples/sentence_spec.rb +119 -0
  80. data/spec/examples/simple_xml_spec.rb +250 -0
  81. data/spec/examples/string_parser_spec.rb +407 -0
  82. data/spec/fixtures/examples/boolean_algebra.rb +62 -0
  83. data/spec/fixtures/examples/calc.rb +86 -0
  84. data/spec/fixtures/examples/capture.rb +36 -0
  85. data/spec/fixtures/examples/comments.rb +22 -0
  86. data/spec/fixtures/examples/deepest_errors.rb +99 -0
  87. data/spec/fixtures/examples/documentation.rb +32 -0
  88. data/spec/fixtures/examples/email_parser.rb +42 -0
  89. data/spec/fixtures/examples/empty.rb +10 -0
  90. data/spec/fixtures/examples/erb.rb +39 -0
  91. data/spec/fixtures/examples/ip_address.rb +103 -0
  92. data/spec/fixtures/examples/json.rb +107 -0
  93. data/spec/fixtures/examples/local.rb +60 -0
  94. data/spec/fixtures/examples/mathn.rb +47 -0
  95. data/spec/fixtures/examples/minilisp.rb +75 -0
  96. data/spec/fixtures/examples/modularity.rb +60 -0
  97. data/spec/fixtures/examples/nested_errors.rb +95 -0
  98. data/spec/fixtures/examples/optimized_erb.rb +105 -0
  99. data/spec/fixtures/examples/parens.rb +25 -0
  100. data/spec/fixtures/examples/prec_calc.rb +71 -0
  101. data/spec/fixtures/examples/readme.rb +59 -0
  102. data/spec/fixtures/examples/scopes.rb +43 -0
  103. data/spec/fixtures/examples/seasons.rb +40 -0
  104. data/spec/fixtures/examples/sentence.rb +18 -0
  105. data/spec/fixtures/examples/simple_xml.rb +51 -0
  106. data/spec/fixtures/examples/string_parser.rb +77 -0
  107. data/spec/parslet/atom_results_spec.rb +39 -0
  108. data/spec/parslet/atoms/alternative_spec.rb +26 -0
  109. data/spec/parslet/atoms/base_spec.rb +127 -0
  110. data/spec/parslet/atoms/capture_spec.rb +21 -0
  111. data/spec/parslet/atoms/combinations_spec.rb +5 -0
  112. data/spec/parslet/atoms/dsl_spec.rb +7 -0
  113. data/spec/parslet/atoms/entity_spec.rb +77 -0
  114. data/spec/parslet/atoms/ignored_spec.rb +15 -0
  115. data/spec/parslet/atoms/infix_spec.rb +5 -0
  116. data/spec/parslet/atoms/lookahead_spec.rb +22 -0
  117. data/spec/parslet/atoms/named_spec.rb +4 -0
  118. data/spec/parslet/atoms/re_spec.rb +14 -0
  119. data/spec/parslet/atoms/repetition_spec.rb +24 -0
  120. data/spec/parslet/atoms/scope_spec.rb +26 -0
  121. data/spec/parslet/atoms/sequence_spec.rb +28 -0
  122. data/spec/parslet/atoms/str_spec.rb +15 -0
  123. data/spec/parslet/atoms/visitor_spec.rb +101 -0
  124. data/spec/parslet/atoms_spec.rb +488 -0
  125. data/spec/parslet/convenience_spec.rb +54 -0
  126. data/spec/parslet/error_reporter/contextual_spec.rb +118 -0
  127. data/spec/parslet/error_reporter/deepest_spec.rb +82 -0
  128. data/spec/parslet/error_reporter/tree_spec.rb +7 -0
  129. data/spec/parslet/export_spec.rb +40 -0
  130. data/spec/parslet/expression/treetop_spec.rb +74 -0
  131. data/spec/parslet/minilisp.citrus +29 -0
  132. data/spec/parslet/minilisp.tt +29 -0
  133. data/spec/parslet/parser_spec.rb +36 -0
  134. data/spec/parslet/parslet_spec.rb +38 -0
  135. data/spec/parslet/pattern_spec.rb +272 -0
  136. data/spec/parslet/position_spec.rb +14 -0
  137. data/spec/parslet/rig/rspec_spec.rb +54 -0
  138. data/spec/parslet/scope_spec.rb +45 -0
  139. data/spec/parslet/slice_spec.rb +186 -0
  140. data/spec/parslet/source/line_cache_spec.rb +74 -0
  141. data/spec/parslet/source_spec.rb +210 -0
  142. data/spec/parslet/transform/context_spec.rb +56 -0
  143. data/spec/parslet/transform_spec.rb +183 -0
  144. data/spec/spec_helper.rb +74 -0
  145. data/spec/support/opal.rb +8 -0
  146. data/spec/support/opal.rb.erb +14 -0
  147. data/spec/support/parslet_matchers.rb +96 -0
  148. 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