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