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