l43_peg 0.1.9 → 0.2.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +34 -279
  3. data/lib/l43_peg/box.rb +27 -0
  4. data/lib/l43_peg/combinators/boxed.rb +16 -0
  5. data/lib/l43_peg/combinators/choice.rb +30 -0
  6. data/lib/l43_peg/combinators/debug_parser.rb +5 -5
  7. data/lib/l43_peg/combinators/ignore.rb +12 -0
  8. data/lib/l43_peg/combinators/lazy.rb +20 -0
  9. data/lib/l43_peg/combinators/many.rb +26 -21
  10. data/lib/l43_peg/combinators/map.rb +26 -0
  11. data/lib/l43_peg/combinators/map_error.rb +24 -0
  12. data/lib/l43_peg/combinators/satisfy.rb +36 -0
  13. data/lib/l43_peg/combinators/sequence.rb +41 -0
  14. data/lib/l43_peg/combinators.rb +62 -73
  15. data/lib/l43_peg/errors.rb +11 -0
  16. data/lib/l43_peg/failure.rb +17 -1
  17. data/lib/l43_peg/input.rb +12 -10
  18. data/lib/l43_peg/mappers.rb +21 -3
  19. data/lib/l43_peg/parser.rb +4 -8
  20. data/lib/l43_peg/parsers/char_parser.rb +48 -8
  21. data/lib/l43_peg/parsers/end_parser.rb +13 -12
  22. data/lib/l43_peg/parsers/eol_parser.rb +55 -0
  23. data/lib/l43_peg/parsers/escape_parser.rb +21 -0
  24. data/lib/l43_peg/parsers/failure_parser.rb +2 -2
  25. data/lib/l43_peg/parsers/int_parser.rb +9 -7
  26. data/lib/l43_peg/parsers/list_parser.rb +33 -0
  27. data/lib/l43_peg/parsers/literal_parser.rb +23 -0
  28. data/lib/l43_peg/parsers/name_parser.rb +13 -0
  29. data/lib/l43_peg/parsers/rgx_parser.rb +35 -20
  30. data/lib/l43_peg/parsers/string_parser.rb +26 -0
  31. data/lib/l43_peg/parsers/success_parser.rb +21 -0
  32. data/lib/l43_peg/parsers/symbol_parser.rb +19 -0
  33. data/lib/l43_peg/parsers/ws_parser.rb +20 -0
  34. data/lib/l43_peg/parsers.rb +50 -24
  35. data/lib/l43_peg/stop.rb +7 -7
  36. data/lib/l43_peg/success.rb +10 -12
  37. data/lib/l43_peg/version.rb +1 -1
  38. data/lib/l43_peg.rb +10 -29
  39. metadata +37 -13
  40. data/lib/l43_peg/cache.rb +0 -9
  41. data/lib/l43_peg/combinators/sel.rb +0 -24
  42. data/lib/l43_peg/combinators/seq.rb +0 -29
  43. data/lib/l43_peg/helper.rb +0 -13
  44. data/lib/l43_peg/parsers/rgx_tokenizer.rb +0 -24
  45. data/lib/l43_peg/parsers/sel_rgx_parser.rb +0 -25
  46. data/lib/l43_peg/parsers/token_parser.rb +0 -32
  47. data/lib/l43_peg/parsers/tokens_parser.rb +0 -63
  48. data/lib/l43_peg/parsers/verb_parser.rb +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9e988304f4b2f29b9d0fdc15008e8cc0bdf1b9bd2407d5563a7a8d0f88ff805e
4
- data.tar.gz: 6d8c8edca204793d062ce30dc2ee22f8f3a95faee74779e62beb4a9d90ef4234
3
+ metadata.gz: 12367798c13e0e131a3d2b0acd28db7c272fd9c25f46715e0ec1a6eb415e0eb5
4
+ data.tar.gz: e57b47017a6af939d130f6178326e3fe90965c7ccd3dadc0308b0f54fba9abe5
5
5
  SHA512:
6
- metadata.gz: 5884815166a138bf01163af5b0782d83cac9ac97571cbcb9cc35a92dcc54dacef96fa5f5b97049c0d39b0585fa71e6c50a04d91459889c64a17a7858ac9bc199
7
- data.tar.gz: 0c709f631fba65dcf86e172aba3b4fc3743d8d0a246089a361a0954a1abaaad69b24fa3de78e73ae62cb7ed3c150f950b7925cdafea2a8157c80d7b8720090a1
6
+ metadata.gz: e28f4a426cf654af9376932587cbb4252f4279392d09d0cc3e94f5d197e1cffc1eaa5134243ade86a3ae3632ced45aa453afccbffc57eca6eb23cda38dc88bb6
7
+ data.tar.gz: 94d10a4621e0ab446b338d15b1a694c045b77728673a1c503aa7627c6b4069d5d62eeb61992023e28bd7c098ca3e9b718e88dc65f7c942bddf1e454bb2b2fd3f
data/README.md CHANGED
@@ -4,311 +4,66 @@
4
4
 
5
5
  ## A Parse Expression Grammar library for Ruby
6
6
 
7
- ### This Version (v0.1.x) is Alpha Quality (many PEG features are missing, like recursion and even alternatives.
7
+ ## Table Of Contents
8
+ - [Quick Start:](#quick-start-table-of-contents)
9
+ - [Built In Parsers...](#built-in-parsers-table-of-contents)
10
+ - [Concepts...](#concepts-table-of-contents)
11
+ - [Combinators...](#combinators-table-of-contents)
12
+ - [Author](#author-table-of-contents)
13
+ - [LICENSE](#license-table-of-contents)
14
+ <!-- END TOC -->
8
15
 
9
- It is however released because it offers quite some nice parsing of ARGV which shall be demonstrated by the following
10
- [speculations](https://rubygems.org/gems/speculate_about)
11
-
12
- See [README_spec.rb](spec/speculations/README_spec.rb) for the generated code for details
13
-
14
- ### Context: `arg_parser`
15
-
16
- Given the following argument specification
17
-
18
- ```ruby
19
- include L43Peg::Combinators
20
- let :args_spec do
21
- {
22
- start: "--start=(.*)",
23
- end: "(?:--end|-e)=(.*)",
24
- kwd: "--(alpha|beta|gamma)"
25
- }
26
- end
27
- ```
28
-
29
- And the assoicated parser
16
+ # Quick Start: [^](#table-of-contents)
30
17
 
18
+ Given we import everyting into our namespace
31
19
  ```ruby
32
- let(:parser) { args_parser(args_spec) }
20
+ require 'l43_peg'
21
+ Kernel.include L43Peg # not the same as RSpec's include
33
22
  ```
34
23
 
35
- Then we can parse some input
36
-
37
- ```ruby
38
- assert_parse_success(parser, %w[--start=42 --beta -e=44], ast: {start: "42", kwd: "beta", end: "44"}, rest: [])
39
- ```
40
-
41
- And we can get the rest in a list of tokens
42
-
24
+ Then we can use the parse function to parse a string or an instance of `Input`
43
25
  ```ruby
44
- assert_parse_success(parser, %w[--start=42 --beta -e=44 -s=not_an_arg --end=too_late], ast: {start: "42", kwd: "beta", end: "44"}, rest: %w[-s=not_an_arg --end=too_late])
45
- ```
46
-
47
- Also note that multiple values are passed into an array
48
-
49
- ```ruby
50
- input = %w[--end=42 --beta -e=44 --beta --end=not_too_late --gamma]
51
- ast = {end: %w[42 44 not_too_late], kwd: %w[beta beta gamma]}
52
- assert_parse_success(parser, input, ast:, rest: [])
53
- ```
54
-
55
- #### Context: Postprocessing
56
-
57
- When we map the parser
58
-
59
- ```ruby
60
- let :int_args do
61
- {
62
- start: "--start=(.*)",
63
- end: "--end=(.*)",
64
- inc: "--inc=(.*)"
65
- }
66
- end
67
- let(:int_arg_parser) {args_parser(int_args, name: "int parser", &:to_i)}
68
- ```
69
-
70
- Then we can convert the string valus
71
-
72
- ```ruby
73
- assert_parse_success(int_arg_parser, %w[--start=42 --end=44 --inc=2], ast: {start: 42, end: 44, inc: 2}, rest: [])
74
- ```
75
-
76
- #### Context: Knowing When To Stop
77
-
78
- An argument parser that respects itself provides a means to _end_ argument parsing even if more matches follow.
79
- An exmaple for that is the posix argument `--`
80
-
81
- We can use whatever we want in `args_parser`, here is a variation:
82
-
83
- Given the specification
84
-
85
- ```ruby
86
- let :args do
87
- {
88
- width: "w:(\\d+)",
89
- height: "h:(\\d+)",
90
- __stop: "(::)"
91
- }
92
- end
93
- let(:wh_parser) {args_parser(args, stop: :__stop, &:to_i)}
26
+ parse("a", with: char_parser) => Success
27
+ parse("a", with: char_parser("b")) => Failure
94
28
  ```
95
29
 
96
- Then parsing the following input
97
-
98
- ```ruby
99
- input = %w[h:42 w:73 :: w:74]
100
- ast = {height: 42, width: 73}
101
- assert_parse_success(wh_parser, input, ast:, rest: %w[w:74])
102
- ```
103
-
104
- ### Context: User Interface
105
-
106
- #### Context: Exposing the args_parser
107
-
108
- Above we have seen that we had to include an internal module so to get access to the `args_parser`.
109
- Client code might not want to use these intrusive methods and therefore the parsers are also exposed
110
- as module methods
111
-
112
- Given an _exposed_ `args_parser`
30
+ And parsers that do not need any arguments can be passed in as symbols
113
31
  ```ruby
114
- let :parser do
115
- L43Peg::Parsers.args_parser(
116
- {
117
- negative: "(-\\d+)",
118
- positive: "\\+?(\\d+)"
119
- },
120
- &:to_i
121
- )
122
- end
32
+ parse("a", with: :char_parser) => :ok, "a", _
123
33
  ```
124
34
 
125
- But we are also not interested in the internal representation of success and failure of parsing which was
126
- used in the speculations above. Nor do we want to transform our input into the internal representations
127
- as was done above by the helpers. (If you need to see the details of this you can inspect the
128
- file [`parser_test.rb` in `spec/support`](spec/support/parser_test.rb))
129
-
130
- Then we can uses the interface of `L43Peg`
131
-
35
+ And we can deconstruct the result into a status, ast and the rest `Input` instance.
132
36
  ```ruby
133
- L43Peg.parse_tokens(parser, %w[43 -44 +45]) => :ok, result
134
- expect(result).to eq(positive: [43, 45], negative: -44)
37
+ parse("a", with: char_parser) => :ok, "a", {src: ""}
38
+ parse("ab", with: char_parser) => :ok, "a", {src: "b"}
39
+ parse(Input.new(src: "ab"), with: char_parser) => :ok, "a", {src: "b"}
135
40
  ```
136
-
137
- And if we get an error the result is as follows
138
-
41
+ And we can also deconstruct it into a `Hash` which provides even more information
139
42
  ```ruby
140
- parser = L43Peg::Parsers.char_parser('a')
141
- L43Peg.parse_string(parser, 'b') => :error, message
142
- expect(message).to eq("char \"b\"")
43
+ parse("+42", with: int_parser) => {ok: true, ast: 42, parsed: "+42"}
143
44
  ```
144
45
 
145
- ## Context: Regexp Parser
146
-
147
- The basic concept is the `rgx_parser`
46
+ ## Complete Documentation
148
47
 
149
- Given a `rgx_parser` for an identifier
150
- ```ruby
151
- include L43Peg::Parsers
152
- let(:id_parser) { rgx_parser("[[:alpha:]][_[:alnum:]]*") }
153
- ```
48
+ ### Built In Parsers... [^](#table-of-contents)
154
49
 
155
- Then we can parse strings that start as such
156
- ```ruby
157
- assert_parse_success(id_parser, "l43_peg", ast: "l43_peg")
158
- ```
50
+ ...are described [here](speculations/built_in.md)
159
51
 
160
- And we can discard some input from the ast with the aid of captures
161
- ```ruby
162
- sym_parser = rgx_parser(":([[:alpha:]][_[:alnum:]]*)")
163
- assert_parse_success(sym_parser, ":no_colon", ast: "no_colon")
164
- ```
165
-
166
- But it can also fail
167
- ```ruby
168
- reason = "input does not match /\\A[[:alpha:]][_[:alnum:]]*/ (in rgx_parser(\"[[:alpha:]][_[:alnum:]]*\"))"
169
- assert_parse_failure(id_parser, "42", reason:)
170
- ```
52
+ ### Concepts... [^](#table-of-contents)
171
53
 
172
- #### Context: Warnings on empty matches
54
+ ...are described [here](speculations/concepts.md)
173
55
 
174
- Oftentimes bugs in PEG parsing are caused by zero width matches, while this is quite obvious with the `many` and
175
- `opt` or `maybe` combinators (**N.B.** they are not yet implemented, use `many(max: 1)` instead)
176
- and they common use patterns with these combinators are safe.
177
-
178
- However regular expression parsing might hide zero width matches, and that's whey they will trigger a warning by default
179
-
180
- Given an empty match rgex parser
181
- ```ruby
182
- let(:empty_parser) { rgx_parser("a*") }
183
- ```
184
-
185
- Then we get a warning when matching an empty string
186
- ```ruby
187
- expect { assert_parse_success(empty_parser, "", ast: "") }
188
- .to output("Warning, parser rgx_parser(\"a*\") succeeds with empty match\n").to_stderr
189
- ```
190
-
191
- However this behavior can also be disabled
192
-
193
- And therefore
194
- ```ruby
195
- parser = rgx_parser("a*", warn_on_empty: false)
196
- expect { assert_parse_success(parser, "", ast: "") }
197
- .not_to output.to_stderr
198
- ```
199
-
200
-
201
- ### Context: Tokenize Strings with Regexen
202
-
203
- Now we can use a list of `rgx_parsers` to _tokenize_ a string (in the same way can use `tokens_parser` to
204
- quantify elements of an array, but with dynamic bounds)
205
-
206
- Given some regexen
207
- ```ruby
208
- let :regexen do
209
- [
210
- [:verb, "<<", nil, ->(*){ [:verb, "<"] }],
211
- [:verb, "\\$(\\$)"],
212
- [:color_and_style, "<(.+?),(.+?)>", :all],
213
- [:color, "<(.+?)>", 1],
214
- [:reset, "\\$"],
215
- [:verb, "[^<$]+"],
216
- ]
217
- end
218
- let(:tokenizer) { L43Peg::Combinators.rgx_tokenize(regexen) }
219
- ```
220
-
221
- Then we can tokenize some inputs
222
- ```ruby
223
- input = "<red,bold>HELLO$and<<<green>$$<reset>"
224
- ast = [
225
- [:color_and_style, ["<red,bold>", "red", "bold"]],
226
- [:verb, "HELLO"],
227
- [:reset, "$"],
228
- [:verb, "and"],
229
- [:verb, "<"],
230
- [:color, "green"],
231
- [:verb, "$"],
232
- [:color, "reset"]
233
- ]
234
- assert_parse_success(tokenizer, input, ast:)
235
- ```
236
-
237
- ### Context: Debugging
238
-
239
- As parsers are by design imbricated functions debugging is not always simple.
240
- Enter the `debug_parser`, a parser that _debugs_ parsers by not changing their behavior
241
- by displaying more or less detailed information
242
-
243
- Given a parser
244
- ```ruby
245
- include L43Peg::Combinators
246
- let :args do
247
- {
248
- lat: "lat:(\\d+)",
249
- long: "long:(\\d+)",
250
- }
251
- end
252
- let(:geo_parser) {args_parser(args, &:to_i)}
253
- ```
254
-
255
- #### Context: Minimum level of information
256
-
257
- Given a minum debug parser
258
- ```ruby
259
- let(:debugger) {debug_parser(geo_parser, level: :min)}
260
- ```
261
-
262
- Then we will get some output
263
- ```ruby
264
- expected =
265
- "Tokens<[\"lat:43\", \"long:2\"]>\nSuccess: @1\n"
266
- expect { parsed_success(debugger, ["lat:43", "long:2"]) }
267
- .to output(expected).to_stderr
268
- ```
269
-
270
- #### Context: Default level of information
271
- Given a default debug parser
272
- ```ruby
273
- let(:debugger) {debug_parser(char_parser("a"))}
274
- ```
275
-
276
- Then we will get some output on errors
277
- ```ruby
278
- expected ="Input<\"b\"@1:1>\nFailure: char \"b\" @[1, 1]\n"
279
- expect { parsed_failure(debugger, "b") }
280
- .to output(expected).to_stderr
281
- ```
282
-
283
- #### Context: Maximum level of information
284
- Given a maxium level parser
285
- ```ruby
286
- let(:max_debugger) { debug_parser(char_parser("b"), level: :max) }
287
- ```
288
-
289
- Then we will get this output on errors
290
- ```ruby
291
- expected =
292
- [
293
- "================================================================================",
294
- 'Input<col:1 input:"bc" lnb:1 context:{}>',
295
- "================================================================================",
296
- 'Success<ast:"b" cache:{} rest:"c">',
297
- "================================================================================",
298
- ""
299
- ].join("\n")
300
-
301
- expect { parsed_success(max_debugger,"bc") }
302
- .to output(expected).to_stderr
303
- ```
56
+ ### Combinators... [^](#table-of-contents)
304
57
 
58
+ ... which are the working horses of any PEG library
59
+ are described [here](speculations/combinators.md)
305
60
 
306
- # Author
61
+ # Author [^](#table-of-contents)
307
62
 
308
- Copyright © 2024 Robert Dober
63
+ Copyright © 202[4,5,6] Robert Dober
309
64
  robert.dober@gmail.com
310
65
 
311
- # LICENSE
66
+ # LICENSE [^](#table-of-contents)
312
67
 
313
68
  GNU AFFERO GENERAL PUBLIC LICENSE, Version 3, 19 November 2007. Please refer to [LICENSE](LICENSE) for details.
314
69
 
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+
5
+ module Box
6
+ attr_reader :value
7
+
8
+ def self.make(class_name)
9
+ klass = Object.const_get(class_name) if Object.const_defined?(class_name)
10
+ raise NotABox, "#{class_name} is already defined but not a Box!" if klass && !(klass < self)
11
+ klass = Object.const_set(class_name, Class.new.include(self)) unless klass
12
+ klass
13
+ end
14
+
15
+ def deconstruct_keys(*) = {class: self, value:}
16
+
17
+ private
18
+ def initialize(value, &blk)
19
+ @value = blk ? blk.(value) : value
20
+ end
21
+ end
22
+
23
+
24
+ module BoxFactory extend self
25
+ end
26
+ end
27
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ module Combinators
5
+ module Boxed extend self
6
+ def boxed(parser, box, name: nil, &mapper)
7
+ name ||= "boxed(#parser.name})"
8
+ Combinators.map(parser, name:) { |ast|
9
+ ast = mapper.(ast) if mapper
10
+ Mappers.box(box, ast)
11
+ }
12
+ end
13
+ end
14
+ end
15
+ end
16
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ module Combinators
5
+ module Choice extend self
6
+ def choice(*parsers, name:)
7
+ name ||= "option(#{parsers.flatten.map(&:name).join(", ")})"
8
+ parser = Parser.new(name) {|input, _name=nil| _choice(input:, name:, parsers: parsers.flatten)}
9
+ end
10
+
11
+ private
12
+ def _choice(input:, name:, parsers:)
13
+ # **N.B.** Cache lookup would be great here to see if some
14
+ # parsers have already failed on this input
15
+ parsers.each do |parser|
16
+ result = parser.(input)
17
+ case result
18
+ in L43Peg::Success => success
19
+ return success
20
+ else
21
+ end
22
+ end
23
+
24
+ L43Peg::Failure.new(parsed_by: name, reason: "no choice matched", name:)
25
+ end
26
+
27
+ end
28
+ end
29
+ end
30
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -7,11 +7,11 @@ module L43Peg
7
7
  DEFAULT_LEVEL = 5
8
8
  MAX_LEVEL = 10
9
9
 
10
- def parse_with_debug(parser:, name:, level:)
10
+ def debug_parser(parser:, name:, level:)
11
11
  level = _normalize_level(level)
12
- -> (input, cache, name1=nil) do
13
- _report_input(input, cache:, level:, name: name1||name)
14
- case parser.(input, cache:)
12
+ Parser.new(name:) do |input, name1=nil|
13
+ _report_input(input, level:, name: name1||name)
14
+ case parser.(input)
15
15
  in Success => success
16
16
  _report_success(success, name: name1||name, level:)
17
17
  in Failure => failure
@@ -49,7 +49,7 @@ module L43Peg
49
49
  failure
50
50
  end
51
51
 
52
- def _report_input(input, cache:, name:, level:)
52
+ def _report_input(input, name:, level:)
53
53
  _hl(level:)
54
54
  _prt(input.debug, level:, needed: MAX_LEVEL) ||
55
55
  _prt(input.head_hr(20, position: true), level:, needed: DEFAULT_LEVEL) ||
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ module Combinators
5
+ module Ignore extend self
6
+ def ignore(parser, name: nil)
7
+ Combinators.map(parser, name: name || "ignore(#{parser.name})") { nil }
8
+ end
9
+ end
10
+ end
11
+ end
12
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ module Combinators
5
+ module Lazy extend self
6
+ def lazy(name: nil, &parser)
7
+ name ||= "lazy()"
8
+ Parser.new(name, &lazy_parse_fn(parser))
9
+ end
10
+
11
+ private
12
+ def lazy_parse_fn(parser)
13
+ -> input do
14
+ parser.().(input)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -1,43 +1,48 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../helper"
4
3
  module L43Peg
5
4
  module Combinators
6
5
  module Many extend self
6
+ def many(parser, name: nil, min: 0, max: nil)
7
+ name ||= "many(#{parser.name}, min: #{min}, max: #{max})"
8
+ Parser.new(name) { many_parse_fn(input: it, parser:, name:, min:, max:) }
9
+ end
7
10
 
8
- include L43Peg::Helper
9
- def many(input:, cache:, name:, parser:, min:, max:)
11
+ private
12
+ def many_parse_fn(input:, name:, parser:, min:, max:)
10
13
  curr_input = input
11
- curr_cache = cache
12
14
  count = 0
13
15
  ast = []
16
+ parsed = String.new
14
17
 
15
18
  loop do
16
19
  if max && count == max
17
- return succeed_parser(ast, curr_input, cache: curr_cache)
20
+ return L43Peg::Success.new(ast:, rest: curr_input, parsed:)
18
21
  end
19
- case parser.(curr_input, cache: curr_cache)
20
- in L43Peg::Success => success
21
- ast.push(success.ast)
22
- curr_input = success.rest
23
- curr_cache = success.cache
24
- count += 1
22
+
23
+ case parser.(curr_input)
25
24
  in L43Peg::Failure
26
- if count < min
27
- return fail_parser("many #{name} should match at least #{min} times but did only #{count} times", input:)
28
- end
29
- return succeed_parser(ast, curr_input, cache: curr_cache)
30
- in L43Peg::Stop
31
- if count < min
32
- return fail_parser("many #{name} should match at least #{min} times but did only #{count} times", input:)
25
+ if count < min
26
+ return L43Peg::Failure.new(
27
+ reason: "many #{name} should match at least #{min} times but did only #{count} times", name:)
28
+ else
29
+ return L43Peg::Success.new(ast:, rest: curr_input, parsed:)
33
30
  end
34
- return succeed_parser(ast, curr_input.drop, cache: curr_cache)
31
+ in {ast: new_ast, parsed: new_parsed, rest: curr_input}
32
+ ast.push(new_ast)
33
+ parsed << new_parsed
34
+ count += 1
35
+ # in L43Peg::Stop
36
+ # if count < min
37
+ # return fail_parser("many #{name} should match at least #{min} times but did only #{count} times", input:)
38
+ # end
39
+ # return succeed_parser(ast:, rest: curr_input.drop, parsed:)
40
+ in bad_result
41
+ Errors.bad_result(bad_result.inspect)
35
42
  end
36
43
  end
37
44
  end
38
-
39
45
  end
40
46
  end
41
47
  end
42
-
43
48
  # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ module Combinators
5
+ module Map extend self
6
+ def map(parser, name: nil, fn: nil, box: nil, &mapper)
7
+ raise ArgumentError, "must not provide keyword parameter fn and a block" if fn && mapper
8
+ mapper = fn || mapper
9
+ raise ArgumentError, "must provide one of the keyword parameters [fn:, box:] or a block" unless mapper || box
10
+ mapper ||= :itself
11
+ Parser.new(name || "map(#{parser.name})") {|input, name=nil| _map(input:, name:, parser:, box:, mapper:)}
12
+ end
13
+
14
+ private
15
+ def _map(box:, input:, name:, parser:, mapper:)
16
+ case parser.(input)
17
+ in L43Peg::Failure => failure
18
+ failure.update(name:)
19
+ in L43Peg::Success => success
20
+ success.map(box:, &mapper)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ module Combinators
5
+ module MapError extend self
6
+ def map_error(parser, name: nil, reason: nil)
7
+ name ||= "map_error(#{parser.name}"
8
+ Parser.new(name) { _map_error(input: it, parser:, name:, reason:) }
9
+ end
10
+
11
+ private
12
+ def _map_error(input:, parser:, name:, reason:)
13
+ case parser.(input)
14
+ in Failure => failure
15
+ failure.map(name:, reason:)
16
+ in success
17
+ success
18
+ end
19
+ end
20
+
21
+ end
22
+ end
23
+ end
24
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ module Combinators
5
+ module Satisfy extend self
6
+ def satisfy(parser, name: nil, fn: nil, &satisfier)
7
+ raise ArgumentError, "must not provide keyword parameter fn and a block" if fn && satisfier
8
+ satisfier = fn || satisfier
9
+ raise ArgumentError, "must provide one the keyword parameter fn: or a block" unless satisfier
10
+ name ||= "satisfy(#{parser.name})"
11
+
12
+ Parser.new(name) {|input, name=nil| _satisfy(input:, name:, parser:, satisfier:)}
13
+ end
14
+
15
+ private
16
+
17
+ def _satisfy(input:, name:, parser:, satisfier:)
18
+ case parser.(input)
19
+ in Success => success
20
+ if satisfier.(success.ast)
21
+ success
22
+ else
23
+ L43Peg::Failure.new(
24
+ parsed_by: self,
25
+ reason: "satisfier failed on ast: #{success.ast.inspect}",
26
+ name:)
27
+ end
28
+ in failure
29
+ failure
30
+ end
31
+ end
32
+
33
+ end
34
+ end
35
+ end
36
+ # SPDX-License-Identifier: AGPL-3.0-or-later