oo_peg 0.1.0 → 0.1.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 +4 -4
- data/lib/oo_peg/input.rb +3 -1
- data/lib/oo_peg/parser/combinators/basics.rb +45 -0
- data/lib/oo_peg/parser/combinators/mappers.rb +70 -0
- data/lib/oo_peg/parser/combinators/repeaters.rb +107 -0
- data/lib/oo_peg/parser/combinators/satisfiers.rb +35 -0
- data/lib/oo_peg/parser/combinators/selectors.rb +36 -0
- data/lib/oo_peg/parser/combinators.rb +81 -184
- data/lib/oo_peg/parser.rb +5 -1
- data/lib/oo_peg/parsers/advanced/list_parser.rb +40 -0
- data/lib/oo_peg/parsers/advanced.rb +21 -0
- data/lib/oo_peg/parsers/base_parsers.rb +3 -3
- data/lib/oo_peg/version.rb +1 -1
- data/lib/oo_peg.rb +1 -1
- metadata +9 -3
- data/lib/oo_peg/parsers/lispy_parser.rb +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 11f34766383a2831c29bcbc2185872fdbe6dc1eb3e6394c9322054c8e6f7e67a
|
4
|
+
data.tar.gz: '078fce739e80aa0a9e2f14d39739ac1c6c1626fa3df529036a0d4f2d5e298bfc'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e4f45d6b634aef946eea657ee0dd47b795c36f4368fff764947851ed46ab3fa7c8a5de5ed8a24eca05e4d452ff3ad73653dd9e04c80554165657c52dd517bf9d
|
7
|
+
data.tar.gz: 41d5e123bc0ea70b589bcfa2d45f93cc6e3741a36123ed39e4ad5ee338665972e2192df55b21b38d160b8d0af38041773cd93118b943ad73d84a9322da3ad8fa
|
data/lib/oo_peg/input.rb
CHANGED
@@ -5,7 +5,7 @@ module OOPeg
|
|
5
5
|
attr_reader :content, :pos
|
6
6
|
|
7
7
|
def advance(by=1)
|
8
|
-
self.class.new(content.drop(by), pos+by)
|
8
|
+
[self.class.new(content.drop(by), pos+by), take(by)]
|
9
9
|
end
|
10
10
|
|
11
11
|
def empty? = content.empty?
|
@@ -17,6 +17,8 @@ module OOPeg
|
|
17
17
|
"#{content.take(count-3).join}..."
|
18
18
|
end
|
19
19
|
|
20
|
+
def take(count=1) = content.take(count)
|
21
|
+
|
20
22
|
def ==(other)
|
21
23
|
self.class === other &&
|
22
24
|
other.content == content &&
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OOPeg
|
4
|
+
class Parser
|
5
|
+
module Combinators
|
6
|
+
module Basics
|
7
|
+
private
|
8
|
+
def _debug(parser, name: nil)
|
9
|
+
Parser.new(parser.name) do |input|
|
10
|
+
puts "debugging #{name || parser.name}: #{input.inspect}"
|
11
|
+
result = Parser.parse(parser, input)
|
12
|
+
puts "debugging #{name || parser.name}: #{result}"
|
13
|
+
result
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def _lookahead(parsers, name:)
|
18
|
+
parser = _select(parsers)
|
19
|
+
Parser.new(name || "lookahead(#{parser.name})") do |input, name|
|
20
|
+
case Parser.parse(parser, input)
|
21
|
+
in {ok: true}
|
22
|
+
{ok: true, ast: nil, input:}
|
23
|
+
in error
|
24
|
+
error
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def _not(parser:, name:)
|
30
|
+
name ||= "not(#{parser.name})"
|
31
|
+
Parser.new(name) do |input|
|
32
|
+
case Parser.parse(parser, input)
|
33
|
+
in {ok: false}
|
34
|
+
input.advance => [input, ast]
|
35
|
+
Result.ok(ast:, input:)
|
36
|
+
else
|
37
|
+
Result.nok(input:, error: "not expected to parse #{input.show(6)}", parser_name: name)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OOPeg
|
4
|
+
class Parser
|
5
|
+
module Combinators
|
6
|
+
module Mappers
|
7
|
+
|
8
|
+
private
|
9
|
+
def _join_list(parser:, name:)
|
10
|
+
name ||= "joined_list(#{parser.name})"
|
11
|
+
_map(parser:, name:) do
|
12
|
+
it => [head, tail]
|
13
|
+
[head, *tail]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def _map(parser:, name:, &mapper)
|
18
|
+
raise ArgumentError, "missing mapper function" unless mapper
|
19
|
+
|
20
|
+
name ||= "map(#{parser.name})"
|
21
|
+
Parser.new(name) do |input|
|
22
|
+
result = Parser.parse(parser, input)
|
23
|
+
# require "debug"; binding.break
|
24
|
+
result.map(&mapper)
|
25
|
+
# require "debug"; binding.break
|
26
|
+
# case result
|
27
|
+
# in {ok: true, input: rest, ast:}
|
28
|
+
# Result.ok(ast: mapper.(ast), input: rest)
|
29
|
+
# in {error:}
|
30
|
+
# Result.nok(error:, input:, parser_name: name)
|
31
|
+
# end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def _map_many(parser:, name:, &mapper)
|
36
|
+
raise ArgumentError, "missing mapper function" unless mapper
|
37
|
+
|
38
|
+
name ||= "map_many(#{parser.name})"
|
39
|
+
_map(parser:, name:) { it.map(&mapper) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def _map_or_rename(parser:, name: nil, error: nil, &mapper)
|
43
|
+
parser_name = name || parser.name
|
44
|
+
Parser.new(name) do |input|
|
45
|
+
result = Parser.parse(parser, input)
|
46
|
+
case result
|
47
|
+
in {ok: true, ast:}
|
48
|
+
mapper ? result.merge(ast: mapper.(ast)) : result
|
49
|
+
in _
|
50
|
+
result.merge(parser_name:, error:)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def _map_result(parser, name, mapper)
|
56
|
+
Parser.new(name) do |input, name|
|
57
|
+
result = Parser.parse(parser, input)
|
58
|
+
# require "debug"; binding.break
|
59
|
+
mapper.(result)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
def _tagged(tag, parser:, name:)
|
63
|
+
name ||= "tagged(#{parser.name} with #{tag.inspect})"
|
64
|
+
_map(parser:, name:) { [tag, it] }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OOPeg
|
4
|
+
class Parser
|
5
|
+
module Combinators
|
6
|
+
module Repeaters
|
7
|
+
private
|
8
|
+
def _many(parser, max:, min:, name:, &blk)
|
9
|
+
name ||= "many(#{parser.name})"
|
10
|
+
many_parser = Parser.new(name) { |input| _many_body(input:, parser:, name:, max:, min:) }
|
11
|
+
return many_parser.map_many(&blk) if blk
|
12
|
+
|
13
|
+
many_parser
|
14
|
+
end
|
15
|
+
|
16
|
+
def _many_body(input:, parser:, name:, max:, min:)
|
17
|
+
total_ast = []
|
18
|
+
original_input = input
|
19
|
+
current_input = input
|
20
|
+
match_count = 0
|
21
|
+
loop do
|
22
|
+
if current_input.empty?
|
23
|
+
break _many_when_empty(ast: total_ast, input: current_input, match_count:, min:, name:, original_input:)
|
24
|
+
end
|
25
|
+
|
26
|
+
# p [total_ast, current_input]
|
27
|
+
r = _many_parse(parser:, current_input:, total_ast:, match_count:, max:, min:, original_input:)
|
28
|
+
case r
|
29
|
+
in [:cont, {current_input:, total_ast:, match_count:}]
|
30
|
+
nil
|
31
|
+
in [:halt, result]
|
32
|
+
break result
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def _many_parse(parser:, current_input:, total_ast:, match_count:, max:, min:, original_input:)
|
38
|
+
case Parser.parse(parser, current_input)
|
39
|
+
in {ok: true, ast:, input:}
|
40
|
+
raise InfiniteLoop, "must not parse zero width inside many in parser: #{parser.name}" if input.pos == current_input.pos
|
41
|
+
current_input = input
|
42
|
+
total_ast = [*total_ast, ast]
|
43
|
+
match_count += 1
|
44
|
+
return [:halt, Result.ok(ast: total_ast, input:)] if max && match_count >= max
|
45
|
+
return [:cont, {current_input:, total_ast:, match_count:}]
|
46
|
+
in _
|
47
|
+
return [:halt, Result.ok(ast: total_ast, input: current_input)] if match_count >= min
|
48
|
+
return [:halt, Result.nok(
|
49
|
+
error: "many #{name} did not succeed the required #{min} times, but only #{match_count}",
|
50
|
+
input: original_input,
|
51
|
+
parser_name: name)]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def _many_when_empty(ast:, input:, match_count:, min:, name:, original_input:)
|
56
|
+
return Result.ok(ast:, input:) if match_count >= min
|
57
|
+
|
58
|
+
Result.nok(
|
59
|
+
error: "#{name.inspect} did not succeed the required #{min} times, but only #{match_count}",
|
60
|
+
input: original_input,
|
61
|
+
parser_name: name)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def _maybe(parser:, name: nil, replace_with: nil)
|
66
|
+
Parser.new(name || "maybe(#{parser.name})") do |input, name|
|
67
|
+
case Parser.parse(parser, input)
|
68
|
+
in {ok: false}
|
69
|
+
Result.ok(ast: replace_with, input:)
|
70
|
+
in success
|
71
|
+
success
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def _sequence(*parsers, name:)
|
77
|
+
parsers = parsers.flatten
|
78
|
+
raise ArgumentError, "all parsers must be instances of Parser" unless parsers.all? { Parser === it }
|
79
|
+
name ||= "seq(#{parsers.map(&:name).join(", ")})"
|
80
|
+
Parser.new(name) do |input|
|
81
|
+
original_input = input
|
82
|
+
result = parsers.reduce_while [input, []] do |(input, ast), parser|
|
83
|
+
|
84
|
+
parsed = Parser.parse(parser, input)
|
85
|
+
# require "debug"; binding.break if parsed.input.nil?
|
86
|
+
case parsed
|
87
|
+
in {ok: true, ast: nil, input:}
|
88
|
+
cont_reduce([input, ast])
|
89
|
+
in {ok: true, ast: ast_node, input:}
|
90
|
+
cont_reduce([input, [*ast, ast_node]])
|
91
|
+
in {ok: false, error:}
|
92
|
+
halt_reduce(Result.nok(input: original_input, error:, parser_name: name))
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
case result
|
97
|
+
in {ok: false} => error
|
98
|
+
result
|
99
|
+
in [input, ast]
|
100
|
+
Result.ok(ast:, input:)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OOPeg
|
4
|
+
class Parser
|
5
|
+
module Combinators
|
6
|
+
module Satisfiers
|
7
|
+
private
|
8
|
+
def _satisfy(parser, name:, &satisfier)
|
9
|
+
name ||= "satisfy(#{parser.name})"
|
10
|
+
|
11
|
+
Parser.new(name) do |input|
|
12
|
+
original_input = input
|
13
|
+
case Parser.parse(parser, input)
|
14
|
+
in {ok: false} => error
|
15
|
+
error
|
16
|
+
in {ok: true, ast:, input:} => result
|
17
|
+
case satisfier.(ast)
|
18
|
+
in true
|
19
|
+
result
|
20
|
+
in [:ok, ast]
|
21
|
+
Result.ok(ast:, input:)
|
22
|
+
in [:error, error]
|
23
|
+
Result.nok(input: original_input, error:, parser_name: name)
|
24
|
+
in _
|
25
|
+
Result.nok(input: original_input, error: "#{name} failed", parser_name: name)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OOPeg
|
4
|
+
class Parser
|
5
|
+
module Combinators
|
6
|
+
module Selectors
|
7
|
+
private
|
8
|
+
def _select(*parsers, name: nil)
|
9
|
+
parsers = parsers.flatten
|
10
|
+
name ||= "select(#{parsers.map(&:name).join(",")})"
|
11
|
+
raise ArgumentError, "all parsers must be instances of Parser" unless parsers.all? { Parser === it }
|
12
|
+
Parser.new(name, &_select_parser(name:, parsers:))
|
13
|
+
end
|
14
|
+
|
15
|
+
def _select_parser(name:, parsers:)
|
16
|
+
-> (input) do
|
17
|
+
result = Result.nok(error: "No parser matched in select named #{name}", input:, parser_name: name)
|
18
|
+
parsers.each do |parser|
|
19
|
+
# case p(Parser.parse(parser, input))
|
20
|
+
this_result = Parser.parse(parser, input)
|
21
|
+
# require "debug"; binding.break
|
22
|
+
case this_result
|
23
|
+
in {ok: true} => result
|
24
|
+
break result
|
25
|
+
in _
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
result
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
@@ -1,7 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative '../../enumerable'
|
4
|
+
require_relative 'combinators/basics'
|
4
5
|
require_relative 'combinators/lazy'
|
6
|
+
require_relative 'combinators/mappers'
|
7
|
+
require_relative 'combinators/repeaters'
|
8
|
+
require_relative 'combinators/satisfiers'
|
9
|
+
require_relative 'combinators/selectors'
|
5
10
|
module OOPeg
|
6
11
|
class Parser
|
7
12
|
class InfiniteLoop < Exception; end
|
@@ -77,6 +82,60 @@ module OOPeg
|
|
77
82
|
# parse(tagged_char_parser, 'q').ast => [:char, 'q']
|
78
83
|
#
|
79
84
|
#
|
85
|
+
# ==== Mapping Lists
|
86
|
+
#
|
87
|
+
# Oftentimes, when applying +map+ to the +many+ combinator we want to map
|
88
|
+
# *all* elements of the returned list, enter...
|
89
|
+
#
|
90
|
+
# ===== +map_many+
|
91
|
+
#
|
92
|
+
# # example: a digit parser
|
93
|
+
#
|
94
|
+
# digit_parser = char_class_parser(:digit).many.map_many(&:to_i)
|
95
|
+
#
|
96
|
+
# parse(digit_parser, '104a').ast => [1, 0, 4]
|
97
|
+
#
|
98
|
+
# A shortcut, using the +mapper+ inside +many+
|
99
|
+
#
|
100
|
+
# # example: a shortcut digit parser
|
101
|
+
#
|
102
|
+
# digit_parser = char_class_parser(:digit).many(&:to_i)
|
103
|
+
#
|
104
|
+
# parse(digit_parser, '104a').ast => [1, 0, 4]
|
105
|
+
#
|
106
|
+
# ==== +join_list+
|
107
|
+
#
|
108
|
+
# The inherent structure of this library will oftentimes return a list of the following structure
|
109
|
+
# <tt>[first_element, [second_element, ..., last_element]]</tt>
|
110
|
+
#
|
111
|
+
# e.g.
|
112
|
+
#
|
113
|
+
# # example: the crux with +and+ and +many+
|
114
|
+
#
|
115
|
+
# element = ws_parser.not.joined
|
116
|
+
# at_least_one = element.and(ws_parser.ignore.and(element).many(&:first))
|
117
|
+
#
|
118
|
+
# parse(at_least_one, 'a b c').ast => ['a', ['b', 'c']]
|
119
|
+
#
|
120
|
+
# This can be fixed with +join_list+
|
121
|
+
#
|
122
|
+
# # example: fixing +and+ and +many+
|
123
|
+
#
|
124
|
+
# element = ws_parser.not.joined
|
125
|
+
# at_least_one = element.and(ws_parser.ignore.and(element).many(&:first)).join_list
|
126
|
+
#
|
127
|
+
# parse(at_least_one, 'a b c').ast => ['a', 'b', 'c']
|
128
|
+
#
|
129
|
+
# *N.B* This is a very specialized combinator which expects the AST it transforms being of the shape
|
130
|
+
# <tt>[_, [*]]</tt> if this is not the case a +NoMatchingPatternError+ will be thrown
|
131
|
+
#
|
132
|
+
# # example: Just be careful man
|
133
|
+
#
|
134
|
+
# bad_parser = char_parser.join_list
|
135
|
+
#
|
136
|
+
# expect { parse(bad_parser, 'a') }
|
137
|
+
# .to raise_error(NoMatchingPatternError)
|
138
|
+
#
|
80
139
|
# ==== +maybe+ (oftentimes called +option+ in other PEG Parsers)
|
81
140
|
#
|
82
141
|
# If a parser +p+ parses a string, the parser +p.maybe+ parses, well
|
@@ -168,6 +227,23 @@ module OOPeg
|
|
168
227
|
#
|
169
228
|
# parse(parser, "+42").ast => ['42']
|
170
229
|
#
|
230
|
+
# ==== +not+ negation
|
231
|
+
#
|
232
|
+
# # example: what is not a character
|
233
|
+
#
|
234
|
+
# parse(char_class_parser(:digit).not.joined, 'a').ast => 'a'
|
235
|
+
# parse(char_class_parser(:digit).not, '7') not! ok
|
236
|
+
#
|
237
|
+
# Practical use of not is when it is combined with +many+.
|
238
|
+
#
|
239
|
+
# Like in regular expressions where <tt>\S</tt> is oftentimes combined to <tt>\S+</tt>
|
240
|
+
#
|
241
|
+
# # example: not whitespaces
|
242
|
+
#
|
243
|
+
# not_ws_parser = ws_parser.not.many(min: 1).joined
|
244
|
+
#
|
245
|
+
# parse(not_ws_parser, ' ') not! ok
|
246
|
+
# parse(not_ws_parser, 't-32 ').ast => 't-32'
|
171
247
|
#
|
172
248
|
# ==== +satifsfy+ can fail a successful result does not touch failing results
|
173
249
|
#
|
@@ -268,190 +344,11 @@ module OOPeg
|
|
268
344
|
# $ab_count => 3
|
269
345
|
#
|
270
346
|
module Combinators
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
result = Parser.parse(parser, input)
|
277
|
-
puts "debugging #{name || parser.name}: #{result}"
|
278
|
-
result
|
279
|
-
end
|
280
|
-
end
|
281
|
-
|
282
|
-
def _lookahead(parsers, name:)
|
283
|
-
parser = _select(parsers)
|
284
|
-
Parser.new(name || "lookahead(#{parser.name})") do |input, name|
|
285
|
-
case Parser.parse(parser, input)
|
286
|
-
in {ok: true}
|
287
|
-
{ok: true, ast: nil, input:}
|
288
|
-
in error
|
289
|
-
error
|
290
|
-
end
|
291
|
-
end
|
292
|
-
end
|
293
|
-
|
294
|
-
def _map(parser:, name:, &mapper)
|
295
|
-
raise ArgumentError, "missing mapper function" unless mapper
|
296
|
-
|
297
|
-
name ||= "map(#{parser.name})"
|
298
|
-
Parser.new(name) do |input|
|
299
|
-
result = Parser.parse(parser, input)
|
300
|
-
# require "debug"; binding.break
|
301
|
-
result.map(&mapper)
|
302
|
-
# require "debug"; binding.break
|
303
|
-
# case result
|
304
|
-
# in {ok: true, input: rest, ast:}
|
305
|
-
# Result.ok(ast: mapper.(ast), input: rest)
|
306
|
-
# in {error:}
|
307
|
-
# Result.nok(error:, input:, parser_name: name)
|
308
|
-
# end
|
309
|
-
end
|
310
|
-
end
|
311
|
-
|
312
|
-
def _many(parser, max:, min:, name:)
|
313
|
-
name ||= "many(#{parser.name})"
|
314
|
-
Parser.new(name) do |input|
|
315
|
-
total_ast = []
|
316
|
-
original_input = input
|
317
|
-
current_input = input
|
318
|
-
match_count = 0
|
319
|
-
loop do
|
320
|
-
if current_input.empty?
|
321
|
-
break Result.ok(ast: total_ast, input:) if match_count >= min
|
322
|
-
break Result.nok(
|
323
|
-
error: "#{name.inspect} did not succeed the required #{min} times, but only #{match_count}",
|
324
|
-
input: original_input,
|
325
|
-
parser_name: name)
|
326
|
-
end
|
327
|
-
|
328
|
-
case Parser.parse(parser, current_input)
|
329
|
-
in {ok: true, ast:, input:}
|
330
|
-
raise InfiniteLoop, "must not parse zero width inside many in parser: #{parser.name}" if input.pos == current_input.pos
|
331
|
-
current_input = input
|
332
|
-
total_ast = [*total_ast, ast]
|
333
|
-
match_count += 1
|
334
|
-
break Result.ok(ast: total_ast, input:) if max && match_count >= max
|
335
|
-
in _
|
336
|
-
break Result.ok(ast: total_ast, input:) if match_count >= min
|
337
|
-
break Result.nok(
|
338
|
-
error: "many #{name} did not succeed the required #{min} times, but only #{match_count}",
|
339
|
-
input: original_input,
|
340
|
-
parser_name: name)
|
341
|
-
end
|
342
|
-
end
|
343
|
-
end
|
344
|
-
end
|
345
|
-
|
346
|
-
def _map_or_rename(parser:, name: nil, error: nil, &mapper)
|
347
|
-
parser_name = name || parser.name
|
348
|
-
Parser.new(name) do |input|
|
349
|
-
result = Parser.parse(parser, input)
|
350
|
-
case result
|
351
|
-
in {ok: true, ast:}
|
352
|
-
mapper ? result.merge(ast: mapper.(ast)) : result
|
353
|
-
in _
|
354
|
-
result.merge(parser_name:, error:)
|
355
|
-
end
|
356
|
-
end
|
357
|
-
end
|
358
|
-
|
359
|
-
def _map_result(parser, name, mapper)
|
360
|
-
Parser.new(name) do |input, name|
|
361
|
-
result = Parser.parse(parser, input)
|
362
|
-
# require "debug"; binding.break
|
363
|
-
mapper.(result)
|
364
|
-
end
|
365
|
-
end
|
366
|
-
|
367
|
-
def _maybe(parser:, name: nil, replace_with: nil)
|
368
|
-
Parser.new(name || "maybe(#{parser.name})") do |input, name|
|
369
|
-
case Parser.parse(parser, input)
|
370
|
-
in {ok: false}
|
371
|
-
Result.ok(ast: replace_with, input:)
|
372
|
-
in success
|
373
|
-
success
|
374
|
-
end
|
375
|
-
end
|
376
|
-
end
|
377
|
-
|
378
|
-
def _satisfy(parser, name:, &satisfier)
|
379
|
-
name ||= "satisfy(#{parser.name})"
|
380
|
-
|
381
|
-
Parser.new(name) do |input|
|
382
|
-
original_input = input
|
383
|
-
case Parser.parse(parser, input)
|
384
|
-
in {ok: false} => error
|
385
|
-
error
|
386
|
-
in {ok: true, ast:, input:} => result
|
387
|
-
case satisfier.(ast)
|
388
|
-
in true
|
389
|
-
result
|
390
|
-
in [:ok, ast]
|
391
|
-
Result.ok(ast:, input:)
|
392
|
-
in [:error, error]
|
393
|
-
Result.nok(input: original_input, error:, parser_name: name)
|
394
|
-
in _
|
395
|
-
Result.nok(input: original_input, error: "#{name} failed", parser_name: name)
|
396
|
-
end
|
397
|
-
end
|
398
|
-
end
|
399
|
-
end
|
400
|
-
|
401
|
-
def _select(*parsers, name: nil)
|
402
|
-
parsers = parsers.flatten
|
403
|
-
name ||= "select(#{parsers.map(&:name).join(",")})"
|
404
|
-
raise ArgumentError, "all parsers must be instances of Parser" unless parsers.all? { Parser === it }
|
405
|
-
Parser.new(name || "select #{parsers.map(&:name).join(", ")}") do |input, name|
|
406
|
-
result = Result.nok(error: "No parser matched in select named #{name}", input:, parser_name: name)
|
407
|
-
parsers.each do |parser|
|
408
|
-
# case p(Parser.parse(parser, input))
|
409
|
-
this_result = Parser.parse(parser, input)
|
410
|
-
# require "debug"; binding.break
|
411
|
-
case this_result
|
412
|
-
in {ok: true} => result
|
413
|
-
break result
|
414
|
-
in _
|
415
|
-
nil
|
416
|
-
end
|
417
|
-
end
|
418
|
-
result
|
419
|
-
end
|
420
|
-
end
|
421
|
-
|
422
|
-
def _sequence(*parsers, name:)
|
423
|
-
parsers = parsers.flatten
|
424
|
-
raise ArgumentError, "all parsers must be instances of Parser" unless parsers.all? { Parser === it }
|
425
|
-
name ||= "seq(#{parsers.map(&:name).join(", ")})"
|
426
|
-
Parser.new(name) do |input|
|
427
|
-
original_input = input
|
428
|
-
result = parsers.reduce_while [input, []] do |(input, ast), parser|
|
429
|
-
# require "debug"; binding.break
|
430
|
-
parsed = Parser.parse(parser, input)
|
431
|
-
# p parsed
|
432
|
-
case parsed
|
433
|
-
in {ok: true, ast: nil, input:}
|
434
|
-
cont_reduce([input, ast])
|
435
|
-
in {ok: true, ast: ast_node, input:}
|
436
|
-
cont_reduce([input, [*ast, ast_node]])
|
437
|
-
in {ok: false, error:}
|
438
|
-
halt_reduce(Result.nok(input: original_input, error:, parser_name: name))
|
439
|
-
end
|
440
|
-
end
|
441
|
-
|
442
|
-
case result
|
443
|
-
in {ok: false} => error
|
444
|
-
result
|
445
|
-
in [input, ast]
|
446
|
-
Result.ok(ast:, input:)
|
447
|
-
end
|
448
|
-
end
|
449
|
-
end
|
450
|
-
|
451
|
-
def _tagged(tag, parser:, name:)
|
452
|
-
name ||= "tagged(#{parser.name} with #{tag.inspect})"
|
453
|
-
_map(parser:, name:) { [tag, it] }
|
454
|
-
end
|
347
|
+
include Combinators::Basics
|
348
|
+
include Combinators::Mappers
|
349
|
+
include Combinators::Repeaters
|
350
|
+
include Combinators::Satisfiers
|
351
|
+
include Combinators::Selectors
|
455
352
|
end
|
456
353
|
end
|
457
354
|
end
|
data/lib/oo_peg/parser.rb
CHANGED
@@ -45,15 +45,19 @@ module OOPeg
|
|
45
45
|
|
46
46
|
def ignore = self.map { nil }
|
47
47
|
|
48
|
+
def join_list(name: nil) = _join_list(parser: self, name:)
|
48
49
|
def joined(joiner: "") = self.map { |ast| ast.join(joiner) }
|
49
50
|
|
50
51
|
def lookahead(name: nil) = _lookahead(self, name:)
|
51
52
|
|
52
|
-
def many(name: nil, max: nil, min: 0) = _many(self, name:, max:, min
|
53
|
+
def many(name: nil, max: nil, min: 0, &blk) = _many(self, name:, max:, min:, &blk)
|
53
54
|
def map(name: nil, &blk) = _map(parser: self, name:, &blk)
|
55
|
+
def map_many(name: nil, &blk) = _map_many(parser: self, name:, &blk)
|
54
56
|
def map_or_rename(name: nil, error: nil, &blk) = _map_or_rename(parser: self, name:, error:, &blk)
|
55
57
|
def maybe(name: nil, replace_with: nil) = _maybe(parser: self, name:, replace_with:)
|
56
58
|
|
59
|
+
def not(name: nil) = _not(parser: self, name:)
|
60
|
+
|
57
61
|
def or(*parsers, name: nil) = _select(self, *parsers, name:)
|
58
62
|
|
59
63
|
def satisfy(name: nil, &satisfier) = _satisfy(self, name:, &satisfier)
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OOPeg
|
4
|
+
module Parsers
|
5
|
+
module Advanced
|
6
|
+
module ListParser extend self
|
7
|
+
include OOPeg::Parsers
|
8
|
+
include Advanced
|
9
|
+
|
10
|
+
def make(
|
11
|
+
element_parser: ws_parser.not.many(min: 1).joined,
|
12
|
+
sep_parser: ws_parser,
|
13
|
+
leading_sep: true,
|
14
|
+
trailing_sep: true,
|
15
|
+
name: nil)
|
16
|
+
name ||= "ListParser"
|
17
|
+
|
18
|
+
head_parser = leading_sep ? sep_parser.maybe.ignore : nil
|
19
|
+
tail_parser = trailing_sep ? sep_parser.maybe.ignore : nil
|
20
|
+
|
21
|
+
inner_parser =
|
22
|
+
element_parser.and(sep_parser.and(element_parser).many(&:first)).join_list
|
23
|
+
|
24
|
+
Parser.and([head_parser, inner_parser, tail_parser].compact, name:).map(&:first)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def _flatten_ast
|
30
|
+
-> ast do
|
31
|
+
ast.first => [head, tail]
|
32
|
+
[head, *tail]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative '../parsers'
|
4
|
+
require_relative 'advanced/list_parser'
|
4
5
|
require_relative 'advanced/operator_parser'
|
5
6
|
require_relative 'advanced/sexp_parser'
|
6
7
|
require_relative 'advanced/string_parser'
|
@@ -53,6 +54,24 @@ module OOPeg
|
|
53
54
|
#
|
54
55
|
# parse(symbol_parser, ':hello ').ast => :hello
|
55
56
|
#
|
57
|
+
# == The List Parser
|
58
|
+
#
|
59
|
+
# Lists are omnipresent in computer languages, they are easily defined as a parser and
|
60
|
+
# many sequences of a separator and a parser.
|
61
|
+
#
|
62
|
+
# They can be configured to accept leading and trailing separators too.
|
63
|
+
#
|
64
|
+
# The default configuration is with the +separator_parser+ being the +ws_parser+
|
65
|
+
# and the +element_parser+ being the negated +ws_parser+, both with a minimum
|
66
|
+
# length of 1.
|
67
|
+
#
|
68
|
+
# The +leading_sep+ and +trailing_sep+ flags are set to +true+ by default.
|
69
|
+
#
|
70
|
+
# # example: the default list_parser
|
71
|
+
#
|
72
|
+
# parse(list_parser, " a b 21 \n:hello").ast => ["a", "b", "21", ":hello"]
|
73
|
+
#
|
74
|
+
#
|
56
75
|
# == Putting it all together: The SexpParser
|
57
76
|
#
|
58
77
|
# with all the above and all the other predefined parsers it is now very easy
|
@@ -104,6 +123,8 @@ module OOPeg
|
|
104
123
|
#
|
105
124
|
module Advanced
|
106
125
|
|
126
|
+
def list_parser(**kwds) = ListParser.make(**kwds)
|
127
|
+
|
107
128
|
def operator_parser(**kwds) = OperatorParser.make(**kwds)
|
108
129
|
|
109
130
|
def sexp_parser(**kwds) = SexpParser.make(**kwds)
|
@@ -141,7 +141,7 @@ module OOPeg
|
|
141
141
|
Result.nok(error: "unexpected end of input", input:, parser_name:)
|
142
142
|
in [h, *]
|
143
143
|
if set.member?(h) && !negate || !set.member?(h) && negate
|
144
|
-
Result.ok(ast: h, input: input.advance)
|
144
|
+
Result.ok(ast: h, input: input.advance.first)
|
145
145
|
else
|
146
146
|
Result.nok(input:, error: "#{h} is not member of the required set #{set}", parser_name: name)
|
147
147
|
end
|
@@ -198,7 +198,7 @@ module OOPeg
|
|
198
198
|
Result.nok(error: "unexpected end of input", parser_name: name, input:)
|
199
199
|
in [h, *]
|
200
200
|
if rgx.match?(h)
|
201
|
-
Result.ok(ast: h, input: input.advance)
|
201
|
+
Result.ok(ast: h, input: input.advance.first)
|
202
202
|
else
|
203
203
|
Result.nok(input:, parser_name: name, error: "#{h} does not match the char class: :#{char_class}")
|
204
204
|
end
|
@@ -215,7 +215,7 @@ module OOPeg
|
|
215
215
|
Result.nok(error: "unexpected end of input", input:, parser_name: name)
|
216
216
|
in [h, *]
|
217
217
|
if rgx.match?(h)
|
218
|
-
Result.ok(ast: h, input: input.advance)
|
218
|
+
Result.ok(ast: h, input: input.advance.first)
|
219
219
|
else
|
220
220
|
Result.nok(input:, error: "#{h} does not match the char class: :#{char_classes}")
|
221
221
|
end
|
data/lib/oo_peg/version.rb
CHANGED
data/lib/oo_peg.rb
CHANGED
@@ -189,7 +189,7 @@ require_relative 'oo_peg/parser'
|
|
189
189
|
# in [a, b, *]
|
190
190
|
# if a.to_i < b.to_i
|
191
191
|
# # it is important to advance the input
|
192
|
-
# R.ok(ast: "#{a.to_i} < #{b.to_i}", input: input.advance(2))
|
192
|
+
# R.ok(ast: "#{a.to_i} < #{b.to_i}", input: input.advance(2).first)
|
193
193
|
# else
|
194
194
|
# R.nok(input:, error: "not rising", parser_name: "my parser")
|
195
195
|
# end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: oo_peg
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Dober
|
@@ -52,16 +52,21 @@ files:
|
|
52
52
|
- lib/oo_peg/parser.rb
|
53
53
|
- lib/oo_peg/parser/class_methods.rb
|
54
54
|
- lib/oo_peg/parser/combinators.rb
|
55
|
+
- lib/oo_peg/parser/combinators/basics.rb
|
55
56
|
- lib/oo_peg/parser/combinators/lazy.rb
|
57
|
+
- lib/oo_peg/parser/combinators/mappers.rb
|
58
|
+
- lib/oo_peg/parser/combinators/repeaters.rb
|
59
|
+
- lib/oo_peg/parser/combinators/satisfiers.rb
|
60
|
+
- lib/oo_peg/parser/combinators/selectors.rb
|
56
61
|
- lib/oo_peg/parsers.rb
|
57
62
|
- lib/oo_peg/parsers/advanced.rb
|
63
|
+
- lib/oo_peg/parsers/advanced/list_parser.rb
|
58
64
|
- lib/oo_peg/parsers/advanced/operator_parser.rb
|
59
65
|
- lib/oo_peg/parsers/advanced/sexp_parser.rb
|
60
66
|
- lib/oo_peg/parsers/advanced/string_parser.rb
|
61
67
|
- lib/oo_peg/parsers/advanced/symbol_parser.rb
|
62
68
|
- lib/oo_peg/parsers/base_parsers.rb
|
63
69
|
- lib/oo_peg/parsers/common_parsers.rb
|
64
|
-
- lib/oo_peg/parsers/lispy_parser.rb
|
65
70
|
- lib/oo_peg/parsers/pseudo_parsers.rb
|
66
71
|
- lib/oo_peg/parsers/true_set.rb
|
67
72
|
- lib/oo_peg/result.rb
|
@@ -70,6 +75,7 @@ homepage: https://codeberg.org/lab419/oo_peg
|
|
70
75
|
licenses:
|
71
76
|
- AGPL-3.0-or-later
|
72
77
|
metadata:
|
78
|
+
documentation_uri: https://rubydoc.info/gems/oo_peg/0.1.1
|
73
79
|
homepage_uri: https://codeberg.org/lab419/oo_peg
|
74
80
|
rdoc_options: []
|
75
81
|
require_paths:
|
@@ -87,5 +93,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
87
93
|
requirements: []
|
88
94
|
rubygems_version: 3.6.8
|
89
95
|
specification_version: 4
|
90
|
-
summary:
|
96
|
+
summary: A peg parser not using custom sytnax but exposing an OO based API
|
91
97
|
test_files: []
|
@@ -1,18 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'advanced'
|
4
|
-
module OOPeg
|
5
|
-
module Parsers
|
6
|
-
##
|
7
|
-
#
|
8
|
-
# A highly configurable parser for s-expressions
|
9
|
-
class LispyParser
|
10
|
-
|
11
|
-
private
|
12
|
-
def initialize(prefix: "([", suffix: "])", sep_parser: ws_parser)
|
13
|
-
end
|
14
|
-
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|