plurimath-parslet 3.0.0
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 +7 -0
- data/HISTORY.txt +284 -0
- data/LICENSE +23 -0
- data/README.adoc +454 -0
- data/Rakefile +71 -0
- data/lib/parslet/accelerator/application.rb +62 -0
- data/lib/parslet/accelerator/engine.rb +112 -0
- data/lib/parslet/accelerator.rb +162 -0
- data/lib/parslet/atoms/alternative.rb +53 -0
- data/lib/parslet/atoms/base.rb +157 -0
- data/lib/parslet/atoms/can_flatten.rb +137 -0
- data/lib/parslet/atoms/capture.rb +38 -0
- data/lib/parslet/atoms/context.rb +103 -0
- data/lib/parslet/atoms/dsl.rb +112 -0
- data/lib/parslet/atoms/dynamic.rb +32 -0
- data/lib/parslet/atoms/entity.rb +45 -0
- data/lib/parslet/atoms/ignored.rb +26 -0
- data/lib/parslet/atoms/infix.rb +115 -0
- data/lib/parslet/atoms/lookahead.rb +52 -0
- data/lib/parslet/atoms/named.rb +32 -0
- data/lib/parslet/atoms/re.rb +41 -0
- data/lib/parslet/atoms/repetition.rb +87 -0
- data/lib/parslet/atoms/scope.rb +26 -0
- data/lib/parslet/atoms/sequence.rb +48 -0
- data/lib/parslet/atoms/str.rb +42 -0
- data/lib/parslet/atoms/visitor.rb +89 -0
- data/lib/parslet/atoms.rb +34 -0
- data/lib/parslet/cause.rb +101 -0
- data/lib/parslet/context.rb +21 -0
- data/lib/parslet/convenience.rb +33 -0
- data/lib/parslet/error_reporter/contextual.rb +120 -0
- data/lib/parslet/error_reporter/deepest.rb +100 -0
- data/lib/parslet/error_reporter/tree.rb +63 -0
- data/lib/parslet/error_reporter.rb +8 -0
- data/lib/parslet/export.rb +163 -0
- data/lib/parslet/expression/treetop.rb +92 -0
- data/lib/parslet/expression.rb +51 -0
- data/lib/parslet/graphviz.rb +97 -0
- data/lib/parslet/parser.rb +68 -0
- data/lib/parslet/pattern/binding.rb +49 -0
- data/lib/parslet/pattern.rb +113 -0
- data/lib/parslet/position.rb +21 -0
- data/lib/parslet/rig/rspec.rb +52 -0
- data/lib/parslet/scope.rb +42 -0
- data/lib/parslet/slice.rb +105 -0
- data/lib/parslet/source/line_cache.rb +99 -0
- data/lib/parslet/source.rb +96 -0
- data/lib/parslet/transform.rb +265 -0
- data/lib/parslet/version.rb +5 -0
- data/lib/parslet.rb +314 -0
- data/plurimath-parslet.gemspec +42 -0
- data/spec/acceptance/infix_parser_spec.rb +145 -0
- data/spec/acceptance/mixing_parsers_spec.rb +74 -0
- data/spec/acceptance/regression_spec.rb +329 -0
- data/spec/acceptance/repetition_and_maybe_spec.rb +44 -0
- data/spec/acceptance/unconsumed_input_spec.rb +21 -0
- data/spec/examples/boolean_algebra_spec.rb +257 -0
- data/spec/examples/calc_spec.rb +278 -0
- data/spec/examples/capture_spec.rb +137 -0
- data/spec/examples/comments_spec.rb +186 -0
- data/spec/examples/deepest_errors_spec.rb +420 -0
- data/spec/examples/documentation_spec.rb +205 -0
- data/spec/examples/email_parser_spec.rb +275 -0
- data/spec/examples/empty_spec.rb +37 -0
- data/spec/examples/erb_spec.rb +482 -0
- data/spec/examples/ip_address_spec.rb +153 -0
- data/spec/examples/json_spec.rb +413 -0
- data/spec/examples/local_spec.rb +302 -0
- data/spec/examples/mathn_spec.rb +151 -0
- data/spec/examples/minilisp_spec.rb +492 -0
- data/spec/examples/modularity_spec.rb +340 -0
- data/spec/examples/nested_errors_spec.rb +322 -0
- data/spec/examples/optimized_erb_spec.rb +299 -0
- data/spec/examples/parens_spec.rb +239 -0
- data/spec/examples/prec_calc_spec.rb +525 -0
- data/spec/examples/readme_spec.rb +228 -0
- data/spec/examples/scopes_spec.rb +187 -0
- data/spec/examples/seasons_spec.rb +196 -0
- data/spec/examples/sentence_spec.rb +119 -0
- data/spec/examples/simple_xml_spec.rb +250 -0
- data/spec/examples/string_parser_spec.rb +407 -0
- data/spec/fixtures/examples/boolean_algebra.rb +62 -0
- data/spec/fixtures/examples/calc.rb +86 -0
- data/spec/fixtures/examples/capture.rb +36 -0
- data/spec/fixtures/examples/comments.rb +22 -0
- data/spec/fixtures/examples/deepest_errors.rb +99 -0
- data/spec/fixtures/examples/documentation.rb +32 -0
- data/spec/fixtures/examples/email_parser.rb +42 -0
- data/spec/fixtures/examples/empty.rb +10 -0
- data/spec/fixtures/examples/erb.rb +39 -0
- data/spec/fixtures/examples/ip_address.rb +103 -0
- data/spec/fixtures/examples/json.rb +107 -0
- data/spec/fixtures/examples/local.rb +60 -0
- data/spec/fixtures/examples/mathn.rb +47 -0
- data/spec/fixtures/examples/minilisp.rb +75 -0
- data/spec/fixtures/examples/modularity.rb +60 -0
- data/spec/fixtures/examples/nested_errors.rb +95 -0
- data/spec/fixtures/examples/optimized_erb.rb +105 -0
- data/spec/fixtures/examples/parens.rb +25 -0
- data/spec/fixtures/examples/prec_calc.rb +71 -0
- data/spec/fixtures/examples/readme.rb +59 -0
- data/spec/fixtures/examples/scopes.rb +43 -0
- data/spec/fixtures/examples/seasons.rb +40 -0
- data/spec/fixtures/examples/sentence.rb +18 -0
- data/spec/fixtures/examples/simple_xml.rb +51 -0
- data/spec/fixtures/examples/string_parser.rb +77 -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 +127 -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 +7 -0
- data/spec/parslet/atoms/entity_spec.rb +77 -0
- data/spec/parslet/atoms/ignored_spec.rb +15 -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 +101 -0
- data/spec/parslet/atoms_spec.rb +488 -0
- data/spec/parslet/convenience_spec.rb +54 -0
- data/spec/parslet/error_reporter/contextual_spec.rb +118 -0
- data/spec/parslet/error_reporter/deepest_spec.rb +82 -0
- data/spec/parslet/error_reporter/tree_spec.rb +7 -0
- data/spec/parslet/export_spec.rb +40 -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 +36 -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 +186 -0
- data/spec/parslet/source/line_cache_spec.rb +74 -0
- data/spec/parslet/source_spec.rb +210 -0
- data/spec/parslet/transform/context_spec.rb +56 -0
- data/spec/parslet/transform_spec.rb +183 -0
- data/spec/spec_helper.rb +74 -0
- data/spec/support/opal.rb +8 -0
- data/spec/support/opal.rb.erb +14 -0
- data/spec/support/parslet_matchers.rb +96 -0
- metadata +240 -0
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Parslet::ErrorReporter::Deepest do
|
4
|
+
let(:reporter) { described_class.new }
|
5
|
+
let(:fake_source) { double('source') }
|
6
|
+
|
7
|
+
describe '#err' do
|
8
|
+
before do
|
9
|
+
allow(fake_source).to receive(:pos).and_return(13)
|
10
|
+
allow(fake_source).to receive(:line_and_column).and_return([1, 1])
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'returns the deepest cause' do
|
14
|
+
expect(reporter).to receive(:deepest).and_return(:deepest)
|
15
|
+
expect(reporter.err('parslet', fake_source, 'message')).to eq(:deepest)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#err_at' do
|
20
|
+
before do
|
21
|
+
allow(fake_source).to receive(:pos).and_return(13)
|
22
|
+
allow(fake_source).to receive(:line_and_column).and_return([1, 1])
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'returns the deepest cause' do
|
26
|
+
expect(reporter).to receive(:deepest).and_return(:deepest)
|
27
|
+
expect(reporter.err('parslet', fake_source, 'message', 13)).to eq(:deepest)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '#deepest(cause)' do
|
32
|
+
def fake_cause(pos = 13, children = nil)
|
33
|
+
double('cause' + pos.to_s, pos: pos, children: children)
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'when there is no deepest cause yet' do
|
37
|
+
let(:cause) { fake_cause }
|
38
|
+
|
39
|
+
it 'returns the given cause' do
|
40
|
+
reporter.deepest(cause).should == cause
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'when the previous cause is deeper (no relationship)' do
|
45
|
+
let(:previous) { fake_cause }
|
46
|
+
|
47
|
+
before do
|
48
|
+
reporter.deepest(previous)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'returns the previous cause' do
|
52
|
+
reporter.deepest(fake_cause(12))
|
53
|
+
.should == previous
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'when the previous cause is deeper (child)' do
|
58
|
+
let(:previous) { fake_cause }
|
59
|
+
|
60
|
+
before do
|
61
|
+
reporter.deepest(previous)
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'returns the given cause' do
|
65
|
+
given = fake_cause(12, [previous])
|
66
|
+
reporter.deepest(given).should == given
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'when the previous cause is shallower' do
|
71
|
+
before do
|
72
|
+
reporter.deepest(fake_cause)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'stores the cause as deepest' do
|
76
|
+
deeper = fake_cause(14)
|
77
|
+
reporter.deepest(deeper)
|
78
|
+
reporter.deepest_cause.should == deeper
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative '../fixtures/examples/minilisp'
|
3
|
+
|
4
|
+
RSpec.describe Parslet::Parser, "exporting to other lingos" do
|
5
|
+
let(:parser) { MiniLisp::Parser.new }
|
6
|
+
|
7
|
+
# I only update the files once I've verified the new syntax to work with
|
8
|
+
# the respective tools. This is more an acceptance test than a real spec.
|
9
|
+
|
10
|
+
describe "<- #to_citrus" do
|
11
|
+
let(:citrus) { File.read(
|
12
|
+
File.join(File.dirname(__FILE__), 'minilisp.citrus'))
|
13
|
+
}
|
14
|
+
|
15
|
+
it "should be valid citrus syntax" do
|
16
|
+
|
17
|
+
if RUBY_ENGINE == 'opal'
|
18
|
+
skip "Citrus export not supported in Opal, somehow?"
|
19
|
+
end
|
20
|
+
# puts parser.to_citrus
|
21
|
+
parser.to_citrus.should == citrus
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "<- #to_treetop" do
|
26
|
+
let(:treetop) { File.read(
|
27
|
+
File.join(File.dirname(__FILE__), 'minilisp.tt'))
|
28
|
+
}
|
29
|
+
|
30
|
+
it "should be valid treetop syntax" do
|
31
|
+
|
32
|
+
if RUBY_ENGINE == 'opal'
|
33
|
+
skip "Treetop export not supported in Opal, somehow?"
|
34
|
+
end
|
35
|
+
|
36
|
+
# puts parser.to_treetop
|
37
|
+
parser.to_treetop.should == treetop
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'parslet'
|
4
|
+
|
5
|
+
describe Parslet::Expression::Treetop do
|
6
|
+
include Parslet
|
7
|
+
|
8
|
+
describe "positive samples" do
|
9
|
+
[ # pattern # input
|
10
|
+
"'abc'", 'abc',
|
11
|
+
"...", 'abc',
|
12
|
+
"[1-4]", '3',
|
13
|
+
|
14
|
+
"'abc'?", 'abc',
|
15
|
+
"'abc'?", '',
|
16
|
+
|
17
|
+
"('abc')", 'abc',
|
18
|
+
|
19
|
+
"'a' 'b'", 'ab',
|
20
|
+
"'a' ('b')", 'ab',
|
21
|
+
|
22
|
+
"'a' / 'b'", 'a',
|
23
|
+
"'a' / 'b'", 'b',
|
24
|
+
|
25
|
+
"'a'*", 'aaa',
|
26
|
+
"'a'*", '',
|
27
|
+
|
28
|
+
"'a'+", 'aa',
|
29
|
+
"'a'+", 'a',
|
30
|
+
|
31
|
+
"'a'{1,2}", 'a',
|
32
|
+
"'a'{1,2}", 'aa',
|
33
|
+
|
34
|
+
"'a'{1,}", 'a',
|
35
|
+
"'a'{1,}", 'aa',
|
36
|
+
|
37
|
+
"'a'{,2}", '',
|
38
|
+
"'a'{,2}", 'a',
|
39
|
+
"'a'{,2}", 'aa',
|
40
|
+
].each_slice(2) do |pattern, input|
|
41
|
+
context "exp(#{pattern.inspect})" do
|
42
|
+
let(:parslet) { exp(pattern) }
|
43
|
+
subject { parslet }
|
44
|
+
it { should parse(input) }
|
45
|
+
context "string representation" do
|
46
|
+
subject { exp(parslet.to_s) }
|
47
|
+
it { should parse(input, :trace => true) }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
describe "negative samples" do
|
53
|
+
[ # pattern # input
|
54
|
+
"'abc'", 'cba',
|
55
|
+
"[1-4]", '5',
|
56
|
+
|
57
|
+
"'a' / 'b'", 'c',
|
58
|
+
|
59
|
+
"'a'+", '',
|
60
|
+
|
61
|
+
"'a'{1,2}", '',
|
62
|
+
"'a'{1,2}", 'aaa',
|
63
|
+
|
64
|
+
"'a'{1,}", '',
|
65
|
+
|
66
|
+
"'a'{,2}", 'aaa',
|
67
|
+
].each_slice(2) do |pattern, input|
|
68
|
+
context "exp(#{pattern.inspect})" do
|
69
|
+
subject { exp(pattern) }
|
70
|
+
it { should_not parse(input) }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
grammar MiniLisp::Parser
|
2
|
+
rule root
|
3
|
+
(expression)
|
4
|
+
end
|
5
|
+
rule expression
|
6
|
+
((space_p) "(" (space_p) (body) ")" (space_p))
|
7
|
+
end
|
8
|
+
rule space_p
|
9
|
+
(space)0*1
|
10
|
+
end
|
11
|
+
rule body
|
12
|
+
((expression) | (identifier) | (float) | (integer) | (string))0*
|
13
|
+
end
|
14
|
+
rule space
|
15
|
+
\s1*
|
16
|
+
end
|
17
|
+
rule identifier
|
18
|
+
(([a-zA-Z=*] [a-zA-Z=*_]0*) (space_p))
|
19
|
+
end
|
20
|
+
rule float
|
21
|
+
(((integer) (("." [0-9]1*) | ("e" [0-9]1*))) (space_p))
|
22
|
+
end
|
23
|
+
rule integer
|
24
|
+
((("+" | "-")0*1 [0-9]1*) (space_p))
|
25
|
+
end
|
26
|
+
rule string
|
27
|
+
("\"" (("\\" .) | (!"\"" .))0* "\"" (space_p))
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
grammar MiniLisp::Parser
|
2
|
+
rule root
|
3
|
+
(expression)
|
4
|
+
end
|
5
|
+
rule expression
|
6
|
+
((space_p) "(" (space_p) (body) ")" (space_p))
|
7
|
+
end
|
8
|
+
rule space_p
|
9
|
+
(space)0..1
|
10
|
+
end
|
11
|
+
rule body
|
12
|
+
((expression) / (identifier) / (float) / (integer) / (string))0..
|
13
|
+
end
|
14
|
+
rule space
|
15
|
+
\s1..
|
16
|
+
end
|
17
|
+
rule identifier
|
18
|
+
(([a-zA-Z=*] [a-zA-Z=*_]0..) (space_p))
|
19
|
+
end
|
20
|
+
rule float
|
21
|
+
(((integer) (("." [0-9]1..) / ("e" [0-9]1..))) (space_p))
|
22
|
+
end
|
23
|
+
rule integer
|
24
|
+
((("+" / "-")0..1 [0-9]1..) (space_p))
|
25
|
+
end
|
26
|
+
rule string
|
27
|
+
("\"" (("\\" .) / (!"\"" .))0.. "\"" (space_p))
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Parslet::Parser do
|
4
|
+
include Parslet
|
5
|
+
class FooParser < Parslet::Parser
|
6
|
+
rule(:foo) { str('foo') }
|
7
|
+
root(:foo)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '<- .root' do
|
11
|
+
parser = Class.new(Parslet::Parser) do
|
12
|
+
def root_parslet
|
13
|
+
:answer
|
14
|
+
end
|
15
|
+
end
|
16
|
+
parser.root :root_parslet
|
17
|
+
|
18
|
+
it "has defined a 'root' method, returning the root" do
|
19
|
+
parser_instance = parser.new
|
20
|
+
expect(parser_instance.root).to eq(:answer)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it "parses 'foo'" do
|
25
|
+
FooParser.new.parse('foo').should == 'foo'
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'composition' do
|
29
|
+
let(:parser) { FooParser.new }
|
30
|
+
|
31
|
+
it 'allows concatenation' do
|
32
|
+
composite = parser >> str('bar')
|
33
|
+
composite.should parse('foobar')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Parslet do
|
4
|
+
include Parslet
|
5
|
+
|
6
|
+
describe Parslet::ParseFailed do
|
7
|
+
it "should be caught by an empty rescue" do
|
8
|
+
begin
|
9
|
+
raise Parslet::ParseFailed
|
10
|
+
rescue
|
11
|
+
# Success! Ignore this.
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
describe "<- .rule" do
|
16
|
+
# Rules define methods. This can be easily tested by defining them right
|
17
|
+
# here.
|
18
|
+
context "empty rule" do
|
19
|
+
rule(:empty) { }
|
20
|
+
|
21
|
+
it "should raise a NotImplementedError" do
|
22
|
+
lambda {
|
23
|
+
empty.parslet
|
24
|
+
}.should raise_error(NotImplementedError)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "containing 'any'" do
|
29
|
+
rule(:any_rule) { any }
|
30
|
+
subject { any_rule }
|
31
|
+
|
32
|
+
it { should be_a Parslet::Atoms::Entity }
|
33
|
+
it "should memoize the returned instance" do
|
34
|
+
any_rule.object_id.should == any_rule.object_id
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,272 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'parslet'
|
4
|
+
|
5
|
+
describe Parslet::Pattern do
|
6
|
+
include Parslet
|
7
|
+
|
8
|
+
# These two factory methods help make the specs more robust to interface
|
9
|
+
# changes. They also help to label trees (t) and patterns (p).
|
10
|
+
def p(pattern)
|
11
|
+
Parslet::Pattern.new(pattern)
|
12
|
+
end
|
13
|
+
def t(obj)
|
14
|
+
obj
|
15
|
+
end
|
16
|
+
|
17
|
+
# Tries to match pattern to the tree, and verifies the bindings hash. Don't
|
18
|
+
# use this for new examples.
|
19
|
+
#
|
20
|
+
RSpec::Matchers.define :match_with_bind do |pattern, exp_bindings|
|
21
|
+
unless respond_to?(:failure_message)
|
22
|
+
alias_method :failure_message_for_should, :failure_message
|
23
|
+
end
|
24
|
+
|
25
|
+
failure_message do |tree|
|
26
|
+
"expected #{pattern.inspect} to match #{tree.inspect}, but didn't. (block wasn't called or not correctly)"
|
27
|
+
end
|
28
|
+
match do |tree|
|
29
|
+
bindings = Parslet::Pattern.new(pattern).match(tree)
|
30
|
+
bindings && bindings == exp_bindings
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# This is the more modern version of verifying a match: (uses 'exp'
|
35
|
+
# implicitly). Checks for a match of pattern in +exp+ and yields the
|
36
|
+
# matched variables.
|
37
|
+
#
|
38
|
+
def with_match_locals(pattern, &block)
|
39
|
+
bindings = p(pattern).match(exp)
|
40
|
+
bindings.should_not be_nil
|
41
|
+
|
42
|
+
block.call(bindings) if block
|
43
|
+
end
|
44
|
+
|
45
|
+
# Can't use #match here, so I went to the Thesaurus.
|
46
|
+
#
|
47
|
+
RSpec::Matchers.define :detect do |pattern|
|
48
|
+
match do |tree|
|
49
|
+
bindings = Parslet::Pattern.new(pattern).match(tree)
|
50
|
+
|
51
|
+
bindings ? true : false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "<- #match" do
|
56
|
+
context "injecting bindings" do
|
57
|
+
let(:pattern) { p(simple(:x)) }
|
58
|
+
|
59
|
+
it "should not modify the original bindings hash" do
|
60
|
+
h = {}
|
61
|
+
b=pattern.match('a', h)
|
62
|
+
h.size.should == 0
|
63
|
+
b.size.should == 1
|
64
|
+
end
|
65
|
+
it "should return nil when no match succeeds" do
|
66
|
+
pattern.match([], :foo => :bar).should be_nil
|
67
|
+
end
|
68
|
+
context "when matching simple(:x) against 'a'" do
|
69
|
+
let(:bindings) { pattern.match(t('a'), :foo => :bar) }
|
70
|
+
|
71
|
+
before(:each) { bindings.should_not be_nil }
|
72
|
+
it "should return the injected bindings" do
|
73
|
+
bindings[:foo].should == :bar
|
74
|
+
end
|
75
|
+
it "should return the new bindings" do
|
76
|
+
bindings[:x].should == 'a'
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
context "simple strings" do
|
81
|
+
let(:exp) { 'aaaa' }
|
82
|
+
|
83
|
+
it "should match simple strings" do
|
84
|
+
exp.should match_with_bind(simple(:x), :x => 'aaaa')
|
85
|
+
end
|
86
|
+
end
|
87
|
+
context "simple hash {:a => 'b'}" do
|
88
|
+
attr_reader :exp
|
89
|
+
before(:each) do
|
90
|
+
@exp = t(:a => 'b')
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should not match {:a => simple(:x), :b => simple(:y)}" do
|
94
|
+
exp.should_not detect(:a => simple(:x), :b => simple(:y))
|
95
|
+
end
|
96
|
+
it "should match {:a => simple(:x)}, binding 'x' to the first argument" do
|
97
|
+
exp.should match_with_bind({:a => simple(:x)}, :x => 'b')
|
98
|
+
end
|
99
|
+
it "should match {:a => 'b'} with no binds" do
|
100
|
+
exp.should match_with_bind({:a => 'b'}, {})
|
101
|
+
end
|
102
|
+
end
|
103
|
+
context "a more complex hash {:a => {:b => 'c'}}" do
|
104
|
+
attr_reader :exp
|
105
|
+
before(:each) do
|
106
|
+
@exp = t(:a => {:b => 'c'})
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should match wholly with {:a => {:b => simple(:x)}}" do
|
110
|
+
exp.should match_with_bind({:a => {:b => simple(:x)}}, :x => 'c')
|
111
|
+
end
|
112
|
+
it "should match wholly with {:a => subtree(:t)}" do
|
113
|
+
with_match_locals(:a => subtree(:t)) do |dict|
|
114
|
+
dict[:t].should == {:b => 'c'}
|
115
|
+
end
|
116
|
+
end
|
117
|
+
it "should not bind subtrees to variables in {:a => simple(:x)}" do
|
118
|
+
p(:a => simple(:x)).should_not detect(exp)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
context "a more complex hash {:a => 'a', :b => 'b'}" do
|
122
|
+
attr_reader :exp
|
123
|
+
before(:each) do
|
124
|
+
@exp = t({:a => 'a', :b => 'b'})
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should not match partially" do
|
128
|
+
Parslet::Pattern.new(:a => simple(:x)).match(exp).should be_nil
|
129
|
+
end
|
130
|
+
it "should match completely" do
|
131
|
+
exp.should match_with_bind({:a => simple(:x), :b => simple(:y)},
|
132
|
+
:x => 'a',
|
133
|
+
:y => 'b')
|
134
|
+
end
|
135
|
+
end
|
136
|
+
context "an array of 'a', 'b', 'c'" do
|
137
|
+
let(:exp) { ['a', 'b', 'c'] }
|
138
|
+
|
139
|
+
it "should match all elements at once" do
|
140
|
+
exp.should match_with_bind(
|
141
|
+
[simple(:x), simple(:y), simple(:z)],
|
142
|
+
:x => 'a', :y => 'b', :z => 'c')
|
143
|
+
end
|
144
|
+
end
|
145
|
+
context "{:a => 'a', :b => 'b'}" do
|
146
|
+
attr_reader :exp
|
147
|
+
before(:each) do
|
148
|
+
@exp = t(:a => 'a', :b => 'b')
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should match both elements simple(:x), simple(:y)" do
|
152
|
+
exp.should match_with_bind(
|
153
|
+
{:a => simple(:x), :b => simple(:y)},
|
154
|
+
:x => 'a', :y => 'b')
|
155
|
+
end
|
156
|
+
it "should not match a constrained match (simple(:x) != simple(:y))" do
|
157
|
+
exp.should_not detect({:a => simple(:x), :b => simple(:x)})
|
158
|
+
end
|
159
|
+
end
|
160
|
+
context "{:a => 'a', :b => 'a'}" do
|
161
|
+
attr_reader :exp
|
162
|
+
before(:each) do
|
163
|
+
@exp = t(:a => 'a', :b => 'a')
|
164
|
+
end
|
165
|
+
|
166
|
+
it "should match constrained pattern" do
|
167
|
+
exp.should match_with_bind(
|
168
|
+
{:a => simple(:x), :b => simple(:x)},
|
169
|
+
:x => 'a')
|
170
|
+
end
|
171
|
+
end
|
172
|
+
context "{:sub1 => {:a => 'a'}, :sub2 => {:a => 'a'}}" do
|
173
|
+
attr_reader :exp
|
174
|
+
before(:each) do
|
175
|
+
@exp = t({
|
176
|
+
:sub1 => {:a => 'a'},
|
177
|
+
:sub2 => {:a => 'a'}
|
178
|
+
})
|
179
|
+
end
|
180
|
+
|
181
|
+
it "should verify constraints over several subtrees" do
|
182
|
+
exp.should match_with_bind({
|
183
|
+
:sub1 => {:a => simple(:x)},
|
184
|
+
:sub2 => {:a => simple(:x)}
|
185
|
+
}, :x => 'a')
|
186
|
+
end
|
187
|
+
it "should return both bind variables simple(:x), simple(:y)" do
|
188
|
+
exp.should match_with_bind({
|
189
|
+
:sub1 => {:a => simple(:x)},
|
190
|
+
:sub2 => {:a => simple(:y)}
|
191
|
+
}, :x => 'a', :y => 'a')
|
192
|
+
end
|
193
|
+
end
|
194
|
+
context "{:sub1 => {:a => 'a'}, :sub2 => {:a => 'b'}}" do
|
195
|
+
attr_reader :exp
|
196
|
+
before(:each) do
|
197
|
+
@exp = t({
|
198
|
+
:sub1 => {:a => 'a'},
|
199
|
+
:sub2 => {:a => 'b'}
|
200
|
+
})
|
201
|
+
end
|
202
|
+
|
203
|
+
it "should verify constraints over several subtrees" do
|
204
|
+
exp.should_not match_with_bind({
|
205
|
+
:sub1 => {:a => simple(:x)},
|
206
|
+
:sub2 => {:a => simple(:x)}
|
207
|
+
}, :x => 'a')
|
208
|
+
end
|
209
|
+
it "should return both bind variables simple(:x), simple(:y)" do
|
210
|
+
exp.should match_with_bind({
|
211
|
+
:sub1 => {:a => simple(:x)},
|
212
|
+
:sub2 => {:a => simple(:y)}
|
213
|
+
}, :x => 'a', :y => 'b')
|
214
|
+
end
|
215
|
+
end
|
216
|
+
context "[{:a => 'x'}, {:a => 'y'}]" do
|
217
|
+
attr_reader :exp
|
218
|
+
before(:each) do
|
219
|
+
@exp = t([{:a => 'x'}, {:a => 'y'}])
|
220
|
+
end
|
221
|
+
|
222
|
+
it "should not match sequence(:x) (as a whole)" do
|
223
|
+
exp.should_not detect(sequence(:x))
|
224
|
+
end
|
225
|
+
end
|
226
|
+
context "['x', 'y', 'z']" do
|
227
|
+
attr_reader :exp
|
228
|
+
before(:each) do
|
229
|
+
@exp = t(['x', 'y', 'z'])
|
230
|
+
end
|
231
|
+
|
232
|
+
it "should match [simple(:x), simple(:y), simple(:z)]" do
|
233
|
+
with_match_locals([simple(:x), simple(:y), simple(:z)]) do |dict|
|
234
|
+
dict[:x].should == 'x'
|
235
|
+
dict[:y].should == 'y'
|
236
|
+
dict[:z].should == 'z'
|
237
|
+
end
|
238
|
+
end
|
239
|
+
it "should match %w(x y z)" do
|
240
|
+
exp.should match_with_bind(%w(x y z), { })
|
241
|
+
end
|
242
|
+
it "should not match [simple(:x), simple(:y), simple(:x)]" do
|
243
|
+
exp.should_not detect([simple(:x), simple(:y), simple(:x)])
|
244
|
+
end
|
245
|
+
it "should not match [simple(:x), simple(:y)]" do
|
246
|
+
exp.should_not detect([simple(:x), simple(:y), simple(:x)])
|
247
|
+
end
|
248
|
+
it "should match sequence(:x) (as array)" do
|
249
|
+
exp.should match_with_bind(sequence(:x), :x => ['x', 'y', 'z'])
|
250
|
+
end
|
251
|
+
end
|
252
|
+
context "{:a => [1,2,3]}" do
|
253
|
+
attr_reader :exp
|
254
|
+
before(:each) do
|
255
|
+
@exp = t(:a => [1,2,3])
|
256
|
+
end
|
257
|
+
|
258
|
+
it "should match :a => sequence(:x) (binding x to the whole array)" do
|
259
|
+
exp.should match_with_bind({:a => sequence(:x)}, {:x => [1,2,3]})
|
260
|
+
end
|
261
|
+
end
|
262
|
+
context "with differently ordered hashes" do
|
263
|
+
it "should still match" do
|
264
|
+
t(:a => 'a', :b => 'b').should detect(:a => 'a', :b => 'b')
|
265
|
+
t(:a => 'a', :b => 'b').should detect(:b => 'b', :a => 'a')
|
266
|
+
|
267
|
+
t(:b => 'b', :a => 'a').should detect(:b => 'b', :a => 'a')
|
268
|
+
t(:b => 'b', :a => 'a').should detect(:a => 'a', :b => 'b')
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Encoding: UTF-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Parslet::Position do
|
6
|
+
slet(:position) { described_class.new('öäüö', 4) }
|
7
|
+
|
8
|
+
it 'should have a charpos of 2' do
|
9
|
+
position.charpos.should == 2
|
10
|
+
end
|
11
|
+
it 'should have a bytepos of 4' do
|
12
|
+
position.bytepos.should == 4
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'parslet/rig/rspec'
|
3
|
+
|
4
|
+
describe 'rspec integration' do
|
5
|
+
include Parslet
|
6
|
+
subject { str('example') }
|
7
|
+
|
8
|
+
it { should parse('example') }
|
9
|
+
it { should_not parse('foo') }
|
10
|
+
it { should parse('example').as('example') }
|
11
|
+
it { should_not parse('foo').as('example') }
|
12
|
+
it { should_not parse('example').as('foo') }
|
13
|
+
|
14
|
+
it { str('foo').as(:bar).should parse('foo').as({:bar => 'foo'}) }
|
15
|
+
it { str('foo').as(:bar).should_not parse('foo').as({:b => 'f'}) }
|
16
|
+
|
17
|
+
it 'accepts a block to assert more specific details about the parsing output' do
|
18
|
+
str('foo').as(:bar).should(parse('foo').as { |output|
|
19
|
+
output.should have_key(:bar)
|
20
|
+
output.values.first.should == 'foo'
|
21
|
+
})
|
22
|
+
end
|
23
|
+
|
24
|
+
# Uncomment to test error messages manually:
|
25
|
+
# it { str('foo').should parse('foo', :trace => true).as('bar') }
|
26
|
+
# it { str('foo').should parse('food', :trace => true) }
|
27
|
+
# it { str('foo').should_not parse('foo', :trace => true).as('foo') }
|
28
|
+
# it { str('foo').should_not parse('foo', :trace => true) }
|
29
|
+
# it 'accepts a block to assert more specific details about the parsing output' do
|
30
|
+
# str('foo').as(:bar).should(parse('foo', :trace => true).as { |output|
|
31
|
+
# output.should_not have_key(:bar)
|
32
|
+
# })
|
33
|
+
# end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
describe 'rspec3 syntax' do
|
38
|
+
include Parslet
|
39
|
+
|
40
|
+
let(:s) { str('example') }
|
41
|
+
|
42
|
+
it { expect(s).to parse('example') }
|
43
|
+
it { expect(s).not_to parse('foo') }
|
44
|
+
it { expect(s).to parse('example').as('example') }
|
45
|
+
it { expect(s).not_to parse('foo').as('example') }
|
46
|
+
|
47
|
+
it { expect(s).not_to parse('example').as('foo') }
|
48
|
+
|
49
|
+
# Uncomment to test error messages manually:
|
50
|
+
# it { expect(str('foo')).to parse('foo', :trace => true).as('bar') }
|
51
|
+
# it { expect(str('foo')).to parse('food', :trace => true) }
|
52
|
+
# it { expect(str('foo')).not_to parse('foo', :trace => true).as('foo') }
|
53
|
+
# it { expect(str('foo')).not_to parse('foo', :trace => true) }
|
54
|
+
end
|