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,144 @@
1
+ require 'spec_helper'
2
+
3
+ describe Parslet::Slice do
4
+ def cslice string, offset, cache=nil
5
+ described_class.new(
6
+ Parslet::Position.new(string, offset),
7
+ string, cache)
8
+ end
9
+
10
+ describe "construction" do
11
+ it "should construct from an offset and a string" do
12
+ cslice('foobar', 40)
13
+ end
14
+ end
15
+ context "('foobar', 40, 'foobar')" do
16
+ let(:slice) { cslice('foobar', 40) }
17
+ describe "comparison" do
18
+ it "should be equal to other slices with the same attributes" do
19
+ other = cslice('foobar', 40)
20
+ slice.should == other
21
+ other.should == slice
22
+ end
23
+ it "should be equal to other slices (offset is irrelevant for comparison)" do
24
+ other = cslice('foobar', 41)
25
+ slice.should == other
26
+ other.should == slice
27
+ end
28
+ it "should be equal to a string with the same content" do
29
+ slice.should == 'foobar'
30
+ end
31
+ it "should be equal to a string (inversed operands)" do
32
+ 'foobar'.should == slice
33
+ end
34
+ it "should not be equal to a string" do
35
+ slice.should_not equal('foobar')
36
+ end
37
+ it "should not be eql to a string" do
38
+ slice.should_not eql('foobar')
39
+ end
40
+ it "should not hash to the same number" do
41
+ slice.hash.should_not == 'foobar'.hash
42
+ end
43
+ end
44
+ describe "offset" do
45
+ it "should return the associated offset" do
46
+ slice.offset.should == 6
47
+ end
48
+ it "should fail to return a line and column" do
49
+ lambda {
50
+ slice.line_and_column
51
+ }.should raise_error(ArgumentError)
52
+ end
53
+
54
+ context "when constructed with a source" do
55
+ let(:slice) { cslice(
56
+ 'foobar', 40,
57
+ flexmock(:cache, :line_and_column => [13, 14])) }
58
+ it "should return proper line and column" do
59
+ slice.line_and_column.should == [13, 14]
60
+ end
61
+ end
62
+ end
63
+ describe "string methods" do
64
+ describe "matching" do
65
+ it "should match as a string would" do
66
+ slice.should match(/bar/)
67
+ slice.should match(/foo/)
68
+
69
+ md = slice.match(/f(o)o/)
70
+ md.captures.first.should == 'o'
71
+ end
72
+ end
73
+ describe "<- #size" do
74
+ subject { slice.size }
75
+ it { should == 6 }
76
+ end
77
+ describe "<- #length" do
78
+ subject { slice.length }
79
+ it { should == 6 }
80
+ end
81
+ describe "<- #+" do
82
+ let(:other) { cslice('baz', 10) }
83
+ subject { slice + other }
84
+
85
+ it "should concat like string does" do
86
+ subject.size.should == 9
87
+ subject.should == 'foobarbaz'
88
+ subject.offset.should == 6
89
+ end
90
+ end
91
+ end
92
+ describe "conversion" do
93
+ describe "<- #to_slice" do
94
+ it "should return self" do
95
+ slice.to_slice.should eq(slice)
96
+ end
97
+ end
98
+ describe "<- #to_sym" do
99
+ it "should return :foobar" do
100
+ slice.to_sym.should == :foobar
101
+ end
102
+ end
103
+ describe "cast to Float" do
104
+ it "should return a float" do
105
+ Float(cslice('1.345', 11)).should == 1.345
106
+ end
107
+ end
108
+ describe "cast to Integer" do
109
+ it "should cast to integer as a string would" do
110
+ s = cslice('1234', 40)
111
+ Integer(s).should == 1234
112
+ s.to_i.should == 1234
113
+ end
114
+ it "should fail when Integer would fail on a string" do
115
+ lambda { Integer(slice) }.should raise_error(ArgumentError, /invalid value/)
116
+ end
117
+ it "should turn into zero when a string would" do
118
+ slice.to_i.should == 0
119
+ end
120
+ end
121
+ end
122
+ describe "inspection and string conversion" do
123
+ describe "#inspect" do
124
+ subject { slice.inspect }
125
+ it { should == '"foobar"@6' }
126
+ end
127
+ describe "#to_s" do
128
+ subject { slice.to_s }
129
+ it { should == 'foobar' }
130
+ end
131
+ end
132
+ describe "serializability" do
133
+ it "should serialize" do
134
+ Marshal.dump(slice)
135
+ end
136
+ context "when storing a line cache" do
137
+ let(:slice) { cslice('foobar', 40, Parslet::Source::LineCache.new()) }
138
+ it "should serialize" do
139
+ Marshal.dump(slice)
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,74 @@
1
+ require 'spec_helper'
2
+
3
+ describe Parslet::Source::RangeSearch do
4
+ describe "<- #lbound" do
5
+ context "for a simple array" do
6
+ let(:ary) { [10, 20, 30, 40, 50] }
7
+ before(:each) { ary.extend Parslet::Source::RangeSearch }
8
+
9
+ it "should return correct answers for numbers not in the array" do
10
+ ary.lbound(5).should == 0
11
+ ary.lbound(15).should == 1
12
+ ary.lbound(25).should == 2
13
+ ary.lbound(35).should == 3
14
+ ary.lbound(45).should == 4
15
+ end
16
+ it "should return correct answers for numbers in the array" do
17
+ ary.lbound(10).should == 1
18
+ ary.lbound(20).should == 2
19
+ ary.lbound(30).should == 3
20
+ ary.lbound(40).should == 4
21
+ end
22
+ it "should cover right edge case" do
23
+ ary.lbound(50).should be_nil
24
+ ary.lbound(51).should be_nil
25
+ end
26
+ it "should cover left edge case" do
27
+ ary.lbound(0).should == 0
28
+ end
29
+ end
30
+ context "for an empty array" do
31
+ let(:ary) { [] }
32
+ before(:each) { ary.extend Parslet::Source::RangeSearch }
33
+
34
+ it "should return nil" do
35
+ ary.lbound(1).should be_nil
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ describe Parslet::Source::LineCache do
42
+ describe "<- scan_for_line_endings" do
43
+ context "calculating the line_and_columns" do
44
+ let(:str) { "foo\nbar\nbazd" }
45
+
46
+ it "should return the first line if we have no line ends" do
47
+ subject.scan_for_line_endings(0, nil)
48
+ subject.line_and_column(3).should == [1, 4]
49
+
50
+ subject.scan_for_line_endings(0, "")
51
+ subject.line_and_column(5).should == [1, 6]
52
+ end
53
+
54
+ it "should find the right line starting from pos 0" do
55
+ subject.scan_for_line_endings(0, str)
56
+ subject.line_and_column(5).should == [2, 2]
57
+ subject.line_and_column(9).should == [3, 2]
58
+ end
59
+
60
+ it "should find the right line starting from pos 5" do
61
+ subject.scan_for_line_endings(5, str)
62
+ subject.line_and_column(11).should == [2, 3]
63
+ end
64
+
65
+ it "should find the right line if scannning the string multiple times" do
66
+ subject.scan_for_line_endings(0, str)
67
+ subject.scan_for_line_endings(0, "#{str}\nthe quick\nbrown fox")
68
+ subject.line_and_column(10).should == [3,3]
69
+ subject.line_and_column(24).should == [5,2]
70
+ end
71
+ end
72
+ end
73
+ end
74
+
@@ -0,0 +1,168 @@
1
+ # Encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Parslet::Source do
6
+ describe "using simple input" do
7
+ let(:str) { "a"*100 + "\n" + "a"*100 + "\n" }
8
+ let(:source) { described_class.new(str) }
9
+
10
+ describe "<- #read(n)" do
11
+ it "should not raise error when the return value is nil" do
12
+ described_class.new('').consume(1)
13
+ end
14
+ it "should return 100 'a's when reading 100 chars" do
15
+ source.consume(100).should == 'a'*100
16
+ end
17
+ end
18
+ describe "<- #chars_left" do
19
+ subject { source.chars_left }
20
+
21
+ it { should == 202 }
22
+ context "after depleting the source" do
23
+ before(:each) { source.consume(10000) }
24
+
25
+ it { should == 0 }
26
+ end
27
+ end
28
+ describe "<- #pos" do
29
+ subject { source.pos.charpos }
30
+
31
+ it { should == 0 }
32
+ context "after reading a few bytes" do
33
+ it "should still be correct" do
34
+ pos = 0
35
+ 10.times do
36
+ pos += (n = rand(10)+1)
37
+ source.consume(n)
38
+
39
+ source.pos.charpos.should == pos
40
+ end
41
+ end
42
+ end
43
+ end
44
+ describe "<- #pos=(n)" do
45
+ subject { source.pos.charpos }
46
+ 10.times do
47
+ pos = rand(200)
48
+ context "setting position #{pos}" do
49
+ before(:each) { source.bytepos = pos }
50
+
51
+ it { should == pos }
52
+ end
53
+ end
54
+ end
55
+ describe '#chars_until' do
56
+ it 'should return 100 chars before line end' do
57
+ source.chars_until("\n").should == 100
58
+ end
59
+ end
60
+ describe "<- #column & #line" do
61
+ subject { source.line_and_column }
62
+
63
+ it { should == [1,1] }
64
+
65
+ context "on the first line" do
66
+ it "should increase column with every read" do
67
+ 10.times do |i|
68
+ source.line_and_column.last.should == 1+i
69
+ source.consume(1)
70
+ end
71
+ end
72
+ end
73
+ context "on the second line" do
74
+ before(:each) { source.consume(101) }
75
+ it { should == [2, 1]}
76
+ end
77
+ context "after reading everything" do
78
+ before(:each) { source.consume(10000) }
79
+
80
+ context "when seeking to 9" do
81
+ before(:each) { source.bytepos = 9 }
82
+ it { should == [1, 10] }
83
+ end
84
+ context "when seeking to 100" do
85
+ before(:each) { source.bytepos = 100 }
86
+ it { should == [1, 101] }
87
+ end
88
+ context "when seeking to 101" do
89
+ before(:each) { source.bytepos = 101 }
90
+ it { should == [2, 1] }
91
+ end
92
+ context "when seeking to 102" do
93
+ before(:each) { source.bytepos = 102 }
94
+ it { should == [2, 2] }
95
+ end
96
+ context "when seeking beyond eof" do
97
+ it "should not throw an error" do
98
+ source.bytepos = 1000
99
+ end
100
+ end
101
+ end
102
+ context "reading char by char, storing the results" do
103
+ attr_reader :results
104
+ before(:each) {
105
+ @results = {}
106
+ while source.chars_left>0
107
+ pos = source.pos.charpos
108
+ @results[pos] = source.line_and_column
109
+ source.consume(1)
110
+ end
111
+
112
+ @results.entries.size.should == 202
113
+ @results
114
+ }
115
+
116
+ context "when using pos argument" do
117
+ it "should return the same results" do
118
+ results.each do |pos, result|
119
+ source.line_and_column(pos).should == result
120
+ end
121
+ end
122
+ end
123
+ it "should give the same results when seeking" do
124
+ results.each do |pos, result|
125
+ source.bytepos = pos
126
+ source.line_and_column.should == result
127
+ end
128
+ end
129
+ it "should give the same results when reading" do
130
+ cur = source.bytepos = 0
131
+ while source.chars_left>0
132
+ source.line_and_column.should == results[cur]
133
+ cur += 1
134
+ source.consume(1)
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ end
141
+
142
+ describe "reading encoded input" do
143
+ let(:source) { described_class.new("éö変わる") }
144
+
145
+ def r str
146
+ Regexp.new(Regexp.escape(str))
147
+ end
148
+
149
+ it "should read characters, not bytes" do
150
+ source.should match(r("é"))
151
+ source.consume(1)
152
+ source.pos.charpos.should == 1
153
+ source.bytepos.should == 2
154
+
155
+ source.should match(r("ö"))
156
+ source.consume(1)
157
+ source.pos.charpos.should == 2
158
+ source.bytepos.should == 4
159
+
160
+ source.should match(r("変"))
161
+ source.consume(1)
162
+
163
+ source.consume(2)
164
+ source.chars_left.should == 0
165
+ source.chars_left.should == 0
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe Parslet::Context do
4
+ def context(*args)
5
+ described_class.new(*args)
6
+ end
7
+
8
+ it "binds hash keys as variable like things" do
9
+ context(:a => 'value').instance_eval { a }.
10
+ should == 'value'
11
+ end
12
+ describe 'when a method in BlankSlate is inherited from the environment somehow' do
13
+ before(:each) { BlankSlate.send(:define_method, :a) { 'c' } }
14
+ after(:each) { BlankSlate.send(:undef_method, :a) }
15
+
16
+ it "masks what is already on blank slate" do
17
+ context(:a => 'b').instance_eval { a }.
18
+ should == 'b'
19
+ end
20
+ end
21
+ it "should not reveal define_singleton_method for all users of blankslate, just for us" do
22
+ expect {
23
+ BlankSlate.new.instance_eval {
24
+ define_singleton_method(:foo) { 'foo' }
25
+ }
26
+ }.to raise_error(NoMethodError)
27
+ end
28
+ it "one contexts variables aren't the next ones" do
29
+ ca = context(:a => 'b')
30
+ cb = context(:b => 'c')
31
+
32
+ ca.methods.should_not include(:b)
33
+ cb.methods.should_not include(:a)
34
+ end
35
+ end
@@ -0,0 +1,165 @@
1
+ require 'spec_helper'
2
+
3
+ require 'parslet'
4
+
5
+ describe Parslet::Transform do
6
+ include Parslet
7
+
8
+ let(:transform) { Parslet::Transform.new }
9
+ attr_reader :transform
10
+ before(:each) do
11
+ @transform = Parslet::Transform.new
12
+ end
13
+
14
+ class A < Struct.new(:elt); end
15
+ class B < Struct.new(:elt); end
16
+ class C < Struct.new(:elt); end
17
+ class Bi < Struct.new(:a, :b); end
18
+
19
+ describe "delayed construction" do
20
+ context "given simple(:x) => A.new(x)" do
21
+ before(:each) do
22
+ transform.rule(simple(:x)) { |d| A.new(d[:x]) }
23
+ end
24
+
25
+ it "should transform 'a' into A.new('a')" do
26
+ transform.apply('a').should == A.new('a')
27
+ end
28
+ it "should transform ['a', 'b'] into [A.new('a'), A.new('b')]" do
29
+ transform.apply(['a', 'b']).should ==
30
+ [A.new('a'), A.new('b')]
31
+ end
32
+ end
33
+ context "given rules on {:a => simple(:x)} and {:b => :_x}" do
34
+ before(:each) do
35
+ transform.rule(:a => simple(:x)) { |d| A.new(d[:x]) }
36
+ transform.rule(:b => simple(:x)) { |d| B.new(d[:x]) }
37
+ end
38
+
39
+ it "should transform {:d=>{:b=>'c'}} into d => B('c')" do
40
+ transform.apply({:d=>{:b=>'c'}}).should == {:d => B.new('c')}
41
+ end
42
+ it "should transform {:a=>{:b=>'c'}} into A(B('c'))" do
43
+ transform.apply({:a=>{:b=>'c'}}).should == A.new(B.new('c'))
44
+ end
45
+ end
46
+ describe "pulling out subbranches" do
47
+ before(:each) do
48
+ transform.rule(:a => {:b => simple(:x)}, :d => {:e => simple(:y)}) { |d|
49
+ Bi.new(*d.values_at(:x, :y))
50
+ }
51
+ end
52
+
53
+ it "should yield Bi.new('c', 'f')" do
54
+ transform.apply(:a => {:b => 'c'}, :d => {:e => 'f'}).should ==
55
+ Bi.new('c', 'f')
56
+ end
57
+ end
58
+ end
59
+ describe "dsl construction" do
60
+ let(:transform) { Parslet::Transform.new do
61
+ rule(simple(:x)) { A.new(x) }
62
+ end
63
+ }
64
+
65
+ it "should still evaluate rules correctly" do
66
+ transform.apply('a').should == A.new('a')
67
+ end
68
+ end
69
+ describe "class construction" do
70
+ class OptimusPrime < Parslet::Transform
71
+ rule(:a => simple(:x)) { A.new(x) }
72
+ rule(:b => simple(:x)) { B.new(x) }
73
+ end
74
+ let(:transform) { OptimusPrime.new }
75
+
76
+ it "should evaluate rules" do
77
+ transform.apply(:a => 'a').should == A.new('a')
78
+ end
79
+
80
+ context "with inheritance" do
81
+ class OptimusPrimeJunior < OptimusPrime
82
+ rule(:b => simple(:x)) { B.new(x.upcase) }
83
+ rule(:c => simple(:x)) { C.new(x) }
84
+ end
85
+ let(:transform) { OptimusPrimeJunior.new }
86
+
87
+ it "should inherit rules from its parent" do
88
+ transform.apply(:a => 'a').should == A.new('a')
89
+ end
90
+
91
+ it "should be able to override rules from its parent" do
92
+ transform.apply(:b => 'b').should == B.new('B')
93
+ end
94
+
95
+ it "should be able to define new rules" do
96
+ transform.apply(:c => 'c').should == C.new('c')
97
+ end
98
+ end
99
+ end
100
+ describe "<- #call_on_match" do
101
+ let(:bindings) { { :foo => 'test' } }
102
+ context "when given a block of arity 1" do
103
+ it "should call the block" do
104
+ called = false
105
+ transform.call_on_match(bindings, lambda do |dict|
106
+ called = true
107
+ end)
108
+
109
+ called.should == true
110
+ end
111
+ it "should yield the bindings" do
112
+ transform.call_on_match(bindings, lambda do |dict|
113
+ dict.should == bindings
114
+ end)
115
+ end
116
+ it "should execute in the current context" do
117
+ foo = 'test'
118
+ transform.call_on_match(bindings, lambda do |dict|
119
+ foo.should == 'test'
120
+ end)
121
+ end
122
+ end
123
+ context "when given a block of arity 0" do
124
+ it "should call the block" do
125
+ called = false
126
+ transform.call_on_match(bindings, proc do
127
+ called = true
128
+ end)
129
+
130
+ called.should == true
131
+ end
132
+ it "should have bindings as local variables" do
133
+ transform.call_on_match(bindings, proc do
134
+ foo.should == 'test'
135
+ end)
136
+ end
137
+ it "should execute in its own context" do
138
+ @bar = 'test'
139
+ transform.call_on_match(bindings, proc do
140
+ @bar.should_not == 'test'
141
+ end)
142
+ end
143
+ end
144
+ end
145
+
146
+ context "various transformations (regression)" do
147
+ context "hashes" do
148
+ it "are matched completely" do
149
+ transform.rule(:a => simple(:x)) { fail }
150
+ transform.apply(:a => 'a', :b => 'b')
151
+ end
152
+ end
153
+ end
154
+
155
+ context "when not using the bindings as hash, but as local variables" do
156
+ it "should access the variables" do
157
+ transform.rule(simple(:x)) { A.new(x) }
158
+ transform.apply('a').should == A.new('a')
159
+ end
160
+ it "should allow context as local variable" do
161
+ transform.rule(simple(:x)) { foo }
162
+ transform.apply('a', :foo => 'bar').should == 'bar'
163
+ end
164
+ end
165
+ end