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,3 @@
1
+ module DParse
2
+ VERSION = '0.1'.freeze
3
+ end
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'd-parse'
4
+
5
+ include DParse::DSL
6
+
7
+ digit = chars(*('0'..'9')).capture
8
+ letter = chars(*('a'..'z')).capture
9
+
10
+ init = alt(char('d'), char('l')).capture
11
+
12
+ parser_map = {
13
+ 'd' => digit,
14
+ 'l' => letter,
15
+ }
16
+
17
+ pair =
18
+ init.bind do |data|
19
+ new_parser = parser_map[data]
20
+ new_parser.map { |d| [data, d] }
21
+ end
22
+
23
+ parser = repeat(pair)
24
+
25
+ p parser.apply('lad1d2d3lblc')
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'd-parse'
4
+ require 'pp'
5
+
6
+ data = <<EOS
7
+ first_name,last_name,age
8
+ Denis,Defreyne,29
9
+ EOS
10
+
11
+ module Grammar
12
+ extend DParse::DSL
13
+
14
+ FIELD = repeat(char_not_in([',', "\n"])).capture
15
+ LINE = intersperse(FIELD, char(',')).select_even
16
+ FILE = seq(intersperse(LINE, char("\n")).select_even, eof).first
17
+ end
18
+
19
+ pp Grammar::FILE.apply(data)
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'd-parse'
4
+ require 'pp'
5
+
6
+ module MyGrammar
7
+ extend DParse::DSL
8
+
9
+ LETTER =
10
+ describe(alt(*('a'..'z').map { |c| char(c) }), 'letter')
11
+
12
+ IDENTIFIER =
13
+ describe(
14
+ repeat(LETTER).capture,
15
+ 'identifier',
16
+ )
17
+
18
+ FUNDEF =
19
+ seq(
20
+ describe(IDENTIFIER, 'identifier'),
21
+ eof,
22
+ )
23
+
24
+ PARSER =
25
+ alt(
26
+ string('Hello!'),
27
+ string('Hello, world!'),
28
+ string('Hello… ?'),
29
+ )
30
+
31
+ TESTB =
32
+ seq(
33
+ alt(
34
+ seq(string('a+a').capture),
35
+ seq(string('a').capture),
36
+ ),
37
+ eof,
38
+ )
39
+ end
40
+
41
+ puts MyGrammar::PARSER.apply('Hello, what?').pretty_message
42
+ puts
43
+ puts MyGrammar::FUNDEF.apply('foo3a').pretty_message
44
+ puts
45
+ puts MyGrammar::TESTB.apply('a+').pretty_message
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'd-parse'
4
+ require 'pp'
5
+
6
+ data = <<EOS
7
+ add(1, mul(2, 3))
8
+ mul(2,3)
9
+ EOS
10
+
11
+ include DParse::DSL
12
+
13
+ # primitives
14
+ digit = describe(alt(*('0'..'9').map { |c| char(c) }), 'digit')
15
+ letter = describe(alt(*('a'..'z').map { |c| char(c) }), 'letter')
16
+ lparen = char('(')
17
+ rparen = char(')')
18
+ comma = char(',')
19
+
20
+ # basic
21
+ identifier = describe(seq(letter, repeat(letter)).capture, 'identifier')
22
+ lit = seq(digit, repeat(digit)).capture.map { |d| d.to_i(10) }
23
+ eof_with_opt_nl = seq(opt(char("\n")), eof)
24
+ whitespace = repeat(whitespace_char)
25
+
26
+ # complex
27
+ funcall = nil
28
+ expression = alt(lazy { funcall }, lit)
29
+ arglist_tail = repeat(seq(comma, whitespace, expression).map { |d| d[2] })
30
+ arglist = seq(expression, arglist_tail).map { |d| [d[0]] + d[1] }
31
+ funcall = seq(identifier, lparen, arglist, rparen).map { |d| [d[0]] + d[2] }
32
+ program = seq(intersperse(expression, char("\n")).select_even, eof_with_opt_nl).first
33
+
34
+ def eval_expr(expr)
35
+ case expr
36
+ when Array
37
+ case expr[0]
38
+ when 'add'
39
+ eval_expr(expr[1]) + eval_expr(expr[2])
40
+ when 'mul'
41
+ eval_expr(expr[1]) * eval_expr(expr[2])
42
+ else
43
+ raise '???'
44
+ end
45
+ when Integer
46
+ expr
47
+ else
48
+ raise '???'
49
+ end
50
+ end
51
+
52
+ res = program.apply(data)
53
+ case res
54
+ when DParse::Success
55
+ exprs = res.data
56
+ pp exprs
57
+ exprs.each { |e| p eval_expr(e) }
58
+ when DParse::Failure
59
+ $stderr.puts res.pretty_message
60
+ exit 1
61
+ end
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'd-parse'
4
+ require 'pp'
5
+
6
+ json = DParse::Parsers::JSON.new
7
+
8
+ puts "[#{Time.now}] start"
9
+ res = json.apply($stdin.read)
10
+ puts "[#{Time.now}] stop"
11
+ puts
12
+
13
+ case res
14
+ when DParse::Success
15
+ pp res.data
16
+ when DParse::Failure
17
+ puts res.pretty_message
18
+ end
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'd-parse'
4
+
5
+ module Grammar
6
+ extend DParse::DSL
7
+
8
+ DIGIT = char_in('0'..'9')
9
+
10
+ ROOT =
11
+ seq(
12
+ intersperse(
13
+ repeat(DIGIT).capture.map { |d| d.to_i(10) },
14
+ char(',').ignore,
15
+ ).compact,
16
+ eof,
17
+ ).first
18
+ end
19
+
20
+ res = Grammar::ROOT.apply('1,2,100,582048,07,09')
21
+ case res
22
+ when DParse::Success
23
+ p res.data
24
+ when DParse::Failure
25
+ $stderr.puts res.pretty_message
26
+ exit 1
27
+ end
@@ -0,0 +1,36 @@
1
+ describe DParse::Failure do
2
+ let(:failure) { described_class.new("one\ntwo\nthree\nfour\nfive", position, origin: origin) }
3
+
4
+ let(:position) { DParse::Position.new(index: 13, line: 2, column: 3) }
5
+ let(:origin) { double(:origin, expectation_message: expectation_message) }
6
+ let(:expectation_message) { 'lols' }
7
+
8
+ describe '#full_message' do
9
+ subject { failure.full_message }
10
+ it { is_expected.to eql('expected lols at line 3, column 4') }
11
+
12
+ context 'no origin' do
13
+ let(:origin) { nil }
14
+ it { is_expected.to eql('expected ? at line 3, column 4') }
15
+ end
16
+ end
17
+
18
+ describe '#pretty_message' do
19
+ subject { failure.pretty_message }
20
+
21
+ context 'column 3' do
22
+ let(:position) { DParse::Position.new(index: 13, line: 2, column: 3) }
23
+ it { is_expected.to eql("expected lols at line 3, column 4\n\nthr\e[31me\e[0me\n\e[31m ↑\e[0m") }
24
+ end
25
+
26
+ context 'column 4' do
27
+ let(:position) { DParse::Position.new(index: 14, line: 2, column: 4) }
28
+ it { is_expected.to eql("expected lols at line 3, column 5\n\nthre\e[31me\e[0m\n\e[31m ↑\e[0m") }
29
+ end
30
+
31
+ context 'column 5' do
32
+ let(:position) { DParse::Position.new(index: 15, line: 2, column: 5) }
33
+ it { is_expected.to eql("expected lols at line 3, column 6\n\nthree\n\e[31m ↑\e[0m") }
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,77 @@
1
+ describe DParse::Parser do
2
+ context 'bare parser' do
3
+ let(:klass) do
4
+ Class.new(described_class) do
5
+ end
6
+ end
7
+
8
+ let(:parser) { klass.new }
9
+
10
+ it 'errors on #read' do
11
+ expect { parser.read('foo', :some_pos) }.to raise_error(NotImplementedError)
12
+ end
13
+
14
+ it 'errors on #inspect' do
15
+ expect { parser.inspect }.to raise_error(NotImplementedError)
16
+ end
17
+ end
18
+
19
+ context 'parser returning an array' do
20
+ let(:klass) do
21
+ Class.new(described_class) do
22
+ def initialize(result)
23
+ @result = result
24
+ end
25
+
26
+ def read(_input, _pos)
27
+ @result
28
+ end
29
+
30
+ def inspect
31
+ 'sample()'
32
+ end
33
+ end
34
+ end
35
+
36
+ let(:result) { DParse::Success.new('…', pos, data: [:a, :b, :c, :d, :e]) }
37
+ let(:pos) { DParse::Position.new(index: 0, line: 0, column: 0) }
38
+
39
+ let(:parser) { klass.new(result) }
40
+
41
+ describe '#first' do
42
+ subject { parser.first }
43
+ example { expect(subject).to parse('…').and_capture(:a) }
44
+ end
45
+
46
+ describe '#second' do
47
+ subject { parser.second }
48
+ example { expect(subject).to parse('…').and_capture(:b) }
49
+ end
50
+
51
+ describe '#select_even' do
52
+ subject { parser.select_even }
53
+ example { expect(subject).to parse('…').and_capture([:a, :c, :e]) }
54
+ end
55
+
56
+ describe '#select_odd' do
57
+ subject { parser.select_odd }
58
+ example { expect(subject).to parse('…').and_capture([:b, :d]) }
59
+ end
60
+
61
+ describe '#match?' do
62
+ subject { parser.match?('…') }
63
+
64
+ let(:pos) { DParse::Position.new(index: 0, line: 0, column: 0) }
65
+
66
+ context 'failure' do
67
+ let(:result) { DParse::Failure.new('…', pos) }
68
+ it { is_expected.to equal(false) }
69
+ end
70
+
71
+ context 'success' do
72
+ let(:result) { DParse::Success.new('…', pos) }
73
+ it { is_expected.to equal(true) }
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,48 @@
1
+ describe DParse::Parsers::Alt do
2
+ let(:a) { DParse::Parsers::String.new('Hello') }
3
+ let(:b) { DParse::Parsers::String.new('Goodbye') }
4
+ let(:parser) { described_class.new(a, b) }
5
+
6
+ example { expect(parser).to parse('Hello').up_to(5).line(0).column(5) }
7
+ example { expect(parser).to parse('Goodbye').up_to(7).line(0).column(7) }
8
+
9
+ example { expect(parser).not_to parse('').and_fail_at(0).line(0).column(0).with_failure('expected \'H\'') }
10
+ example { expect(parser).not_to parse('Wilkommen').and_fail_at(0).line(0).column(0).with_failure('expected \'H\'') }
11
+ example { expect(parser).not_to parse('Hallo').and_fail_at(1).line(0).column(1).with_failure('expected \'e\'') }
12
+ example { expect(parser).not_to parse('Hellenistic period').and_fail_at(4).line(0).column(4).with_failure('expected \'o\'') }
13
+
14
+ describe '#inspect' do
15
+ subject { parser.inspect }
16
+
17
+ let(:a) { DParse::Parsers::Char.new('a') }
18
+ let(:b) { DParse::Parsers::Char.new('b') }
19
+
20
+ it { is_expected.to eql('alt(char("a"),char("b"))') }
21
+ end
22
+
23
+ context 'ambiguous parsers' do
24
+ let(:a) { double('parser') }
25
+ let(:b) { double('parser') }
26
+
27
+ before do
28
+ expect(a).to receive(:read).and_return(DParse::Failure.new('…', DParse::Position.new(index: 10, line: 0, column: 10)))
29
+ expect(b).to receive(:read).and_return(DParse::Failure.new('…', DParse::Position.new(index: 20, line: 0, column: 20)))
30
+ end
31
+
32
+ context 'shortest first' do
33
+ let(:parser) { described_class.new(a, b) }
34
+
35
+ it 'picks the most specific failure' do
36
+ expect(parser).not_to parse('…').and_fail_at(20).line(0).column(20)
37
+ end
38
+ end
39
+
40
+ context 'longest first' do
41
+ let(:parser) { described_class.new(b, a) }
42
+
43
+ it 'picks the most specific failure' do
44
+ expect(parser).not_to parse('…').and_fail_at(20).line(0).column(20)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,15 @@
1
+ describe DParse::Parsers::Any do
2
+ let(:parser) { described_class.new }
3
+
4
+ example { expect(parser).to parse('a').up_to(1).line(0).column(1) }
5
+ example { expect(parser).to parse('b').up_to(1).line(0).column(1) }
6
+ example { expect(parser).to parse('ab').up_to(1).line(0).column(1) }
7
+
8
+ example { expect(parser).not_to parse('').and_fail_at(0).line(0).column(0).with_failure('expected any character except end of file') }
9
+
10
+ describe '#inspect' do
11
+ subject { parser.inspect }
12
+
13
+ it { is_expected.to eql('any()') }
14
+ end
15
+ end
@@ -0,0 +1,31 @@
1
+ describe DParse::Parsers::Bind do
2
+ let(:a) { DParse::Parsers::Char.new('a') }
3
+ let(:b) { DParse::Parsers::Char.new('b') }
4
+
5
+ let(:init) { DParse::Parsers::Alt.new(a, b).capture }
6
+
7
+ let(:parser) do
8
+ parser_map = {
9
+ 'a' => DParse::Parsers::Char.new('0'),
10
+ 'b' => DParse::Parsers::Char.new('1'),
11
+ }
12
+
13
+ init.bind do |data|
14
+ new_parser = parser_map[data]
15
+ new_parser.map { |d| [data, d] }
16
+ end
17
+ end
18
+
19
+ example { expect(parser).to parse('a0').up_to(2).line(0).column(2) }
20
+ example { expect(parser).to parse('b1').up_to(2).line(0).column(2) }
21
+
22
+ example { expect(parser).not_to parse('').and_fail_at(0).line(0).column(0) }
23
+ example { expect(parser).not_to parse('a').and_fail_at(1).line(0).column(1) }
24
+ example { expect(parser).not_to parse('aa').and_fail_at(1).line(0).column(1) }
25
+ example { expect(parser).not_to parse('ab').and_fail_at(1).line(0).column(1) }
26
+ example { expect(parser).not_to parse('a1').and_fail_at(1).line(0).column(1) }
27
+ example { expect(parser).not_to parse('b').and_fail_at(1).line(0).column(1) }
28
+ example { expect(parser).not_to parse('ba').and_fail_at(1).line(0).column(1) }
29
+ example { expect(parser).not_to parse('bb').and_fail_at(1).line(0).column(1) }
30
+ example { expect(parser).not_to parse('b0').and_fail_at(1).line(0).column(1) }
31
+ end
@@ -0,0 +1,11 @@
1
+ describe DParse::Parsers::Capturing do
2
+ let(:any) { DParse::Parsers::Any.new }
3
+ let(:parser) { described_class.new(any) }
4
+
5
+ example { expect(parser).to parse('a').up_to(1).line(0).column(1).and_capture('a') }
6
+ example { expect(parser).to parse('aa').up_to(1).line(0).column(1).and_capture('a') }
7
+ example { expect(parser).to parse('ab').up_to(1).line(0).column(1).and_capture('a') }
8
+ example { expect(parser).to parse('ba').up_to(1).line(0).column(1).and_capture('b') }
9
+
10
+ example { expect(parser).not_to parse('').and_fail_at(0).line(0).column(0) }
11
+ end
@@ -0,0 +1,22 @@
1
+ describe DParse::Parsers::CharIn do
2
+ let(:parser) { described_class.new(%w(a b)) }
3
+
4
+ example { expect(parser).to parse('a').up_to(1) }
5
+ example { expect(parser).to parse('b').up_to(1) }
6
+ example { expect(parser).to parse('ab').up_to(1) }
7
+ example { expect(parser).to parse('ac').up_to(1) }
8
+ example { expect(parser).to parse('ba').up_to(1) }
9
+ example { expect(parser).to parse('bc').up_to(1) }
10
+
11
+ example { expect(parser).not_to parse('').and_fail_at(0).line(0).column(0) }
12
+ example { expect(parser).not_to parse('c').and_fail_at(0).line(0).column(0) }
13
+ example { expect(parser).not_to parse('d').and_fail_at(0).line(0).column(0) }
14
+ example { expect(parser).not_to parse('ca').and_fail_at(0).line(0).column(0) }
15
+ example { expect(parser).not_to parse('db').and_fail_at(0).line(0).column(0) }
16
+
17
+ describe '#inspect' do
18
+ subject { parser.inspect }
19
+
20
+ it { is_expected.to eql('alt(char("a"),char("b"))') }
21
+ end
22
+ end