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.
- checksums.yaml +7 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +104 -0
- data/Guardfile +3 -0
- data/LICENSE +19 -0
- data/NEWS.md +0 -0
- data/README.md +137 -0
- data/Rakefile +14 -0
- data/d-parse.gemspec +26 -0
- data/lib/d-parse.rb +10 -0
- data/lib/d-parse/dsl.rb +71 -0
- data/lib/d-parse/failure.rb +46 -0
- data/lib/d-parse/parser.rb +102 -0
- data/lib/d-parse/parsers.rb +26 -0
- data/lib/d-parse/parsers/combinators/alt.rb +32 -0
- data/lib/d-parse/parsers/combinators/repeat.rb +42 -0
- data/lib/d-parse/parsers/combinators/seq.rb +49 -0
- data/lib/d-parse/parsers/highlevel/char_in.rb +13 -0
- data/lib/d-parse/parsers/highlevel/intersperse.rb +18 -0
- data/lib/d-parse/parsers/highlevel/json.rb +237 -0
- data/lib/d-parse/parsers/highlevel/opt.rb +16 -0
- data/lib/d-parse/parsers/highlevel/string.rb +13 -0
- data/lib/d-parse/parsers/highlevel/whitespace_char.rb +15 -0
- data/lib/d-parse/parsers/modifiers/capturing.rb +13 -0
- data/lib/d-parse/parsers/modifiers/describe.rb +28 -0
- data/lib/d-parse/parsers/modifiers/ignore.rb +17 -0
- data/lib/d-parse/parsers/modifiers/lazy.rb +18 -0
- data/lib/d-parse/parsers/modifiers/map.rb +24 -0
- data/lib/d-parse/parsers/primitives/any.rb +22 -0
- data/lib/d-parse/parsers/primitives/bind.rb +25 -0
- data/lib/d-parse/parsers/primitives/char.rb +27 -0
- data/lib/d-parse/parsers/primitives/char_not.rb +27 -0
- data/lib/d-parse/parsers/primitives/char_not_in.rb +30 -0
- data/lib/d-parse/parsers/primitives/eof.rb +21 -0
- data/lib/d-parse/parsers/primitives/except.rb +33 -0
- data/lib/d-parse/parsers/primitives/fail.rb +17 -0
- data/lib/d-parse/parsers/primitives/succeed.rb +13 -0
- data/lib/d-parse/position.rb +31 -0
- data/lib/d-parse/success.rb +35 -0
- data/lib/d-parse/version.rb +3 -0
- data/samples/parse-bind +25 -0
- data/samples/parse-csv +19 -0
- data/samples/parse-errortest +45 -0
- data/samples/parse-fun +61 -0
- data/samples/parse-json +18 -0
- data/samples/parse-readme +27 -0
- data/spec/d-parse/failure_spec.rb +36 -0
- data/spec/d-parse/parser_spec.rb +77 -0
- data/spec/d-parse/parsers/alt_spec.rb +48 -0
- data/spec/d-parse/parsers/any_spec.rb +15 -0
- data/spec/d-parse/parsers/bind_spec.rb +31 -0
- data/spec/d-parse/parsers/capture_spec.rb +11 -0
- data/spec/d-parse/parsers/char_in_spec.rb +22 -0
- data/spec/d-parse/parsers/char_not_in_spec.rb +23 -0
- data/spec/d-parse/parsers/char_not_spec.rb +16 -0
- data/spec/d-parse/parsers/char_spec.rb +22 -0
- data/spec/d-parse/parsers/describe_spec.rb +22 -0
- data/spec/d-parse/parsers/end_of_input_spec.rb +20 -0
- data/spec/d-parse/parsers/except_spec.rb +20 -0
- data/spec/d-parse/parsers/fail_spec.rb +12 -0
- data/spec/d-parse/parsers/intersperse_spec.rb +18 -0
- data/spec/d-parse/parsers/json_spec.rb +69 -0
- data/spec/d-parse/parsers/lazy_spec.rb +16 -0
- data/spec/d-parse/parsers/map_spec.rb +54 -0
- data/spec/d-parse/parsers/optional_spec.rb +16 -0
- data/spec/d-parse/parsers/or_spec.rb +26 -0
- data/spec/d-parse/parsers/repeat_spec.rb +40 -0
- data/spec/d-parse/parsers/sequence_spec.rb +52 -0
- data/spec/d-parse/parsers/string_spec.rb +19 -0
- data/spec/d-parse/parsers/succeed_spec.rb +12 -0
- data/spec/d-parse/parsers/whitespace_char_spec.rb +14 -0
- data/spec/spec_helper.rb +97 -0
- 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,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
|