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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/README +1 -1
  3. data/lib/parslet/context.rb +1 -1
  4. data/lib/parslet/parser.rb +1 -1
  5. data/parslet.gemspec +18 -0
  6. data/spec/acceptance/examples_spec.rb +37 -0
  7. data/spec/acceptance/infix_parser_spec.rb +112 -0
  8. data/spec/acceptance/regression_spec.rb +314 -0
  9. data/spec/acceptance/repetition_and_maybe_spec.rb +42 -0
  10. data/spec/acceptance/unconsumed_input_spec.rb +21 -0
  11. data/spec/parslet/atom_results_spec.rb +39 -0
  12. data/spec/parslet/atoms/alternative_spec.rb +26 -0
  13. data/spec/parslet/atoms/base_spec.rb +126 -0
  14. data/spec/parslet/atoms/capture_spec.rb +21 -0
  15. data/spec/parslet/atoms/combinations_spec.rb +5 -0
  16. data/spec/parslet/atoms/dsl_spec.rb +25 -0
  17. data/spec/parslet/atoms/entity_spec.rb +77 -0
  18. data/spec/parslet/atoms/infix_spec.rb +5 -0
  19. data/spec/parslet/atoms/lookahead_spec.rb +22 -0
  20. data/spec/parslet/atoms/named_spec.rb +4 -0
  21. data/spec/parslet/atoms/re_spec.rb +14 -0
  22. data/spec/parslet/atoms/repetition_spec.rb +24 -0
  23. data/spec/parslet/atoms/scope_spec.rb +26 -0
  24. data/spec/parslet/atoms/sequence_spec.rb +28 -0
  25. data/spec/parslet/atoms/str_spec.rb +15 -0
  26. data/spec/parslet/atoms/visitor_spec.rb +80 -0
  27. data/spec/parslet/atoms_spec.rb +429 -0
  28. data/spec/parslet/convenience_spec.rb +48 -0
  29. data/spec/parslet/error_reporter/contextual_spec.rb +115 -0
  30. data/spec/parslet/error_reporter/deepest_spec.rb +73 -0
  31. data/spec/parslet/error_reporter/tree_spec.rb +7 -0
  32. data/spec/parslet/export_spec.rb +67 -0
  33. data/spec/parslet/expression/treetop_spec.rb +74 -0
  34. data/spec/parslet/minilisp.citrus +29 -0
  35. data/spec/parslet/minilisp.tt +29 -0
  36. data/spec/parslet/parser_spec.rb +31 -0
  37. data/spec/parslet/parslet_spec.rb +38 -0
  38. data/spec/parslet/pattern_spec.rb +272 -0
  39. data/spec/parslet/position_spec.rb +14 -0
  40. data/spec/parslet/rig/rspec_spec.rb +54 -0
  41. data/spec/parslet/scope_spec.rb +45 -0
  42. data/spec/parslet/slice_spec.rb +144 -0
  43. data/spec/parslet/source/line_cache_spec.rb +74 -0
  44. data/spec/parslet/source_spec.rb +168 -0
  45. data/spec/parslet/transform/context_spec.rb +35 -0
  46. data/spec/parslet/transform_spec.rb +165 -0
  47. data/spec/spec_helper.rb +38 -0
  48. 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