d-parse 0.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 (73) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +10 -0
  3. data/Gemfile.lock +104 -0
  4. data/Guardfile +3 -0
  5. data/LICENSE +19 -0
  6. data/NEWS.md +0 -0
  7. data/README.md +137 -0
  8. data/Rakefile +14 -0
  9. data/d-parse.gemspec +26 -0
  10. data/lib/d-parse.rb +10 -0
  11. data/lib/d-parse/dsl.rb +71 -0
  12. data/lib/d-parse/failure.rb +46 -0
  13. data/lib/d-parse/parser.rb +102 -0
  14. data/lib/d-parse/parsers.rb +26 -0
  15. data/lib/d-parse/parsers/combinators/alt.rb +32 -0
  16. data/lib/d-parse/parsers/combinators/repeat.rb +42 -0
  17. data/lib/d-parse/parsers/combinators/seq.rb +49 -0
  18. data/lib/d-parse/parsers/highlevel/char_in.rb +13 -0
  19. data/lib/d-parse/parsers/highlevel/intersperse.rb +18 -0
  20. data/lib/d-parse/parsers/highlevel/json.rb +237 -0
  21. data/lib/d-parse/parsers/highlevel/opt.rb +16 -0
  22. data/lib/d-parse/parsers/highlevel/string.rb +13 -0
  23. data/lib/d-parse/parsers/highlevel/whitespace_char.rb +15 -0
  24. data/lib/d-parse/parsers/modifiers/capturing.rb +13 -0
  25. data/lib/d-parse/parsers/modifiers/describe.rb +28 -0
  26. data/lib/d-parse/parsers/modifiers/ignore.rb +17 -0
  27. data/lib/d-parse/parsers/modifiers/lazy.rb +18 -0
  28. data/lib/d-parse/parsers/modifiers/map.rb +24 -0
  29. data/lib/d-parse/parsers/primitives/any.rb +22 -0
  30. data/lib/d-parse/parsers/primitives/bind.rb +25 -0
  31. data/lib/d-parse/parsers/primitives/char.rb +27 -0
  32. data/lib/d-parse/parsers/primitives/char_not.rb +27 -0
  33. data/lib/d-parse/parsers/primitives/char_not_in.rb +30 -0
  34. data/lib/d-parse/parsers/primitives/eof.rb +21 -0
  35. data/lib/d-parse/parsers/primitives/except.rb +33 -0
  36. data/lib/d-parse/parsers/primitives/fail.rb +17 -0
  37. data/lib/d-parse/parsers/primitives/succeed.rb +13 -0
  38. data/lib/d-parse/position.rb +31 -0
  39. data/lib/d-parse/success.rb +35 -0
  40. data/lib/d-parse/version.rb +3 -0
  41. data/samples/parse-bind +25 -0
  42. data/samples/parse-csv +19 -0
  43. data/samples/parse-errortest +45 -0
  44. data/samples/parse-fun +61 -0
  45. data/samples/parse-json +18 -0
  46. data/samples/parse-readme +27 -0
  47. data/spec/d-parse/failure_spec.rb +36 -0
  48. data/spec/d-parse/parser_spec.rb +77 -0
  49. data/spec/d-parse/parsers/alt_spec.rb +48 -0
  50. data/spec/d-parse/parsers/any_spec.rb +15 -0
  51. data/spec/d-parse/parsers/bind_spec.rb +31 -0
  52. data/spec/d-parse/parsers/capture_spec.rb +11 -0
  53. data/spec/d-parse/parsers/char_in_spec.rb +22 -0
  54. data/spec/d-parse/parsers/char_not_in_spec.rb +23 -0
  55. data/spec/d-parse/parsers/char_not_spec.rb +16 -0
  56. data/spec/d-parse/parsers/char_spec.rb +22 -0
  57. data/spec/d-parse/parsers/describe_spec.rb +22 -0
  58. data/spec/d-parse/parsers/end_of_input_spec.rb +20 -0
  59. data/spec/d-parse/parsers/except_spec.rb +20 -0
  60. data/spec/d-parse/parsers/fail_spec.rb +12 -0
  61. data/spec/d-parse/parsers/intersperse_spec.rb +18 -0
  62. data/spec/d-parse/parsers/json_spec.rb +69 -0
  63. data/spec/d-parse/parsers/lazy_spec.rb +16 -0
  64. data/spec/d-parse/parsers/map_spec.rb +54 -0
  65. data/spec/d-parse/parsers/optional_spec.rb +16 -0
  66. data/spec/d-parse/parsers/or_spec.rb +26 -0
  67. data/spec/d-parse/parsers/repeat_spec.rb +40 -0
  68. data/spec/d-parse/parsers/sequence_spec.rb +52 -0
  69. data/spec/d-parse/parsers/string_spec.rb +19 -0
  70. data/spec/d-parse/parsers/succeed_spec.rb +12 -0
  71. data/spec/d-parse/parsers/whitespace_char_spec.rb +14 -0
  72. data/spec/spec_helper.rb +97 -0
  73. metadata +140 -0
@@ -0,0 +1,23 @@
1
+ describe DParse::Parsers::CharNotIn do
2
+ let(:parser) { described_class.new(%w(a b)) }
3
+
4
+ example { expect(parser).to parse('c').up_to(1) }
5
+ example { expect(parser).to parse('ca').up_to(1) }
6
+ example { expect(parser).to parse('cb').up_to(1) }
7
+ example { expect(parser).to parse('cab').up_to(1) }
8
+
9
+ example { expect(parser).not_to parse('').and_fail_at(0).line(0).column(0).with_failure('expected any character not in \'a\', \'b\'') }
10
+ example { expect(parser).not_to parse('a').and_fail_at(0).line(0).column(0).with_failure('expected any character not in \'a\', \'b\'') }
11
+ example { expect(parser).not_to parse('ab').and_fail_at(0).line(0).column(0).with_failure('expected any character not in \'a\', \'b\'') }
12
+ example { expect(parser).not_to parse('ac').and_fail_at(0).line(0).column(0).with_failure('expected any character not in \'a\', \'b\'') }
13
+ example { expect(parser).not_to parse('b').and_fail_at(0).line(0).column(0).with_failure('expected any character not in \'a\', \'b\'') }
14
+ example { expect(parser).not_to parse('ba').and_fail_at(0).line(0).column(0).with_failure('expected any character not in \'a\', \'b\'') }
15
+ example { expect(parser).not_to parse('bb').and_fail_at(0).line(0).column(0).with_failure('expected any character not in \'a\', \'b\'') }
16
+ example { expect(parser).not_to parse('bc').and_fail_at(0).line(0).column(0).with_failure('expected any character not in \'a\', \'b\'') }
17
+
18
+ describe '#inspect' do
19
+ subject { parser.inspect }
20
+
21
+ it { is_expected.to eql('char_not_in(["a", "b"])') }
22
+ end
23
+ end
@@ -0,0 +1,16 @@
1
+ describe DParse::Parsers::CharNot do
2
+ let(:parser) { described_class.new('a') }
3
+
4
+ example { expect(parser).to parse('b').up_to(1) }
5
+ example { expect(parser).to parse('ba').up_to(1) }
6
+
7
+ example { expect(parser).not_to parse('').and_fail_at(0).line(0).column(0).with_failure('expected any character not equal to \'a\'') }
8
+ example { expect(parser).not_to parse('a').and_fail_at(0).line(0).column(0).with_failure('expected any character not equal to \'a\'') }
9
+ example { expect(parser).not_to parse('ab').and_fail_at(0).line(0).column(0).with_failure('expected any character not equal to \'a\'') }
10
+
11
+ describe '#inspect' do
12
+ subject { parser.inspect }
13
+
14
+ it { is_expected.to eql('char_not("a")') }
15
+ end
16
+ end
@@ -0,0 +1,22 @@
1
+ describe DParse::Parsers::Char do
2
+ let(:parser) { described_class.new('a') }
3
+
4
+ example { expect(parser).to parse('a').up_to(1) }
5
+ example { expect(parser).to parse('aa').up_to(1) }
6
+ example { expect(parser).to parse('ab').up_to(1) }
7
+
8
+ example { expect(parser).not_to parse('').and_fail_at(0).line(0).column(0).with_failure('expected \'a\'') }
9
+ example { expect(parser).not_to parse('b').and_fail_at(0).line(0).column(0).with_failure('expected \'a\'') }
10
+
11
+ context 'newline parser' do
12
+ let(:parser) { described_class.new("\n") }
13
+
14
+ example { expect(parser).to parse("\n").up_to(1).line(1).column(0) }
15
+ end
16
+
17
+ describe '#inspect' do
18
+ subject { parser.inspect }
19
+
20
+ it { is_expected.to eql('char("a")') }
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ describe DParse::Parsers::Describe do
2
+ let(:identifier) do
3
+ DParse::Parsers::Seq.new(
4
+ DParse::Parsers::Repeat.new(
5
+ DParse::Parsers::CharIn.new('a'..'z'),
6
+ ),
7
+ DParse::Parsers::EOF.new,
8
+ )
9
+ end
10
+
11
+ let(:parser) { described_class.new(identifier, 'identifier') }
12
+
13
+ example { expect(parser).not_to parse('0123').and_fail_at(0).line(0).column(0).with_failure('expected identifier') }
14
+
15
+ describe '#inspect' do
16
+ subject { parser.inspect }
17
+
18
+ let(:identifier) { DParse::Parsers::Char.new('a') }
19
+
20
+ it { is_expected.to eql('identifier()') }
21
+ end
22
+ end
@@ -0,0 +1,20 @@
1
+ describe DParse::Parsers::EOF do
2
+ let(:parser) { described_class.new }
3
+
4
+ example { expect(parser).to parse('') }
5
+
6
+ example { expect(parser).not_to parse('a').and_fail_at(0).line(0).column(0).with_failure('expected end of input') }
7
+ example { expect(parser).not_to parse('aa').and_fail_at(0).line(0).column(0).with_failure('expected end of input') }
8
+
9
+ example { expect(parser).not_to parse("\n").and_fail_at(0).line(0).column(0).with_failure('expected end of input') }
10
+ example { expect(parser).not_to parse("\r").and_fail_at(0).line(0).column(0).with_failure('expected end of input') }
11
+ example { expect(parser).not_to parse("'").and_fail_at(0).line(0).column(0).with_failure('expected end of input') }
12
+ example { expect(parser).not_to parse('"').and_fail_at(0).line(0).column(0).with_failure('expected end of input') }
13
+ example { expect(parser).not_to parse('\\').and_fail_at(0).line(0).column(0).with_failure('expected end of input') }
14
+
15
+ describe '#inspect' do
16
+ subject { parser.inspect }
17
+
18
+ it { is_expected.to eql('eof()') }
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ describe DParse::Parsers::Except do
2
+ let(:parser) { described_class.new(initial_parser, bad_parser) }
3
+
4
+ let(:initial_parser) { DParse::Parsers::Any.new }
5
+ let(:bad_parser) { DParse::Parsers::Char.new('x') }
6
+
7
+ example { expect(parser).to parse('a').up_to(1) }
8
+ example { expect(parser).to parse('aa').up_to(1) }
9
+ example { expect(parser).to parse('ax').up_to(1) }
10
+ example { expect(parser).not_to parse('x').and_fail_at(0).line(0).column(0).with_failure('expected any character except end of file, not \'x\'') }
11
+ example { expect(parser).not_to parse('xa').and_fail_at(0).line(0).column(0).with_failure('expected any character except end of file, not \'x\'') }
12
+
13
+ example { expect(parser).not_to parse('').and_fail_at(0).line(0).column(0).with_failure('expected any character except end of file') }
14
+
15
+ describe '#inspect' do
16
+ subject { parser.inspect }
17
+
18
+ it { is_expected.to eql('except(any(), char("x"))') }
19
+ end
20
+ end
@@ -0,0 +1,12 @@
1
+ describe DParse::Parsers::Fail do
2
+ let(:parser) { described_class.new }
3
+
4
+ example { expect(parser).not_to parse('').and_fail_at(0).line(0).column(0).with_failure('expected nothing (always fail)') }
5
+ example { expect(parser).not_to parse('a').and_fail_at(0).line(0).column(0).with_failure('expected nothing (always fail)') }
6
+
7
+ describe '#inspect' do
8
+ subject { parser.inspect }
9
+
10
+ it { is_expected.to eql('fail()') }
11
+ end
12
+ end
@@ -0,0 +1,18 @@
1
+ describe DParse::Parsers::Intersperse do
2
+ let(:a) { DParse::Parsers::Char.new('a') }
3
+ let(:b) { DParse::Parsers::Char.new(',') }
4
+ let(:parser) { described_class.new(a, b) }
5
+
6
+ example { expect(parser).to parse('a').up_to(1) }
7
+ example { expect(parser).to parse('a,a').up_to(3) }
8
+ example { expect(parser).to parse('a,a,a').up_to(5) }
9
+
10
+ example { expect(parser).to parse('a,').up_to(1) }
11
+ example { expect(parser).to parse('a,a,').up_to(3) }
12
+
13
+ example { expect(parser).not_to parse('') }
14
+ example { expect(parser).not_to parse(',') }
15
+ example { expect(parser).not_to parse(',a') }
16
+ example { expect(parser).not_to parse(',a,') }
17
+ example { expect(parser).not_to parse(',a,a') }
18
+ end
@@ -0,0 +1,69 @@
1
+ describe DParse::Parsers::JSON do
2
+ let(:parser) do
3
+ DParse::Parsers::Seq.new(
4
+ described_class.new,
5
+ DParse::Parsers::EOF.new,
6
+ ).first
7
+ end
8
+
9
+ let(:input) do
10
+ '{"name": "Denis", "favourite_animals": ["donkey", "giraffe"], "nothing": null, "yup": true, "nope": false, "one": 1, "zero": 0, "two": 2, "object": {"foo":"bar"} }'
11
+ end
12
+
13
+ let(:expected_capture) do
14
+ {
15
+ 'name' => 'Denis',
16
+ 'favourite_animals' => %w(donkey giraffe),
17
+ 'nothing' => nil,
18
+ 'yup' => true,
19
+ 'nope' => false,
20
+ 'one' => 1,
21
+ 'zero' => 0,
22
+ 'two' => 2,
23
+ 'object' => { 'foo' => 'bar' },
24
+ }
25
+ end
26
+
27
+ example { expect(parser).to parse(input).up_to(163).and_capture(expected_capture) }
28
+
29
+ example { expect(parser).to parse('{"a":"b"}').and_capture('a' => 'b') }
30
+ example { expect(parser).to parse('{ "a" : "b" }').and_capture('a' => 'b') }
31
+ example { expect(parser).to parse("{\t\"a\"\t:\t\"b\"\t}").and_capture('a' => 'b') }
32
+ example { expect(parser).to parse("{\r\"a\"\r:\r\"b\"\r}").and_capture('a' => 'b') }
33
+ example { expect(parser).to parse("{\n\"a\"\n:\n\"b\"\n}").and_capture('a' => 'b') }
34
+
35
+ example { expect(parser).to parse('{"num":0}').up_to(9).and_capture('num' => 0) }
36
+ example { expect(parser).to parse('{"nu m":0}').up_to(10).and_capture('nu m' => 0) }
37
+ example { expect(parser).to parse('{"nu\"m":0}').up_to(11).and_capture('nu"m' => 0) }
38
+ example { expect(parser).to parse('{"nu\\\\m":0}').up_to(11).and_capture('nu\\m' => 0) }
39
+ example { expect(parser).to parse('{"nu\\/m":0}').up_to(11).and_capture('nu/m' => 0) }
40
+ example { expect(parser).to parse('{"nu\\bm":0}').up_to(11).and_capture("nu\bm" => 0) }
41
+ example { expect(parser).to parse('{"nu\\fm":0}').up_to(11).and_capture("nu\fm" => 0) }
42
+ example { expect(parser).to parse('{"nu\\nm":0}').up_to(11).and_capture("nu\nm" => 0) }
43
+ example { expect(parser).to parse('{"nu\\rm":0}').up_to(11).and_capture("nu\rm" => 0) }
44
+ example { expect(parser).to parse('{"nu\\tm":0}').up_to(11).and_capture("nu\tm" => 0) }
45
+ example { expect(parser).to parse('{"nu\\u0000m":0}').up_to(15).and_capture("nu\u0000m" => 0) }
46
+ example { expect(parser).to parse('{"nu\\u0020m":0}').up_to(15).and_capture('nu m' => 0) }
47
+
48
+ example { expect(parser).to parse('{"num":0}').up_to(9).and_capture('num' => 0) }
49
+ example { expect(parser).to parse('{"num":-0}').up_to(10).and_capture('num' => 0) }
50
+ example { expect(parser).to parse('{"num":3}').up_to(9).and_capture('num' => 3) }
51
+ example { expect(parser).to parse('{"num":-3}').up_to(10).and_capture('num' => -3) }
52
+ example { expect(parser).to parse('{"num":0.1}').up_to(11).and_capture('num' => 0.1) }
53
+ example { expect(parser).to parse('{"num":-0.1}').up_to(12).and_capture('num' => -0.1) }
54
+
55
+ example { expect(parser).to parse('{"num":0e1}').up_to(11).and_capture('num' => 0) }
56
+ example { expect(parser).to parse('{"num":0E1}').up_to(11).and_capture('num' => 0) }
57
+ example { expect(parser).to parse('{"num":0e2}').up_to(11).and_capture('num' => 0) }
58
+ example { expect(parser).to parse('{"num":0E2}').up_to(11).and_capture('num' => 0) }
59
+
60
+ example { expect(parser).to parse('{"num":2e1}').up_to(11).and_capture('num' => 20) }
61
+ example { expect(parser).to parse('{"num":2E1}').up_to(11).and_capture('num' => 20) }
62
+ example { expect(parser).to parse('{"num":2e2}').up_to(11).and_capture('num' => 200) }
63
+ example { expect(parser).to parse('{"num":2E2}').up_to(11).and_capture('num' => 200) }
64
+
65
+ example { expect(parser).to parse('{"num":2.1e1}').up_to(13).and_capture('num' => 21.0) }
66
+ example { expect(parser).to parse('{"num":2.1E1}').up_to(13).and_capture('num' => 21.0) }
67
+ example { expect(parser).to parse('{"num":2.1e2}').up_to(13).and_capture('num' => 210.0) }
68
+ example { expect(parser).to parse('{"num":2.1E2}').up_to(13).and_capture('num' => 210.0) }
69
+ end
@@ -0,0 +1,16 @@
1
+ describe DParse::Parsers::Lazy do
2
+ let(:char) { DParse::Parsers::Char.new('a') }
3
+ let(:parser) { described_class.new { char } }
4
+
5
+ example { expect(parser).to parse('a').up_to(1) }
6
+ example { expect(parser).to parse('aa').up_to(1) }
7
+ example { expect(parser).to parse('ab').up_to(1) }
8
+ example { expect(parser).not_to parse('') }
9
+ example { expect(parser).not_to parse('b') }
10
+
11
+ describe '#inspect' do
12
+ subject { parser.inspect }
13
+
14
+ it { is_expected.to eql('lazy(?)') }
15
+ end
16
+ end
@@ -0,0 +1,54 @@
1
+ describe DParse::Parsers::Map do
2
+ let(:inner_parser) { double(:parser) }
3
+ let(:parser) { described_class.new(inner_parser) { |data, res, orig_pos| [:mapped, data, res.class.to_s, orig_pos.index] } }
4
+
5
+ describe '#apply / #read' do
6
+ subject { parser.apply('a') }
7
+
8
+ context 'success' do
9
+ before do
10
+ expect(inner_parser).to receive(:read).and_return(
11
+ DParse::Success.new(
12
+ '…',
13
+ DParse::Position.new(index: 10, line: 2, column: 3),
14
+ best_failure: DParse::Failure.new(
15
+ '…',
16
+ DParse::Position.new(index: 20, line: 4, column: 6),
17
+ ),
18
+ ),
19
+ )
20
+ end
21
+
22
+ it 'retains successness' do
23
+ expect(subject).to be_a(DParse::Success)
24
+ end
25
+
26
+ it 'retains position' do
27
+ expect(subject.pos.index).to eql(10)
28
+ expect(subject.pos.line).to eql(2)
29
+ expect(subject.pos.column).to eql(3)
30
+ end
31
+
32
+ it 'maps data' do
33
+ expect(subject.data).to eql([:mapped, nil, 'DParse::Success', 0])
34
+ end
35
+
36
+ it 'retains best_failure' do
37
+ expect(subject.best_failure).to be_a(DParse::Failure)
38
+ expect(subject.best_failure.pos.index).to eql(20)
39
+ expect(subject.best_failure.pos.line).to eql(4)
40
+ expect(subject.best_failure.pos.column).to eql(6)
41
+ end
42
+ end
43
+ end
44
+
45
+ describe '#inspect' do
46
+ subject { parser.inspect }
47
+
48
+ before do
49
+ expect(inner_parser).to receive(:inspect).and_return('test_parser()')
50
+ end
51
+
52
+ it { is_expected.to eql('map(test_parser(), <proc>)') }
53
+ end
54
+ end
@@ -0,0 +1,16 @@
1
+ describe DParse::Parsers::Opt do
2
+ let(:char) { DParse::Parsers::Char.new('a') }
3
+ let(:parser) { described_class.new(char) }
4
+
5
+ example { expect(parser).to parse('a').up_to(1) }
6
+ example { expect(parser).to parse('b').up_to(0) }
7
+ example { expect(parser).to parse('ab').up_to(1) }
8
+ example { expect(parser).to parse('ba').up_to(0) }
9
+ example { expect(parser).to parse('').up_to(0) }
10
+
11
+ describe '#inspect' do
12
+ subject { parser.inspect }
13
+
14
+ it { is_expected.to eql('alt(char("a"),succeed())') }
15
+ end
16
+ end
@@ -0,0 +1,26 @@
1
+ describe DParse::Parsers::Alt do
2
+ let(:a) { DParse::Parsers::Char.new('a') }
3
+ let(:b) { DParse::Parsers::Char.new('b') }
4
+ let(:parser) { described_class.new(a, b) }
5
+
6
+ example { expect(parser).to parse('a') }
7
+ example { expect(parser).to parse('aa') }
8
+ example { expect(parser).to parse('ab') }
9
+ example { expect(parser).to parse('ac') }
10
+ example { expect(parser).to parse('b') }
11
+ example { expect(parser).to parse('ba') }
12
+ example { expect(parser).to parse('bb') }
13
+ example { expect(parser).to parse('bc') }
14
+
15
+ example { expect(parser).not_to parse('') }
16
+ example { expect(parser).not_to parse('c') }
17
+ example { expect(parser).not_to parse('ca') }
18
+ example { expect(parser).not_to parse('cb') }
19
+ example { expect(parser).not_to parse('cc') }
20
+
21
+ describe '#inspect' do
22
+ subject { parser.inspect }
23
+
24
+ it { is_expected.to eql('alt(char("a"),char("b"))') }
25
+ end
26
+ end
@@ -0,0 +1,40 @@
1
+ describe DParse::Parsers::Repeat do
2
+ let(:char) { DParse::Parsers::Char.new('a') }
3
+ let(:parser) { described_class.new(char) }
4
+
5
+ example { expect(parser).to parse('').up_to(0) }
6
+ example { expect(parser).to parse('b').up_to(0) }
7
+ example { expect(parser).to parse('a').up_to(1) }
8
+ example { expect(parser).to parse('ab').up_to(1) }
9
+ example { expect(parser).to parse('aa').up_to(2) }
10
+ example { expect(parser).to parse('aab').up_to(2) }
11
+ example { expect(parser).to parse('aaa').up_to(3) }
12
+ example { expect(parser).to parse('aaab').up_to(3) }
13
+
14
+ describe '#inspect' do
15
+ subject { parser.inspect }
16
+
17
+ it { is_expected.to eql('repeat(char("a"))') }
18
+ end
19
+
20
+ context 'successes with associated failures' do
21
+ let(:parser) { described_class.new(DParse::Parsers::String.new('hello')) }
22
+
23
+ it 'picks the most specific failure' do
24
+ expect(parser.apply('hell')).to be_a(DParse::Success)
25
+ expect(parser.apply('hell').pos.index).to eql(0)
26
+ expect(parser.apply('hell').best_failure).to be_a(DParse::Failure)
27
+ expect(parser.apply('hell').best_failure.pos.index).to eql(4)
28
+
29
+ expect(parser.apply('hello')).to be_a(DParse::Success)
30
+ expect(parser.apply('hello').pos.index).to eql(5)
31
+ expect(parser.apply('hello').best_failure).to be_a(DParse::Failure)
32
+ expect(parser.apply('hello').best_failure.pos.index).to eql(5)
33
+
34
+ expect(parser.apply('helloh')).to be_a(DParse::Success)
35
+ expect(parser.apply('helloh').pos.index).to eql(5)
36
+ expect(parser.apply('helloh').best_failure).to be_a(DParse::Failure)
37
+ expect(parser.apply('helloh').best_failure.pos.index).to eql(6)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,52 @@
1
+ describe DParse::Parsers::Seq do
2
+ let(:char_a) { DParse::Parsers::Char.new('a') }
3
+ let(:char_b) { DParse::Parsers::Char.new('b') }
4
+ let(:parser) { described_class.new(char_a, char_b) }
5
+
6
+ example { expect(parser).not_to parse('').and_fail_at(0).line(0).column(0).with_failure('expected \'a\'') }
7
+ example { expect(parser).not_to parse('a').and_fail_at(1).line(0).column(1).with_failure('expected \'b\'') }
8
+ example { expect(parser).not_to parse('ac').and_fail_at(1).line(0).column(1).with_failure('expected \'b\'') }
9
+ example { expect(parser).not_to parse('b').and_fail_at(0).line(0).column(0).with_failure('expected \'a\'') }
10
+ example { expect(parser).not_to parse('ba').and_fail_at(0).line(0).column(0).with_failure('expected \'a\'') }
11
+
12
+ example { expect(parser).to parse('ab').up_to(2) }
13
+ example { expect(parser).to parse('aba').up_to(2) }
14
+ example { expect(parser).to parse('abb').up_to(2) }
15
+
16
+ describe '#inspect' do
17
+ subject { parser.inspect }
18
+
19
+ it { is_expected.to eql('seq(char("a"),char("b"))') }
20
+ end
21
+
22
+ context 'successes with associated failures' do
23
+ let(:a) { double('parser') }
24
+ let(:b) { double('parser') }
25
+
26
+ before do
27
+ expect(a).to receive(:read).and_return(
28
+ DParse::Success.new(
29
+ '…',
30
+ DParse::Position.new(index: 1, line: 0, column: 1),
31
+ best_failure: DParse::Failure.new(
32
+ '…',
33
+ DParse::Position.new(index: 20, line: 0, column: 20),
34
+ ),
35
+ ),
36
+ )
37
+
38
+ expect(b).to receive(:read).and_return(
39
+ DParse::Failure.new(
40
+ '…',
41
+ DParse::Position.new(index: 10, line: 0, column: 10),
42
+ ),
43
+ )
44
+ end
45
+
46
+ let(:parser) { described_class.new(a, b) }
47
+
48
+ it 'picks the most specific failure' do
49
+ expect(parser).not_to parse('…').and_fail_at(20).line(0).column(20)
50
+ end
51
+ end
52
+ end