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,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Tree output" do
4
+ extend Parslet
5
+
6
+ def self.hash_examples(h)
7
+ h.each do |atom, expected|
8
+ it "should convert #{atom} to #{expected.inspect}" do
9
+ atom.parse(input).should == expected
10
+ end
11
+ end
12
+ end
13
+
14
+ context "when parsing the empty string" do
15
+ let(:input) { '' }
16
+ hash_examples(
17
+ # No naming
18
+ str('a').maybe => '',
19
+ str('a').repeat => '',
20
+
21
+ # Named contents: maybe yields nil
22
+ str('a').maybe.as(:f) => {:f => nil},
23
+ str('a').repeat.as(:f) => {:f => []},
24
+
25
+ # Contents that aren't simple strings
26
+ (str('a') >> str('b')).maybe.as(:f) => {:f => nil},
27
+ (str('a') >> str('b')).repeat.as(:f) => {:f => []},
28
+
29
+ # The other way around: Contents would be tagged, but nil result isn't
30
+ (str('a') >> str('b')).as(:f).maybe => '',
31
+ (str('a') >> str('b')).as(:f).repeat => ''
32
+ )
33
+ end
34
+
35
+ context "when parsing 'aa'" do
36
+ let(:input) { 'aa' }
37
+ hash_examples(
38
+ # since they're not named, repetitions get merged together.
39
+ str('a').as(:a).repeat >> str('a').as(:a).repeat => [{:a=>'a'},{:a=>'a'}]
40
+ )
41
+ end
42
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Unconsumed input:" do
4
+ class RepeatingBlockParser < Parslet::Parser
5
+ root :expressions
6
+ rule(:expressions) { expression.repeat }
7
+ rule(:expression) { str('(') >> aab >> str(')') }
8
+ rule(:aab) { str('a').repeat(1) >> str('b') }
9
+ end
10
+ describe RepeatingBlockParser do
11
+ let(:parser) { described_class.new }
12
+ it "throws annotated error" do
13
+ error = catch_failed_parse { parser.parse('(aaac)') }
14
+ end
15
+ it "doesn't error out if prefix is true" do
16
+ expect {
17
+ parser.parse('(aaac)', :prefix => true)
18
+ }.not_to raise_error
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Result of a Parslet#parse' do
4
+ include Parslet; extend Parslet
5
+
6
+ describe "regression" do
7
+ [
8
+ # Behaviour with maybe-nil
9
+ [str('foo').maybe >> str('bar'), "bar", "bar"],
10
+ [str('bar') >> str('foo').maybe, "bar", 'bar'],
11
+
12
+ # These might be hard to understand; look at the result of
13
+ # str.maybe >> str
14
+ # and
15
+ # str.maybe >> str first.
16
+ [(str('f').maybe >> str('b')).repeat, "bb", "bb"],
17
+ [(str('b') >> str('f').maybe).repeat, "bb", 'bb'],
18
+
19
+ [str('a').as(:a) >> (str('b') >> str('c').as(:a)).repeat, 'abc',
20
+ [{:a=>'a'}, {:a=>'c'}]],
21
+
22
+ [str('a').as(:a).repeat >> str('b').as(:b).repeat, 'ab', [{:a=>'a'}, {:b=>'b'}]],
23
+
24
+ # Repetition behaviour / named vs. unnamed
25
+ [str('f').repeat, '', ''],
26
+ [str('f').repeat.as(:f), '', {:f => []}],
27
+
28
+ # Maybe behaviour / named vs. unnamed
29
+ [str('f').maybe, '', ''],
30
+ [str('f').maybe.as(:f), '', {:f => nil}],
31
+ ].each do |parslet, input, result|
32
+ context "#{parslet.inspect}" do
33
+ it "should parse \"#{input}\" into \"#{result}\"" do
34
+ parslet.parse(input).should == result
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe Parslet::Atoms::Alternative do
4
+ include Parslet
5
+
6
+ describe '| shortcut' do
7
+ let(:alternative) { str('a') | str('b') }
8
+
9
+ context "when chained with different atoms" do
10
+ before(:each) {
11
+ # Chain something else to the alternative parslet. If it modifies the
12
+ # parslet atom in place, we'll notice:
13
+
14
+ alternative | str('d')
15
+ }
16
+ let!(:chained) { alternative | str('c') }
17
+
18
+
19
+ it "is side-effect free" do
20
+ chained.should parse('c')
21
+ chained.should parse('a')
22
+ chained.should_not parse('d')
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,126 @@
1
+ require 'spec_helper'
2
+
3
+ describe Parslet::Atoms::Base do
4
+ let(:parslet) { Parslet::Atoms::Base.new }
5
+ let(:context) { Parslet::Atoms::Context.new }
6
+
7
+ describe "<- #try(io)" do
8
+ it "should raise NotImplementedError" do
9
+ lambda {
10
+ parslet.try(flexmock(:io), context, false)
11
+ }.should raise_error(NotImplementedError)
12
+ end
13
+ end
14
+ describe "<- #flatten_sequence" do
15
+ [
16
+ # 9 possibilities for making a word of 2 letters from the alphabeth of
17
+ # A(rray), H(ash) and S(tring). Make sure that all results are valid.
18
+ #
19
+ ['a', 'b'], 'ab', # S S
20
+ [['a'], ['b']], ['a', 'b'], # A A
21
+ [{:a=>'a'}, {:b=>'b'}], {:a=>'a',:b=>'b'}, # H H
22
+
23
+ [{:a=>'a'}, ['a']], [{:a=>'a'}, 'a'], # H A
24
+ [{:a=>'a'}, 's'], {:a=>'a'}, # H S
25
+
26
+ [['a'], {:a=>'a'}], ['a', {:a=>'a'}], # A H (symmetric to H A)
27
+ [['a'], 'b'], ['a'], # A S
28
+
29
+ ['a', {:b=>'b'}], {:b=>'b'}, # S H (symmetric to H S)
30
+ ['a', ['b']], ['b'], # S A (symmetric to A S)
31
+
32
+ [nil, ['a']], ['a'], # handling of lhs nil
33
+ [nil, {:a=>'a'}], {:a=>'a'},
34
+ [['a'], nil], ['a'], # handling of rhs nil
35
+ [{:a=>'a'}, nil], {:a=>'a'}
36
+ ].each_slice(2) do |sequence, result|
37
+ context "for " + sequence.inspect do
38
+ it "should equal #{result.inspect}" do
39
+ parslet.flatten_sequence(sequence).should == result
40
+ end
41
+ end
42
+ end
43
+ end
44
+ describe "<- #flatten_repetition" do
45
+ def unnamed(obj)
46
+ parslet.flatten_repetition(obj, false)
47
+ end
48
+
49
+ it "should give subtrees precedence" do
50
+ unnamed([[{:a=>"a"}, {:m=>"m"}], {:a=>"a"}]).should == [{:a=>"a"}]
51
+ end
52
+ end
53
+ describe '#parse(source)' do
54
+ context "when given something that looks like a source" do
55
+ let(:source) { flexmock("source lookalike",
56
+ :line_and_column => [1,2],
57
+ :bytepos => 1,
58
+ :chars_left => 0) }
59
+
60
+ it "should not rewrap in a source" do
61
+ flexmock(Parslet::Source).
62
+ should_receive(:new => :source_created).never
63
+
64
+ begin
65
+ parslet.parse(source)
66
+ rescue NotImplementedError
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ context "when the parse fails, the exception" do
73
+ it "should contain a string" do
74
+ begin
75
+ Parslet.str('foo').parse('bar')
76
+ rescue Parslet::ParseFailed => ex
77
+ ex.message.should be_kind_of(String)
78
+ end
79
+ end
80
+ end
81
+ context "when not all input is consumed" do
82
+ let(:parslet) { Parslet.str('foo') }
83
+
84
+ it "should raise with a proper error message" do
85
+ error = catch_failed_parse {
86
+ parslet.parse('foobar') }
87
+
88
+ error.to_s.should == "Don't know what to do with \"bar\" at line 1 char 4."
89
+ end
90
+ end
91
+ context "when only parsing string prefix" do
92
+ let(:parslet) { Parslet.str('foo') >> Parslet.str('bar') }
93
+
94
+ it "returns the first half on a prefix parse" do
95
+ parslet.parse('foobarbaz', :prefix => true).should == 'foobar'
96
+ end
97
+ end
98
+
99
+ describe ':reporter option' do
100
+ let(:parslet) { Parslet.str('test') >> Parslet.str('ing') }
101
+ let(:reporter) { flexmock(:reporter) }
102
+
103
+ it "replaces the default reporter" do
104
+ cause = flexmock(:cause)
105
+
106
+ # Two levels of the parse, calling two different error reporting
107
+ # methods.
108
+ reporter.
109
+ should_receive(:err_at).once
110
+ reporter.
111
+ should_receive(:err => cause).once
112
+ reporter.
113
+ should_receive(:succ).once
114
+
115
+ # The final cause will be sent the #raise method.
116
+ cause.
117
+ should_receive(:raise).once.and_throw(:raise)
118
+
119
+ catch(:raise) {
120
+ parslet.parse('testung', :reporter => reporter)
121
+
122
+ fail "NEVER REACHED"
123
+ }
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe Parslet::Atoms::Capture do
4
+ include Parslet
5
+
6
+ let(:context) { Parslet::Atoms::Context.new(nil) }
7
+
8
+ def inject string, parser
9
+ source = Parslet::Source.new(string)
10
+ parser.apply(source, context, true)
11
+ end
12
+
13
+ it "should capture simple results" do
14
+ inject 'a', str('a').capture(:a)
15
+ context.captures[:a].should == 'a'
16
+ end
17
+ it "should capture complex results" do
18
+ inject 'a', str('a').as(:b).capture(:a)
19
+ context.captures[:a].should == {:b => 'a'}
20
+ end
21
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Parslet combinations" do
4
+ include Parslet
5
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe Parslet::Atoms::DSL do
4
+ describe "deprecated methods" do
5
+ let(:parslet) { Parslet.str('foo') }
6
+ describe "<- #absnt?" do
7
+ slet(:absnt) { parslet.absnt? }
8
+ it '#bound_parslet' do
9
+ absnt.bound_parslet.should == parslet
10
+ end
11
+ it 'should be a negative lookahead' do
12
+ absnt.positive.should == false
13
+ end
14
+ end
15
+ describe "<- #prsnt?" do
16
+ slet(:prsnt) { parslet.prsnt? }
17
+ it '#bound_parslet' do
18
+ prsnt.bound_parslet.should == parslet
19
+ end
20
+ it 'should be a positive lookahead' do
21
+ prsnt.positive.should == true
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,77 @@
1
+ require 'spec_helper'
2
+
3
+ describe Parslet::Atoms::Entity do
4
+ context "when constructed with str('bar') inside" do
5
+ let(:named) { Parslet::Atoms::Entity.new('name', &proc { Parslet.str('bar') }) }
6
+
7
+ it "should parse 'bar' without raising exceptions" do
8
+ named.parse('bar')
9
+ end
10
+ it "should raise when applied to 'foo'" do
11
+ lambda {
12
+ named.parse('foo')
13
+ }.should raise_error(Parslet::ParseFailed)
14
+ end
15
+
16
+ describe "#inspect" do
17
+ it "should return the name of the entity" do
18
+ named.inspect.should == 'NAME'
19
+ end
20
+ end
21
+ end
22
+ context "when constructed with empty block" do
23
+ let(:entity) { Parslet::Atoms::Entity.new('name', &proc { }) }
24
+
25
+ it "should raise NotImplementedError" do
26
+ lambda {
27
+ entity.parse('some_string')
28
+ }.should raise_error(NotImplementedError)
29
+ end
30
+ end
31
+
32
+ context "recursive definition parser" do
33
+ class RecDefParser
34
+ include Parslet
35
+ rule :recdef do
36
+ str('(') >> atom >> str(')')
37
+ end
38
+ rule :atom do
39
+ str('a') | str('b') | recdef
40
+ end
41
+ end
42
+ let(:parser) { RecDefParser.new }
43
+
44
+ it "should parse balanced parens" do
45
+ parser.recdef.parse("(((a)))")
46
+ end
47
+ it "should not throw 'stack level too deep' when printing errors" do
48
+ cause = catch_failed_parse { parser.recdef.parse('(((a))') }
49
+ cause.ascii_tree
50
+ end
51
+ end
52
+
53
+ context "when constructed with a label" do
54
+ let(:named) { Parslet::Atoms::Entity.new('name', 'label', &proc { Parslet.str('bar') }) }
55
+
56
+ it "should parse 'bar' without raising exceptions" do
57
+ named.parse('bar')
58
+ end
59
+ it "should raise when applied to 'foo'" do
60
+ lambda {
61
+ named.parse('foo')
62
+ }.should raise_error(Parslet::ParseFailed)
63
+ end
64
+
65
+ describe "#inspect" do
66
+ it "should return the label of the entity" do
67
+ named.inspect.should == 'label'
68
+ end
69
+ end
70
+
71
+ describe "#parslet" do
72
+ it "should set the label on the cached parslet" do
73
+ named.parslet.label.should == 'label'
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe Parslet::Atoms::Infix do
4
+
5
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe Parslet::Atoms::Lookahead do
4
+ include Parslet
5
+
6
+ describe 'negative lookahead' do
7
+ it "influences the error tree" do
8
+ parser = str('f').absent? >> str('b')
9
+ cause = catch_failed_parse { parser.parse('f') }
10
+
11
+ cause.ascii_tree.should == "Failed to match sequence (!'f' 'b') at line 1 char 1.\n`- Input should not start with 'f' at line 1 char 1.\n"
12
+ end
13
+ end
14
+ describe 'positive lookahead' do
15
+ it "influences the error tree" do
16
+ parser = str('f').present? >> str('b')
17
+ cause = catch_failed_parse { parser.parse('b') }
18
+
19
+ cause.ascii_tree.should == "Failed to match sequence (&'f' 'b') at line 1 char 1.\n`- Input should start with 'f' at line 1 char 1.\n"
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,4 @@
1
+ require 'spec_helper'
2
+
3
+ describe Parslet::Atoms::Named do
4
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ describe Parslet::Atoms::Re do
4
+ describe "construction" do
5
+ include Parslet
6
+
7
+ it "should allow match(str) form" do
8
+ match('[a]').should be_a(Parslet::Atoms::Re)
9
+ end
10
+ it "should allow match[str] form" do
11
+ match['a'].should be_a(Parslet::Atoms::Re)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe Parslet::Atoms::Repetition do
4
+ include Parslet
5
+
6
+ describe "repeat" do
7
+ let(:parslet) { str('a') }
8
+
9
+ describe "(min, max)" do
10
+ subject { parslet.repeat(1,2) }
11
+
12
+ it { should_not parse("") }
13
+ it { should parse("a") }
14
+ it { should parse("aa") }
15
+ end
16
+ describe "0 times" do
17
+ it "raises an ArgumentError" do
18
+ expect {
19
+ parslet.repeat(0,0)
20
+ }.to raise_error(ArgumentError)
21
+ end
22
+ end
23
+ end
24
+ end