parslet 1.7.0 → 1.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README +1 -1
- data/lib/parslet/context.rb +1 -1
- data/lib/parslet/parser.rb +1 -1
- data/parslet.gemspec +18 -0
- data/spec/acceptance/examples_spec.rb +37 -0
- data/spec/acceptance/infix_parser_spec.rb +112 -0
- data/spec/acceptance/regression_spec.rb +314 -0
- data/spec/acceptance/repetition_and_maybe_spec.rb +42 -0
- data/spec/acceptance/unconsumed_input_spec.rb +21 -0
- data/spec/parslet/atom_results_spec.rb +39 -0
- data/spec/parslet/atoms/alternative_spec.rb +26 -0
- data/spec/parslet/atoms/base_spec.rb +126 -0
- data/spec/parslet/atoms/capture_spec.rb +21 -0
- data/spec/parslet/atoms/combinations_spec.rb +5 -0
- data/spec/parslet/atoms/dsl_spec.rb +25 -0
- data/spec/parslet/atoms/entity_spec.rb +77 -0
- data/spec/parslet/atoms/infix_spec.rb +5 -0
- data/spec/parslet/atoms/lookahead_spec.rb +22 -0
- data/spec/parslet/atoms/named_spec.rb +4 -0
- data/spec/parslet/atoms/re_spec.rb +14 -0
- data/spec/parslet/atoms/repetition_spec.rb +24 -0
- data/spec/parslet/atoms/scope_spec.rb +26 -0
- data/spec/parslet/atoms/sequence_spec.rb +28 -0
- data/spec/parslet/atoms/str_spec.rb +15 -0
- data/spec/parslet/atoms/visitor_spec.rb +80 -0
- data/spec/parslet/atoms_spec.rb +429 -0
- data/spec/parslet/convenience_spec.rb +48 -0
- data/spec/parslet/error_reporter/contextual_spec.rb +115 -0
- data/spec/parslet/error_reporter/deepest_spec.rb +73 -0
- data/spec/parslet/error_reporter/tree_spec.rb +7 -0
- data/spec/parslet/export_spec.rb +67 -0
- data/spec/parslet/expression/treetop_spec.rb +74 -0
- data/spec/parslet/minilisp.citrus +29 -0
- data/spec/parslet/minilisp.tt +29 -0
- data/spec/parslet/parser_spec.rb +31 -0
- data/spec/parslet/parslet_spec.rb +38 -0
- data/spec/parslet/pattern_spec.rb +272 -0
- data/spec/parslet/position_spec.rb +14 -0
- data/spec/parslet/rig/rspec_spec.rb +54 -0
- data/spec/parslet/scope_spec.rb +45 -0
- data/spec/parslet/slice_spec.rb +144 -0
- data/spec/parslet/source/line_cache_spec.rb +74 -0
- data/spec/parslet/source_spec.rb +168 -0
- data/spec/parslet/transform/context_spec.rb +35 -0
- data/spec/parslet/transform_spec.rb +165 -0
- data/spec/spec_helper.rb +38 -0
- 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,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,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,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
|