parslet 1.7.0 → 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
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