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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 32be46d729318aafa59078974eeee88c3b8021cf4cff85c2a17d8786ff0a3908
4
- data.tar.gz: 6de98652e7c7583db727a647a213a5d48af9faf0106234e34b61871003fee036
3
+ metadata.gz: 11f34766383a2831c29bcbc2185872fdbe6dc1eb3e6394c9322054c8e6f7e67a
4
+ data.tar.gz: '078fce739e80aa0a9e2f14d39739ac1c6c1626fa3df529036a0d4f2d5e298bfc'
5
5
  SHA512:
6
- metadata.gz: 7efb7a5d6fdb13b628e80d7f7876065970f41160f21651572fb535fcac1e4ef05fcbee3a850dbd4f31f23a81a665c728a9f52a869067028f8bc4e1eba8c06034
7
- data.tar.gz: 0c0d5ec9eb9930d17bc04fe709d88de42885cccc4c7aee875d2e0749eccee01bbe5404af5f15c5bba3ca8246b7309deb57b35c1f28cef5c9b3883d83e6a164ad
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
- private
273
- def _debug(parser, name: nil)
274
- Parser.new(parser.name) do |input|
275
- puts "debugging #{name || parser.name}: #{input.inspect}"
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
@@ -2,7 +2,7 @@
2
2
 
3
3
  module OOPeg
4
4
  module Version
5
- VERSION = "0.1.0"
5
+ VERSION = "0.1.1"
6
6
  end
7
7
  end
8
8
  # SPDX-License-Identifier: AGPL-3.0-or-later
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.0
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: '["A peg parser not using custom sytnax but exposing an OO based API"]'
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