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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d212114155ea2fce120ce17157dcd10a9bb81ebf
4
- data.tar.gz: 07ad0296a6bea037a87594ffc565429b39e6258b
3
+ metadata.gz: 546221c33c4829eb1bede8a241b40418197462de
4
+ data.tar.gz: d440cbbac367cf7909bdeeb764b431bc3a32bdf7
5
5
  SHA512:
6
- metadata.gz: 704d66fde31caf3741fa202d2ac56eaf410ec3ed0cf9050bc834af92a115d847a14e72d61cd97b44c60ba8bc1e50bf792cfadff3257e2b789a7bc9550d0b944a
7
- data.tar.gz: 6ae7e679bfe78d0de4f6c66f4d096f2985b23ece311d5705deb5fe9098ce516ba4d6a7ecb2df7f34d942c7c2370dac3b075d0a6c168e887ac1bdbbdc020a21b2
6
+ metadata.gz: adae2aa91f57458cf679d60c3aefd5545a02f99895745cfc61e05290e86cae69adc1b38c121756e96beb4c6ffb4bce375af04e39050ecfe59154ecd3e187a76a
7
+ data.tar.gz: f8eeac537a6b4dcc958efeb1355d4ad6040e88e91efa2e0397706803ba58c42f69b970720b62cae7f29ff550f50e652c984853af41cab31da7a6ebb4cccbc4ae
data/README CHANGED
@@ -70,4 +70,4 @@ STATUS
70
70
 
71
71
  Production worthy.
72
72
 
73
- (c) 2010-2014 Kaspar Schiess
73
+ (c) 2010-2016 Kaspar Schiess
@@ -21,7 +21,7 @@ class Parslet::Context < BlankSlate
21
21
  include Parslet
22
22
 
23
23
  def meta_def(name, &body)
24
- metaclass = class <<self; self; end
24
+ metaclass = class << self; self; end
25
25
 
26
26
  metaclass.send(:define_method, name, &body)
27
27
  end
@@ -30,7 +30,7 @@
30
30
  class Parslet::Parser < Parslet::Atoms::Base
31
31
  include Parslet
32
32
 
33
- class <<self # class methods
33
+ class << self # class methods
34
34
  # Define the parsers #root function. This is the place where you start
35
35
  # parsing; if you have a rule for 'file' that describes what should be
36
36
  # in a file, this would be your root declaration:
@@ -0,0 +1,18 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'parslet'
5
+ s.version = '1.7.1'
6
+
7
+ s.authors = ['Kaspar Schiess']
8
+ s.email = 'kaspar.schiess@absurd.li'
9
+ s.extra_rdoc_files = ['README']
10
+ s.files = %w(HISTORY.txt LICENSE Rakefile README parslet.gemspec) + Dir.glob("{lib,spec,example}/**/*")
11
+ s.homepage = 'http://kschiess.github.io/parslet'
12
+ s.license = 'MIT'
13
+ s.rdoc_options = ['--main', 'README']
14
+ s.require_paths = ['lib']
15
+ s.summary = 'Parser construction library with great error reporting in Ruby.'
16
+
17
+ s.add_dependency 'blankslate', '>= 2.0', '<= 4.0'
18
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+ require 'open3'
3
+
4
+ describe "Regression on" do
5
+ Dir["example/*.rb"].each do |example|
6
+ context example do
7
+ # Generates a product path for a given example file.
8
+ def product_path(str, ext)
9
+ str.
10
+ gsub('.rb', ".#{ext}").
11
+ gsub('example/','example/output/')
12
+ end
13
+
14
+ it "runs successfully" do
15
+ stdin, stdout, stderr = Open3.popen3("ruby #{example}")
16
+
17
+ handle_map = {
18
+ stdout => :out,
19
+ stderr => :err
20
+ }
21
+ expectation_found = handle_map.any? do |io, ext|
22
+ name = product_path(example, ext)
23
+
24
+ if File.exists?(name)
25
+ io.read.strip.should == File.read(name).strip
26
+ true
27
+ end
28
+ end
29
+
30
+ unless expectation_found
31
+ fail "Example doesn't have either an .err or an .out file. "+
32
+ "Please create in examples/output!"
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,112 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Infix expression parsing' do
4
+ class InfixExpressionParser < Parslet::Parser
5
+ rule(:space) { match['\s'] }
6
+
7
+ def cts atom
8
+ atom >> space.repeat
9
+ end
10
+ def infix *args
11
+ Infix.new(*args)
12
+ end
13
+
14
+ rule(:mul_op) { match['*/'] >> str(' ').maybe }
15
+ rule(:add_op) { match['+-'] >> str(' ').maybe }
16
+ rule(:digit) { match['0-9'] }
17
+ rule(:integer) { cts digit.repeat(1) }
18
+
19
+ rule(:expression) { infix_expression(integer,
20
+ [mul_op, 2, :left],
21
+ [add_op, 1, :right]) }
22
+ end
23
+
24
+ let(:p) { InfixExpressionParser.new }
25
+ describe '#integer' do
26
+ let(:i) { p.integer }
27
+ it "parses integers" do
28
+ i.should parse('1')
29
+ i.should parse('123')
30
+ end
31
+ it "consumes trailing white space" do
32
+ i.should parse('1 ')
33
+ i.should parse('134 ')
34
+ end
35
+ it "doesn't parse floats" do
36
+ i.should_not parse('1.3')
37
+ end
38
+ end
39
+ describe '#multiplication' do
40
+ let(:m) { p.expression }
41
+ it "parses simple multiplication" do
42
+ m.should parse('1*2').as(l: '1', o: '*', r: '2')
43
+ end
44
+ it "parses simple multiplication with spaces" do
45
+ m.should parse('1 * 2').as(l: '1 ', o: '* ', r: '2')
46
+ end
47
+ it "parses division" do
48
+ m.should parse('1/2')
49
+ end
50
+ end
51
+ describe '#addition' do
52
+ let(:a) { p.expression }
53
+
54
+ it "parses simple addition" do
55
+ a.should parse('1+2')
56
+ end
57
+ it "parses complex addition" do
58
+ a.should parse('1+2+3-4')
59
+ end
60
+ it "parses a single element" do
61
+ a.should parse('1')
62
+ end
63
+ end
64
+
65
+ describe 'mixed operations' do
66
+ let(:mo) { p.expression }
67
+
68
+ describe 'inspection' do
69
+ it 'produces useful expressions' do
70
+ p.expression.parslet.inspect.should ==
71
+ "infix_expression(INTEGER, [MUL_OP, ADD_OP])"
72
+ end
73
+ end
74
+ describe 'right associativity' do
75
+ it 'produces trees that lean right' do
76
+ mo.should parse('1+2+3').as(
77
+ l: '1', o: '+', r: {l: '2', o: '+', r: '3'})
78
+ end
79
+ end
80
+ describe 'left associativity' do
81
+ it 'produces trees that lean left' do
82
+ mo.should parse('1*2*3').as(
83
+ l: {l:'1', o:'*', r:'2'}, o:'*', r:'3')
84
+ end
85
+ end
86
+ describe 'error handling' do
87
+ describe 'incomplete expression' do
88
+ it 'produces the right error' do
89
+ cause = catch_failed_parse {
90
+ mo.parse('1+') }
91
+
92
+ cause.ascii_tree.to_s.should == <<-ERROR
93
+ INTEGER was expected at line 1 char 3.
94
+ `- Failed to match sequence (DIGIT{1, } SPACE{0, }) at line 1 char 3.
95
+ `- Expected at least 1 of DIGIT at line 1 char 3.
96
+ `- Premature end of input at line 1 char 3.
97
+ ERROR
98
+ end
99
+ end
100
+ describe 'invalid operator' do
101
+ it 'produces the right error' do
102
+ cause = catch_failed_parse {
103
+ mo.parse('1%') }
104
+
105
+ cause.ascii_tree.to_s.should == <<-ERROR
106
+ Don't know what to do with "%" at line 1 char 2.
107
+ ERROR
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,314 @@
1
+ # Encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+
5
+ require 'parslet'
6
+
7
+ describe "Regressions from real examples" do
8
+ # This parser piece produces on the left a subtree that is keyed (a hash)
9
+ # and on the right a subtree that is a repetition of such subtrees. I've
10
+ # for now decided that these would merge into the repetition such that the
11
+ # return value is an array. This avoids maybe loosing keys/values in a
12
+ # hash merge.
13
+ #
14
+ class ArgumentListParser
15
+ include Parslet
16
+
17
+ rule :argument_list do
18
+ expression.as(:argument) >>
19
+ (comma >> expression.as(:argument)).repeat
20
+ end
21
+ rule :expression do
22
+ string
23
+ end
24
+ rule :string do
25
+ str('"') >>
26
+ (
27
+ str('\\') >> any |
28
+ str('"').absent? >> any
29
+ ).repeat.as(:string) >>
30
+ str('"') >> space?
31
+ end
32
+ rule :comma do
33
+ str(',') >> space?
34
+ end
35
+ rule :space? do
36
+ space.maybe
37
+ end
38
+ rule :space do
39
+ match("[ \t]").repeat(1)
40
+ end
41
+
42
+ def parse(str)
43
+ argument_list.parse(str)
44
+ end
45
+ end
46
+ describe ArgumentListParser do
47
+ let(:instance) { ArgumentListParser.new }
48
+ it "should have method expression" do
49
+ instance.should respond_to(:expression)
50
+ end
51
+ it 'should parse "arg1", "arg2"' do
52
+ result = ArgumentListParser.new.parse('"arg1", "arg2"')
53
+
54
+ result.size.should == 2
55
+ result.each do |r|
56
+ r[:argument]
57
+ end
58
+ end
59
+ it 'should parse "arg1", "arg2", "arg3"' do
60
+ result = ArgumentListParser.new.parse('"arg1", "arg2", "arg3"')
61
+
62
+ result.size.should == 3
63
+ result.each do |r|
64
+ r[:argument]
65
+ end
66
+ end
67
+ end
68
+
69
+ class ParensParser < Parslet::Parser
70
+ rule(:balanced) {
71
+ str('(').as(:l) >> balanced.maybe.as(:m) >> str(')').as(:r)
72
+ }
73
+
74
+ root(:balanced)
75
+ end
76
+ describe ParensParser do
77
+ let(:instance) { ParensParser.new }
78
+
79
+ context "statefulness: trying several expressions in sequence" do
80
+ it "should not be stateful" do
81
+ # NOTE: Since you've come here to read this, I'll explain why
82
+ # this is broken and not fixed: You're looking at the tuning branch,
83
+ # which rewrites a bunch of stuff - so I have failing tests to
84
+ # remind me of what is left to be done. And to remind you not to
85
+ # trust this code.
86
+ instance.parse('(())')
87
+ lambda {
88
+ instance.parse('((()))')
89
+ instance.parse('(((())))')
90
+ }.should_not raise_error
91
+ end
92
+ end
93
+ context "expression '(())'" do
94
+ let(:result) { instance.parse('(())') }
95
+
96
+ it "should yield a doubly nested hash" do
97
+ result.should be_a(Hash)
98
+ result.should have_key(:m)
99
+ result[:m].should be_a(Hash) # This was an array earlier
100
+ end
101
+ context "inner hash" do
102
+ let(:inner) { result[:m] }
103
+
104
+ it "should have nil as :m" do
105
+ inner[:m].should be_nil
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ class ALanguage < Parslet::Parser
112
+ root(:expressions)
113
+
114
+ rule(:expressions) { (line >> eol).repeat(1) | line }
115
+ rule(:line) { space? >> an_expression.as(:exp).repeat }
116
+ rule(:an_expression) { str('a').as(:a) >> space? }
117
+
118
+ rule(:eol) { space? >> match["\n\r"].repeat(1) >> space? }
119
+
120
+ rule(:space?) { space.repeat }
121
+ rule(:space) { multiline_comment.as(:multi) | line_comment.as(:line) | str(' ') }
122
+
123
+ rule(:line_comment) { str('//') >> (match["\n\r"].absent? >> any).repeat }
124
+ rule(:multiline_comment) { str('/*') >> (str('*/').absent? >> any).repeat >> str('*/') }
125
+ end
126
+ describe ALanguage do
127
+ def remove_indent(s)
128
+ s.to_s.lines.map { |l| l.chomp.strip }.join("\n")
129
+ end
130
+
131
+ it "should count lines correctly" do
132
+ cause = catch_failed_parse {
133
+ subject.parse('a
134
+ a a a
135
+ aaa // ff
136
+ /*
137
+ a
138
+ */
139
+ b
140
+ ')
141
+ }
142
+
143
+ remove_indent(cause.ascii_tree).should == remove_indent(%q(
144
+ Expected one of [(LINE EOL){1, }, LINE] at line 1 char 1.
145
+ |- Extra input after last repetition at line 7 char 11.
146
+ | `- Failed to match sequence (LINE EOL) at line 7 char 11.
147
+ | `- Failed to match sequence (SPACE? [\n\r]{1, } SPACE?) at line 7 char 11.
148
+ | `- Expected at least 1 of [\n\r] at line 7 char 11.
149
+ | `- Failed to match [\n\r] at line 7 char 11.
150
+ `- Don't know what to do with "\n " at line 1 char 2.).strip)
151
+ end
152
+ end
153
+
154
+ class BLanguage < Parslet::Parser
155
+ root :expression
156
+ rule(:expression) { b.as(:one) >> b.as(:two) }
157
+ rule(:b) { str('b') }
158
+ end
159
+ describe BLanguage do
160
+ it "should parse 'bb'" do
161
+ subject.should parse('bb').as(:one => 'b', :two => 'b')
162
+ end
163
+ it "should transform with binding constraint" do
164
+ transform = Parslet::Transform.new do |t|
165
+ t.rule(:one => simple(:b), :two => simple(:b)) { :ok }
166
+ end
167
+ transform.apply(subject.parse('bb')).should == :ok
168
+ end
169
+ end
170
+
171
+ class UnicodeLanguage < Parslet::Parser
172
+ root :gobble
173
+ rule(:gobble) { any.repeat }
174
+ end
175
+ describe UnicodeLanguage do
176
+ it "should parse UTF-8 strings" do
177
+ subject.should parse('éèäöü').as('éèäöü')
178
+ subject.should parse('RubyKaigi2009のテーマは、「変わる/変える」です。 前回の').as('RubyKaigi2009のテーマは、「変わる/変える」です。 前回の')
179
+ end
180
+ end
181
+
182
+ class UnicodeSentenceLanguage < Parslet::Parser
183
+ rule(:sentence) { (match('[^。]').repeat(1) >> str("。")).as(:sentence) }
184
+ rule(:sentences) { sentence.repeat }
185
+ root(:sentences)
186
+ end
187
+ describe UnicodeSentenceLanguage do
188
+ let(:string) {
189
+ "RubyKaigi2009のテーマは、「変わる/変える」です。 前回の" +
190
+ "RubyKaigi2008のテーマであった「多様性」の言葉の通り、 " +
191
+ "2008年はRubyそのものに関しても、またRubyの活躍する舞台に関しても、 " +
192
+ "ますます多様化が進みつつあります。RubyKaigi2008は、そのような " +
193
+ "Rubyの生態系をあらためて認識する場となりました。 しかし、" +
194
+ "こうした多様化が進む中、異なる者同士が単純に距離を 置いたままでは、" +
195
+ "その違いを認識したところであまり意味がありません。 異なる実装、" +
196
+ "異なる思想、異なる背景といった、様々な多様性を理解しつつ、 " +
197
+ "すり合わせるべきものをすり合わせ、変えていくべきところを " +
198
+ "変えていくことが、豊かな未来へとつながる道に違いありません。"
199
+ }
200
+
201
+ it "should parse sentences" do
202
+ subject.should parse(string)
203
+ end
204
+ end
205
+
206
+ class TwoCharLanguage < Parslet::Parser
207
+ root :twochar
208
+ rule(:twochar) { any >> str('2') }
209
+ end
210
+ describe TwoCharLanguage do
211
+ def di(s)
212
+ s.strip.to_s.lines.map { |l| l.chomp.strip }.join("\n")
213
+ end
214
+
215
+ it "should raise an error" do
216
+ error = catch_failed_parse {
217
+ subject.parse('123') }
218
+ di(error.ascii_tree).should == di(%q(
219
+ Failed to match sequence (. '2') at line 1 char 2.
220
+ `- Don't know what to do with "3" at line 1 char 3.
221
+ ))
222
+ end
223
+ end
224
+
225
+ # Issue #68: Extra input reporting, written by jmettraux
226
+ class RepetitionParser < Parslet::Parser
227
+ rule(:nl) { match('[\s]').repeat(1) }
228
+ rule(:nl?) { nl.maybe }
229
+ rule(:sp) { str(' ').repeat(1) }
230
+ rule(:sp?) { str(' ').repeat(0) }
231
+ rule(:line) { sp >> str('line') }
232
+ rule(:body) { ((line | block) >> nl).repeat(0) }
233
+ rule(:block) { sp? >> str('begin') >> sp >> match('[a-z]') >> nl >>
234
+ body >> sp? >> str('end') }
235
+ rule(:blocks) { nl? >> block >> (nl >> block).repeat(0) >> nl? }
236
+
237
+ root(:blocks)
238
+ end
239
+ describe RepetitionParser do
240
+ def di(s)
241
+ s.strip.to_s.lines.map { |l| l.chomp.strip }.join("\n")
242
+ end
243
+
244
+ it 'parses a block' do
245
+ subject.parse(%q{
246
+ begin a
247
+ end
248
+ })
249
+ end
250
+ it 'parses nested blocks' do
251
+ subject.parse(%q{
252
+ begin a
253
+ begin b
254
+ end
255
+ end
256
+ })
257
+ end
258
+ it 'parses successive blocks' do
259
+ subject.parse(%q{
260
+ begin a
261
+ end
262
+ begin b
263
+ end
264
+ })
265
+ end
266
+ it 'fails gracefully on a missing end' do
267
+ error = catch_failed_parse {
268
+ subject.parse(%q{
269
+ begin a
270
+ begin b
271
+ end
272
+ }) }
273
+
274
+ di(error.ascii_tree).should == di(%q(
275
+ Failed to match sequence (NL? BLOCK (NL BLOCK){0, } NL?) at line 2 char 11.
276
+ `- Failed to match sequence (SP? 'begin' SP [a-z] NL BODY SP? 'end') at line 5 char 9.
277
+ `- Premature end of input at line 5 char 9.
278
+ ))
279
+ end
280
+ it 'fails gracefully on a missing end (2)' do
281
+ error = catch_failed_parse {
282
+ subject.parse(%q{
283
+ begin a
284
+ end
285
+ begin b
286
+ begin c
287
+ end
288
+ }) }
289
+
290
+ di(error.ascii_tree).should == di(%q(
291
+ Failed to match sequence (NL? BLOCK (NL BLOCK){0, } NL?) at line 3 char 14.
292
+ `- Don't know what to do with "begin b\n " at line 4 char 11.
293
+ ))
294
+ end
295
+ it 'fails gracefully on a missing end (deepest reporter)' do
296
+ error = catch_failed_parse {
297
+ subject.parse(%q{
298
+ begin a
299
+ end
300
+ begin b
301
+ begin c
302
+ li
303
+ end
304
+ end
305
+ },
306
+ :reporter => Parslet::ErrorReporter::Deepest.new) }
307
+
308
+ di(error.ascii_tree).should == di(%q(
309
+ Failed to match sequence (NL? BLOCK (NL BLOCK){0, } NL?) at line 3 char 16.
310
+ `- Expected "end", but got "li\n" at line 6 char 17.
311
+ ))
312
+ end
313
+ end
314
+ end