l43_peg 0.1.8 → 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.
- checksums.yaml +4 -4
- data/README.md +34 -279
- data/lib/l43_peg/box.rb +27 -0
- data/lib/l43_peg/combinators/boxed.rb +16 -0
- data/lib/l43_peg/combinators/choice.rb +30 -0
- data/lib/l43_peg/combinators/debug_parser.rb +5 -5
- data/lib/l43_peg/combinators/ignore.rb +12 -0
- data/lib/l43_peg/combinators/lazy.rb +20 -0
- data/lib/l43_peg/combinators/many.rb +26 -21
- data/lib/l43_peg/combinators/map.rb +26 -0
- data/lib/l43_peg/combinators/map_error.rb +24 -0
- data/lib/l43_peg/combinators/satisfy.rb +36 -0
- data/lib/l43_peg/combinators/sequence.rb +41 -0
- data/lib/l43_peg/combinators.rb +62 -71
- data/lib/l43_peg/errors.rb +11 -0
- data/lib/l43_peg/failure.rb +17 -1
- data/lib/l43_peg/input.rb +12 -10
- data/lib/l43_peg/mappers.rb +21 -3
- data/lib/l43_peg/parser.rb +4 -4
- data/lib/l43_peg/parsers/char_parser.rb +48 -8
- data/lib/l43_peg/parsers/end_parser.rb +13 -12
- data/lib/l43_peg/parsers/eol_parser.rb +55 -0
- data/lib/l43_peg/parsers/escape_parser.rb +21 -0
- data/lib/l43_peg/parsers/failure_parser.rb +2 -2
- data/lib/l43_peg/parsers/int_parser.rb +9 -7
- data/lib/l43_peg/parsers/list_parser.rb +33 -0
- data/lib/l43_peg/parsers/literal_parser.rb +23 -0
- data/lib/l43_peg/parsers/name_parser.rb +13 -0
- data/lib/l43_peg/parsers/rgx_parser.rb +35 -20
- data/lib/l43_peg/parsers/string_parser.rb +26 -0
- data/lib/l43_peg/parsers/success_parser.rb +21 -0
- data/lib/l43_peg/parsers/symbol_parser.rb +19 -0
- data/lib/l43_peg/parsers/ws_parser.rb +20 -0
- data/lib/l43_peg/parsers.rb +51 -14
- data/lib/l43_peg/stop.rb +7 -7
- data/lib/l43_peg/success.rb +10 -12
- data/lib/l43_peg/version.rb +6 -0
- data/lib/l43_peg.rb +14 -22
- metadata +40 -19
- data/lib/l43/require_helper.rb +0 -8
- data/lib/l43_peg/cache.rb +0 -9
- data/lib/l43_peg/combinators/sel.rb +0 -24
- data/lib/l43_peg/combinators/seq.rb +0 -29
- data/lib/l43_peg/helper.rb +0 -13
- data/lib/l43_peg/parsers/rgx_tokenizer.rb +0 -24
- data/lib/l43_peg/parsers/sel_rgx_parser.rb +0 -25
- data/lib/l43_peg/parsers/token_parser.rb +0 -32
- data/lib/l43_peg/parsers/tokens_parser.rb +0 -63
- 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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 12367798c13e0e131a3d2b0acd28db7c272fd9c25f46715e0ec1a6eb415e0eb5
|
|
4
|
+
data.tar.gz: e57b47017a6af939d130f6178326e3fe90965c7ccd3dadc0308b0f54fba9abe5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
20
|
+
require 'l43_peg'
|
|
21
|
+
Kernel.include L43Peg # not the same as RSpec's include
|
|
33
22
|
```
|
|
34
23
|
|
|
35
|
-
Then we can parse
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
134
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
146
|
-
|
|
147
|
-
The basic concept is the `rgx_parser`
|
|
46
|
+
## Complete Documentation
|
|
148
47
|
|
|
149
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
54
|
+
...are described [here](speculations/concepts.md)
|
|
173
55
|
|
|
174
|
-
|
|
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 ©
|
|
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
|
|
data/lib/l43_peg/box.rb
ADDED
|
@@ -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
|
|
10
|
+
def debug_parser(parser:, name:, level:)
|
|
11
11
|
level = _normalize_level(level)
|
|
12
|
-
|
|
13
|
-
_report_input(input,
|
|
14
|
-
case parser.(input
|
|
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,
|
|
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
|
-
|
|
9
|
-
def
|
|
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
|
|
20
|
+
return L43Peg::Success.new(ast:, rest: curr_input, parsed:)
|
|
18
21
|
end
|
|
19
|
-
|
|
20
|
-
|
|
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 <
|
|
27
|
-
return
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
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
|