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,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