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,16 @@
1
+ module DParse
2
+ module Parsers
3
+ class Opt < DParse::Parser
4
+ def self.new(parser)
5
+ DParse::Parsers::Alt.new(
6
+ parser,
7
+ DParse::Parsers::Succeed.new,
8
+ )
9
+ end
10
+
11
+ def initialize(*)
12
+ raise ArgumentError, "#{self.class} is not supposed to be initialized"
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ module DParse
2
+ module Parsers
3
+ class String < DParse::Parser
4
+ def self.new(string)
5
+ DParse::Parsers::Seq.new(*string.chars.map { |c| DParse::Parsers::Char.new(c) })
6
+ end
7
+
8
+ def initialize(*)
9
+ raise ArgumentError, "#{self.class} is not supposed to be initialized"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ module DParse
2
+ module Parsers
3
+ class WhitespaceChar < DParse::Parser
4
+ WS = [' ', "\t"].freeze
5
+
6
+ def self.new
7
+ DParse::Parsers::CharIn.new(WS)
8
+ end
9
+
10
+ def initialize(*)
11
+ raise ArgumentError, "#{self.class} is not supposed to be initialized"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ module DParse
2
+ module Parsers
3
+ class Capturing < DParse::Parser
4
+ def self.new(parser)
5
+ parser.map { |_data, result, old_pos| result.input[old_pos.index...result.pos.index] }
6
+ end
7
+
8
+ def initialize(*)
9
+ raise ArgumentError, "#{self.class} is not supposed to be initialized"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,28 @@
1
+ module DParse
2
+ module Parsers
3
+ class Describe < DParse::Parser
4
+ def initialize(parser, name)
5
+ @parser = parser
6
+ @name = name
7
+ end
8
+
9
+ def read(input, pos)
10
+ res = @parser.read(input, pos)
11
+ case res
12
+ when DParse::Success
13
+ res
14
+ when DParse::Failure
15
+ Failure.new(res.input, res.pos, origin: self)
16
+ end
17
+ end
18
+
19
+ def inspect
20
+ "#{@name}()"
21
+ end
22
+
23
+ def expectation_message
24
+ @name
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ module DParse
2
+ module Parsers
3
+ class Ignore < DParse::Parser
4
+ def initialize(parser)
5
+ @parser = parser
6
+ end
7
+
8
+ def read(input, pos)
9
+ @parser.read(input, pos).map { |_| nil }
10
+ end
11
+
12
+ def inspect
13
+ "ignore(#{@parser.inspect})"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ module DParse
2
+ module Parsers
3
+ class Lazy < DParse::Parser
4
+ def initialize(&block)
5
+ raise ArgumentError, 'Expected block' unless block_given?
6
+ @block = block
7
+ end
8
+
9
+ def read(input, pos)
10
+ @block.call.read(input, pos)
11
+ end
12
+
13
+ def inspect
14
+ 'lazy(?)'
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,24 @@
1
+ module DParse
2
+ module Parsers
3
+ class Map < DParse::Parser
4
+ def initialize(parser, &block)
5
+ @parser = parser
6
+ @block = block
7
+ end
8
+
9
+ def read(input, pos)
10
+ res = @parser.read(input, pos)
11
+ case res
12
+ when Success
13
+ Success.new(input, res.pos, data: @block.call(res.data, res, pos), best_failure: res.best_failure)
14
+ when Failure
15
+ res
16
+ end
17
+ end
18
+
19
+ def inspect
20
+ "map(#{@parser}, <proc>)"
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,22 @@
1
+ module DParse
2
+ module Parsers
3
+ class Any < DParse::Parser
4
+ def read(input, pos)
5
+ char = input[pos.index]
6
+ if char
7
+ Success.new(input, pos.advance(char))
8
+ else
9
+ Failure.new(input, pos, origin: self)
10
+ end
11
+ end
12
+
13
+ def inspect
14
+ 'any()'
15
+ end
16
+
17
+ def expectation_message
18
+ 'any character except end of file'
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,25 @@
1
+ module DParse
2
+ module Parsers
3
+ class Bind < DParse::Parser
4
+ def initialize(parser, &block)
5
+ @parser = parser
6
+ @block = block
7
+ end
8
+
9
+ def read(input, pos)
10
+ res = @parser.read(input, pos)
11
+ case res
12
+ when Success
13
+ other_parser = @block.call(res.data)
14
+ other_parser.read(input, res.pos)
15
+ when Failure
16
+ res
17
+ end
18
+ end
19
+
20
+ def inspect
21
+ "bind(#{@parser}, <proc>)"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,27 @@
1
+ module DParse
2
+ module Parsers
3
+ class Char < DParse::Parser
4
+ def initialize(char)
5
+ raise ArgumentError, 'Expected input to have one char' unless char.length == 1
6
+ @char = char
7
+ end
8
+
9
+ def read(input, pos)
10
+ char = input[pos.index]
11
+ if char == @char
12
+ Success.new(input, pos.advance(char))
13
+ else
14
+ Failure.new(input, pos, origin: self)
15
+ end
16
+ end
17
+
18
+ def inspect
19
+ "char(#{@char.inspect})"
20
+ end
21
+
22
+ def expectation_message
23
+ display(@char)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ module DParse
2
+ module Parsers
3
+ class CharNot < DParse::Parser
4
+ def initialize(char)
5
+ raise ArgumentError, 'Expected input to have one char' unless char.length == 1
6
+ @char = char
7
+ end
8
+
9
+ def read(input, pos)
10
+ char = input[pos.index]
11
+ if char != @char && char
12
+ Success.new(input, pos.advance(char))
13
+ else
14
+ Failure.new(input, pos, origin: self)
15
+ end
16
+ end
17
+
18
+ def inspect
19
+ "char_not(#{@char.inspect})"
20
+ end
21
+
22
+ def expectation_message
23
+ "any character not equal to #{display(@char)}"
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,30 @@
1
+ module DParse
2
+ module Parsers
3
+ class CharNotIn < DParse::Parser
4
+ def initialize(chars)
5
+ unless chars.all? { |char| char.length == 1 }
6
+ raise ArgumentError, 'Expected input to have one char'
7
+ end
8
+
9
+ @chars = chars
10
+ end
11
+
12
+ def read(input, pos)
13
+ char = input[pos.index]
14
+ if char && @chars.all? { |c| char != c }
15
+ Success.new(input, pos.advance(char))
16
+ else
17
+ Failure.new(input, pos, origin: self)
18
+ end
19
+ end
20
+
21
+ def inspect
22
+ "char_not_in(#{@chars.inspect})"
23
+ end
24
+
25
+ def expectation_message
26
+ "any character not in #{@chars.map { |c| display(c) }.join(', ')}"
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,21 @@
1
+ module DParse
2
+ module Parsers
3
+ class EOF < DParse::Parser
4
+ def read(input, pos)
5
+ if input.size == pos.index
6
+ Success.new(input, pos)
7
+ else
8
+ Failure.new(input, pos, origin: self)
9
+ end
10
+ end
11
+
12
+ def inspect
13
+ 'eof()'
14
+ end
15
+
16
+ def expectation_message
17
+ 'end of input'
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,33 @@
1
+ module DParse
2
+ module Parsers
3
+ class Except < DParse::Parser
4
+ def initialize(parser, bad_parser)
5
+ @parser = parser
6
+ @bad_parser = bad_parser
7
+ end
8
+
9
+ def read(input, pos)
10
+ res = @parser.read(input, pos)
11
+ case res
12
+ when Success
13
+ bad_res = @bad_parser.read(input, pos)
14
+ if bad_res.is_a?(Success) && bad_res.pos.index == res.pos.index
15
+ Failure.new(input, pos, origin: self)
16
+ else
17
+ res
18
+ end
19
+ when Failure
20
+ res
21
+ end
22
+ end
23
+
24
+ def inspect
25
+ "except(#{@parser.inspect}, #{@bad_parser.inspect})"
26
+ end
27
+
28
+ def expectation_message
29
+ @parser.expectation_message + ', not ' + @bad_parser.expectation_message
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,17 @@
1
+ module DParse
2
+ module Parsers
3
+ class Fail < DParse::Parser
4
+ def read(input, pos)
5
+ Failure.new(input, pos, origin: self)
6
+ end
7
+
8
+ def inspect
9
+ 'fail()'
10
+ end
11
+
12
+ def expectation_message
13
+ 'nothing (always fail)'
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ module DParse
2
+ module Parsers
3
+ class Succeed < DParse::Parser
4
+ def read(input, pos)
5
+ Success.new(input, pos)
6
+ end
7
+
8
+ def inspect
9
+ 'succeed()'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,31 @@
1
+ module DParse
2
+ class Position
3
+ attr_reader :index
4
+ attr_reader :line
5
+ attr_reader :column
6
+
7
+ def initialize(index: 0, line: 0, column: 0)
8
+ @index = index
9
+ @line = line
10
+ @column = column
11
+ end
12
+
13
+ FAR_BEHIND = new(index: -1, line: -1, column: -1)
14
+
15
+ def advance(char)
16
+ Position.new(
17
+ index: @index + 1,
18
+ line: char == "\n" ? @line + 1 : @line,
19
+ column: char == "\n" ? 0 : @column + 1,
20
+ )
21
+ end
22
+
23
+ def to_s
24
+ "line #{@line + 1}, column #{@column + 1}"
25
+ end
26
+
27
+ def inspect
28
+ "Pos(#{@index}; #{@line}:#{@column})"
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,35 @@
1
+ module DParse
2
+ class Success
3
+ attr_reader :input
4
+ attr_reader :pos
5
+ attr_reader :data
6
+ attr_reader :best_failure
7
+
8
+ def initialize(input, pos, data: nil, best_failure: nil)
9
+ @input = input
10
+ @pos = pos
11
+ @data = data
12
+ @best_failure = best_failure
13
+ end
14
+
15
+ def map
16
+ self.class.new(@input, @pos, data: yield(@data), best_failure: @best_failure)
17
+ end
18
+
19
+ def with_best_failure(failure)
20
+ self.class.new(@input, @pos, data: @data, best_failure: failure)
21
+ end
22
+
23
+ def to_s
24
+ "Success(#{@pos}; #{@data}#{@best_failure ? '; best failure = ' + best_failure.inspect : ''})"
25
+ end
26
+
27
+ def success?
28
+ true
29
+ end
30
+
31
+ def inspect
32
+ to_s
33
+ end
34
+ end
35
+ end