Coverage Report

Created: 2026-11-11 06:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/nom-supreme-0.8.0/src/multi.rs
Line
Count
Source
1
/*!
2
Perfected looping parsers designed to behave more reliably and provide more
3
useful parse errors.
4
5
The combinators in this module all generally follow the same pattern for
6
parsing in a loop. They parse an item; then, they attempt to parse a
7
`terminator`. If the `terminator` is found, the parse returns successfully;
8
otherwise, they attempt to parse a `separator`. If they fail to parse either
9
a `separator` or a `terminator`, the parse fails; otherwise, it will continue
10
on to parse the next item. The parsed items are collected together into a final
11
value; each combinator does this in a slightly different way:
12
13
- [`collect_separated_terminated`] collects the parsed items into a collection
14
with [`Extend`].
15
- [`parse_separated_terminated`] combines the parsed items with a folding
16
function.
17
- [`parse_separated_terminated_res`] combines the parsed items with a fallible
18
folding function; it may return early if the folding function returns an
19
[`Err`].
20
21
These combinators always parse at least 1 item. If you want 0 or more things
22
to be parsed, use [`opt`] or [`alt`] to handle that case.
23
24
These combinators will stop as soon as they find a `terminator`. If you wish
25
to have a `terminator` parser that is the same as your `separator`, you'll need
26
to add some extra context to the terminator parser; perhaps a lookahead
27
with [`peek`].
28
29
These combinators exists to provide meaningful parse errors. By requiring a
30
`terminator`, we can ensure that they don't suffer from the normal folding
31
parser problem of unconditionally returning success because a subparser failure
32
is interpreted as the end of the loop. This ensures that potentially important
33
errors aren't thrown away.
34
35
The combinators will attempt to smartly allow 0-length matches. It will allow
36
subparsers to have 0-length matches, but if a full loop is made without any
37
progress being made, we assume we've encountered an infinite loop and return
38
a parse error.
39
40
[`opt`]: crate::parser_ext::ParserExt::opt
41
[`alt`]: nom::branch::alt
42
[`peek`]: crate::parser_ext::ParserExt::peek
43
*/
44
45
use core::{convert::Infallible, iter};
46
47
use nom::{
48
    error::{ErrorKind::SeparatedNonEmptyList, FromExternalError, ParseError},
49
    Err::Error,
50
    InputLength, Parser,
51
};
52
53
/**
54
Parse a series of 1 or more things, separated by `separator`, terminated by
55
`terminator`, and collect them into a collection using [`Extend`].
56
57
When this parser is run, it will create a new, empty collection with
58
[`Default`]. It will then collect each parsed item into the collection with
59
[`Extend`]. See the [module] docs for details of how this parser parses a sequence.
60
61
See the [module] docs for a detailed description of how this parser parses a sequence.
62
63
# Example
64
65
```
66
use nom_supreme::{
67
    multi::collect_separated_terminated,
68
    parser_ext::ParserExt,
69
    error::ErrorTree,
70
};
71
use nom::character::complete::{digit1, char, space0};
72
use nom::{IResult, Parser, error::ParseError};
73
74
fn parse_number(input: &str) -> IResult<&str, i32, ErrorTree<&str>> {
75
    digit1
76
        .preceded_by(char('-').opt())
77
        .recognize()
78
        .parse_from_str()
79
        .parse(input)
80
}
81
82
// A vector is a square brackets, containing comma separated numbers, with
83
// whitespace in between
84
let mut vec_parser = collect_separated_terminated(
85
    // Parse numbers
86
    parse_number.terminated(space0),
87
88
    // separated by commas
89
    char(',').terminated(space0),
90
91
    // terminated by a close bracket
92
    char(']'),
93
)
94
// Allow for empty vectors
95
.or(char(']').value(Vec::new()))
96
.preceded_by(char('[').terminated(space0));
97
98
let result: IResult<&str, Vec<i32>, ErrorTree<&str>> = vec_parser.parse("[1, 2, -3, 4]");
99
let vec = result.unwrap().1;
100
assert_eq!(vec, [1, 2, -3, 4]);
101
102
```
103
104
[module]: crate::multi
105
*/
106
0
pub fn collect_separated_terminated<
107
0
    Input,
108
0
    ParseOutput,
109
0
    SepOutput,
110
0
    TermOutput,
111
0
    ParseErr,
112
0
    Collection,
113
0
>(
114
0
    parser: impl Parser<Input, ParseOutput, ParseErr>,
115
0
    separator: impl Parser<Input, SepOutput, ParseErr>,
116
0
    terminator: impl Parser<Input, TermOutput, ParseErr>,
117
0
) -> impl Parser<Input, Collection, ParseErr>
118
0
where
119
0
    Input: Clone + InputLength,
120
0
    ParseErr: ParseError<Input>,
121
0
    Collection: Default + Extend<ParseOutput>,
122
{
123
0
    parse_separated_terminated(
124
0
        parser,
125
0
        separator,
126
0
        terminator,
127
        Collection::default,
128
        // TODO: use extend_one
129
0
        |collection, item| express!(collection.extend(iter::once(item))),
130
    )
131
0
}
132
133
/**
134
Parse a series of 1 or more things, separated by `separator`, terminated by
135
`terminator`, and fold them together using a folding function.
136
137
When this parser is run, it will first create an accumulator value with `init`.
138
It will then combine it with every parsed item using `fold`, which should
139
return the new accumulator for each item. See the [module] docs for details
140
of how this parser parses a sequence.
141
142
[module]: crate::multi
143
*/
144
#[inline]
145
0
pub fn parse_separated_terminated<Input, ParseOutput, SepOutput, TermOutput, ParseErr, Accum>(
146
0
    parser: impl Parser<Input, ParseOutput, ParseErr>,
147
0
    separator: impl Parser<Input, SepOutput, ParseErr>,
148
0
    terminator: impl Parser<Input, TermOutput, ParseErr>,
149
0
150
0
    init: impl FnMut() -> Accum,
151
0
    mut fold: impl FnMut(Accum, ParseOutput) -> Accum,
152
0
) -> impl Parser<Input, Accum, ParseErr>
153
0
where
154
0
    Input: Clone + InputLength,
155
0
    ParseErr: ParseError<Input>,
156
{
157
0
    parse_separated_terminated_impl(
158
0
        parser,
159
0
        separator,
160
0
        terminator,
161
0
        init,
162
0
        move |accum, item| Ok(fold(accum, item)),
163
        |_input, err: Infallible| match err {},
164
    )
165
0
}
166
167
/**
168
Parse a series of 1 or more things, separated by some `separator`, terminated
169
by some `terminator`, folding them all together with a fallible fold function.
170
171
This function is identical to [`parse_separated_terminated`], except that
172
the fold function may return an error, which ends the parse early. See its
173
documentation for more details about the precise behavior of this parser.
174
*/
175
#[inline]
176
0
pub fn parse_separated_terminated_res<
177
0
    Input,
178
0
    ParseOutput,
179
0
    SepOutput,
180
0
    TermOutput,
181
0
    ParseErr,
182
0
    Accum,
183
0
    FoldErr,
184
0
>(
185
0
    parser: impl Parser<Input, ParseOutput, ParseErr>,
186
0
    separator: impl Parser<Input, SepOutput, ParseErr>,
187
0
    terminator: impl Parser<Input, TermOutput, ParseErr>,
188
0
189
0
    init: impl FnMut() -> Accum,
190
0
    fold: impl FnMut(Accum, ParseOutput) -> Result<Accum, FoldErr>,
191
0
) -> impl Parser<Input, Accum, ParseErr>
192
0
where
193
0
    Input: Clone + InputLength,
194
0
    ParseErr: ParseError<Input> + FromExternalError<Input, FoldErr>,
195
{
196
0
    parse_separated_terminated_impl(parser, separator, terminator, init, fold, |input, err| {
197
0
        ParseErr::from_external_error(input, SeparatedNonEmptyList, err)
198
0
    })
199
0
}
200
201
/// Helper enum for tracking zero length parses. `parse_separated_terminated`
202
/// allows for subparsers to return zero-length matches, but if *every*
203
/// subparser does so in a loop, that's reported as an error.
204
///
205
/// This enum specifically tracks the least-recent zero-length parse that has
206
/// not been succeeded by a non-zero-length parser.
207
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
208
enum ZeroLengthParseState<E> {
209
    None,
210
    Item,
211
    Separator { terminator_error: E },
212
}
213
214
impl<E> ZeroLengthParseState<E> {
215
0
    fn terminator_error(self) -> Option<E> {
216
0
        match self {
217
0
            Self::Separator { terminator_error } => Some(terminator_error),
218
0
            _ => None,
219
        }
220
0
    }
221
}
222
223
/// Shared implementation for parse_separated_terminated_res and
224
/// parse_separated_terminated. This exists so that we don't have to have an
225
/// unnecessary bound of FromExternalError on parse_separated_terminated.
226
#[inline]
227
0
fn parse_separated_terminated_impl<
228
0
    Input,
229
0
    ParseOutput,
230
0
    SepOutput,
231
0
    TermOutput,
232
0
    ParseErr,
233
0
    Accum,
234
0
    FoldErr,
235
0
>(
236
0
    mut parser: impl Parser<Input, ParseOutput, ParseErr>,
237
0
    mut separator: impl Parser<Input, SepOutput, ParseErr>,
238
0
    mut terminator: impl Parser<Input, TermOutput, ParseErr>,
239
0
240
0
    mut init: impl FnMut() -> Accum,
241
0
    mut fold: impl FnMut(Accum, ParseOutput) -> Result<Accum, FoldErr>,
242
0
243
0
    mut build_error: impl FnMut(Input, FoldErr) -> ParseErr,
244
0
) -> impl Parser<Input, Accum, ParseErr>
245
0
where
246
0
    Input: Clone + InputLength,
247
0
    ParseErr: ParseError<Input>,
248
{
249
0
    move |mut input: Input| {
250
0
        let mut accum = init();
251
252
0
        let mut zero_length_state = ZeroLengthParseState::None;
253
254
        loop {
255
            // Try to find a value. To fail to do so at this point is an
256
            // error, since we either just started or successfully parsed a
257
            // separator.
258
            //
259
            // If an error occurs here, also try to attach terminator_error.
260
            // terminator_error is available if the most recent separator parse
261
            // was zero-length, which means that both the terminator and the
262
            // item would be valid parses at this point.
263
0
            let (tail, value) = match parser.parse(input.clone()) {
264
0
                Ok(success) => success,
265
0
                Err(Error(item_error)) => {
266
0
                    break Err(Error(match zero_length_state.terminator_error() {
267
0
                        None => item_error,
268
0
                        Some(terminator_error) => item_error.or(terminator_error),
269
                    }))
270
                }
271
0
                Err(err) => break Err(err),
272
            };
273
274
            // Check zero-length matches
275
0
            zero_length_state = match (input.input_len() == tail.input_len(), zero_length_state) {
276
                // If both the item and the separator had a zero length match,
277
                // we're hanging. Bail.
278
                //
279
                // It doesn't make sense to include the terminator error here,
280
                // because we *did* successfully parse a separator and an
281
                // item, they just happened to be zero length
282
                (true, ZeroLengthParseState::Separator { .. }) => {
283
0
                    break Err(Error(ParseErr::from_error_kind(
284
0
                        input,
285
0
                        SeparatedNonEmptyList,
286
0
                    )))
287
                }
288
289
                // If only the item had a zero-length match, update the
290
                // state.
291
0
                (true, _) => ZeroLengthParseState::Item,
292
293
                // If the item had a non-zero length match, clear the state
294
0
                (false, _) => ZeroLengthParseState::None,
295
            };
296
297
            // Advance the loop state
298
0
            accum = fold(accum, value).map_err(|err| Error(build_error(input, err)))?;
299
0
            input = tail;
300
301
            // Try to find a terminator; if we found it, we're done. If we
302
            // didn't, preserve the error, so that it can be reported as an
303
            // .or() branch with the subsequent separator or item error.
304
0
            let terminator_error = match terminator.parse(input.clone()) {
305
                // We found a terminator, so we're done
306
0
                Ok((tail, _)) => break Ok((tail, accum)),
307
308
                // No terminator. Keep track of the error in case we also fail
309
                // to find a separator or item.
310
0
                Err(Error(err)) => err,
311
312
                // Other kinds of errors should be returned immediately.
313
0
                Err(err) => break Err(err),
314
            };
315
316
            // No terminator, so instead try to find a separator
317
0
            let tail = match separator.parse(input.clone()) {
318
0
                Ok((tail, _)) => tail,
319
0
                Err(Error(separator_error)) => {
320
0
                    break Err(Error(separator_error.or(terminator_error)))
321
                }
322
0
                Err(err) => break Err(err),
323
            };
324
325
            // Check zero-length matches
326
0
            zero_length_state = match (input.input_len() == tail.input_len(), zero_length_state) {
327
                // If both the separator and the item had a zero length match,
328
                // we're hanging. Bail.
329
                (true, ZeroLengthParseState::Item) => {
330
0
                    break Err(Error(ParseErr::from_error_kind(
331
0
                        input,
332
0
                        SeparatedNonEmptyList,
333
0
                    )))
334
                }
335
                // If only the separator had a zero-length match, update the
336
                // state. Additionally preserve the terminator error so that
337
                // it can be reported as an alternate if there was an item
338
                // error.
339
0
                (true, _) => ZeroLengthParseState::Separator { terminator_error },
340
341
                // If the separator had a non-zero length match, clear the
342
                // state
343
0
                (false, _) => ZeroLengthParseState::None,
344
            };
345
346
            // Advance the loop state
347
0
            input = tail;
348
        }
349
0
    }
350
0
}
351
352
#[cfg(test)]
353
mod test_separated_terminated {
354
    use cool_asserts::assert_matches;
355
    use nom::{
356
        branch::alt,
357
        character::complete::{alpha0, char, digit1, space0},
358
        error::ErrorKind,
359
        Err, IResult, Parser,
360
    };
361
362
    use crate::error::{BaseErrorKind, ErrorTree, Expectation};
363
    use crate::parser_ext::ParserExt;
364
365
    use super::parse_separated_terminated;
366
367
    /// Parse a series of numbers, separated by commas, terminated by a period.
368
    fn parse_number_list(input: &str) -> IResult<&str, Vec<i64>, ErrorTree<&str>> {
369
        parse_separated_terminated(
370
            digit1.parse_from_str(),
371
            char(',').delimited_by(space0),
372
            char('.').preceded_by(space0),
373
            Vec::new,
374
            |vec, num| express!(vec.push(num)),
375
        )
376
        .parse(input)
377
    }
378
379
    #[test]
380
    fn basic() {
381
        assert_eq!(
382
            parse_number_list("1, 2, 3, 4, 5.").unwrap(),
383
            ("", vec![1, 2, 3, 4, 5]),
384
        )
385
    }
386
387
    #[test]
388
    fn trailing_input() {
389
        assert_eq!(
390
            parse_number_list("1, 2, 3, 4, 5. 4, 5, 6.").unwrap(),
391
            (" 4, 5, 6.", vec![1, 2, 3, 4, 5]),
392
        )
393
    }
394
395
    #[test]
396
    fn only_one() {
397
        assert_eq!(parse_number_list("10.").unwrap(), ("", vec![10]),)
398
    }
399
400
    #[test]
401
    fn at_least_one() {
402
        let err = parse_number_list("abc").unwrap_err();
403
404
        assert_matches!(
405
            err,
406
            Err::Error(ErrorTree::Base {
407
                location: "abc",
408
                kind: BaseErrorKind::Expected(Expectation::Digit)
409
            })
410
        );
411
    }
412
413
    /// Test that a parse failure from both the separator and the terminator
414
    /// causes an error including both messages
415
    #[test]
416
    fn terminator_separator_miss() {
417
        let err = parse_number_list("10, 20 30.").unwrap_err();
418
419
        let choices = assert_matches!(err, Err::Error(ErrorTree::Alt(choices)) => choices);
420
        assert_matches!(
421
            choices.as_slice(),
422
            [
423
                ErrorTree::Base {
424
                    location: "30.",
425
                    kind: BaseErrorKind::Expected(Expectation::Char(','))
426
                },
427
                ErrorTree::Base {
428
                    location: "30.",
429
                    kind: BaseErrorKind::Expected(Expectation::Char('.'))
430
                },
431
            ]
432
        );
433
    }
434
435
    /// Test that a terminator is required, even at EoF
436
    #[test]
437
    fn required_terminator() {
438
        let err = parse_number_list("1, 2, 3").unwrap_err();
439
440
        let choices = assert_matches!(err, Err::Error(ErrorTree::Alt(choices)) => choices);
441
        assert_matches!(
442
            choices.as_slice(),
443
            [
444
                ErrorTree::Base {
445
                    location: "",
446
                    kind: BaseErrorKind::Expected(Expectation::Char(','))
447
                },
448
                ErrorTree::Base {
449
                    location: "",
450
                    kind: BaseErrorKind::Expected(Expectation::Char('.'))
451
                },
452
            ]
453
        );
454
    }
455
456
    /// Test that a parse failure from the item parser includes only that error
457
    /// if the separator isn't zero-length
458
    #[test]
459
    fn item_error() {
460
        let err = parse_number_list("1, 2, abc.").unwrap_err();
461
462
        assert_matches!(
463
            err,
464
            Err::Error(ErrorTree::Base {
465
                location: "abc.",
466
                kind: BaseErrorKind::Expected(Expectation::Digit),
467
            })
468
        );
469
    }
470
471
    /// Parse a series of numbers ending in periods, separated by 0 or more
472
    /// whitespace, terminated by a semicolon. Used to test 0-length
473
    /// separator behavior.
474
    fn parse_number_dot_list(input: &str) -> IResult<&str, Vec<i64>, ErrorTree<&str>> {
475
        parse_separated_terminated(
476
            digit1.terminated(char('.')).parse_from_str(),
477
            space0,
478
            char(';'),
479
            Vec::new,
480
            |vec, num| express!(vec.push(num)),
481
        )
482
        .parse(input)
483
    }
484
485
    #[test]
486
    fn zero_length_separator() {
487
        assert_eq!(
488
            parse_number_dot_list("1.2. 3.4.  5.; abc").unwrap(),
489
            (" abc", vec![1, 2, 3, 4, 5])
490
        );
491
    }
492
493
    /// Test that, when a separator matches zero length, and then the item
494
    /// parser fails, the returned error includes both the item error and the
495
    /// terminator error.
496
    #[test]
497
    fn zero_length_separator_item_term_error() {
498
        let err = parse_number_dot_list("1.2.3.abc.;").unwrap_err();
499
500
        let choices = assert_matches!(err, Err::Error(ErrorTree::Alt(choices)) => choices);
501
        assert_matches!(
502
            choices.as_slice(),
503
            [
504
                ErrorTree::Base {
505
                    location: "abc.;",
506
                    kind: BaseErrorKind::Expected(Expectation::Digit)
507
                },
508
                ErrorTree::Base {
509
                    location: "abc.;",
510
                    kind: BaseErrorKind::Expected(Expectation::Char(';'))
511
                },
512
            ]
513
        );
514
    }
515
516
    /// Parse a series of runs of 1 or more digits or 0 more more letters, separated by
517
    /// an optional dash, terminated by a semicolon. Used to test
518
    /// infinite loop detection
519
    fn parse_letters_numbers(input: &str) -> IResult<&str, Vec<&str>, ErrorTree<&str>> {
520
        parse_separated_terminated(
521
            alt((digit1, alpha0)),
522
            char('-').opt(),
523
            char(';'),
524
            Vec::new,
525
            |vec, num| express!(vec.push(num)),
526
        )
527
        .parse(input)
528
    }
529
530
    #[test]
531
    fn zero_length_item() {
532
        assert_eq!(
533
            parse_letters_numbers("----; abc").unwrap(),
534
            (" abc", vec!["", "", "", "", ""])
535
        )
536
    }
537
538
    #[test]
539
    fn zero_length_separators() {
540
        assert_eq!(
541
            parse_letters_numbers("abc123abc123; abc").unwrap(),
542
            (" abc", vec!["abc", "123", "abc", "123"]),
543
        )
544
    }
545
546
    /// Test that both zero-length separators and items are allowed together,
547
    /// as long as the loop makes progress
548
    #[test]
549
    fn zero_length_mixed() {
550
        assert_eq!(
551
            parse_letters_numbers("abc--123abc-123-; abc").unwrap(),
552
            (" abc", vec!["abc", "", "123", "abc", "123", ""]),
553
        )
554
    }
555
556
    /// Test that if the loop makes no progress, that's an error
557
    #[test]
558
    fn infinite_loop_aborts() {
559
        let err = parse_letters_numbers("abc123-.; abc").unwrap_err();
560
561
        assert_matches!(
562
            err,
563
            Err::Error(ErrorTree::Base {
564
                location: ".; abc",
565
                kind: BaseErrorKind::Kind(ErrorKind::SeparatedNonEmptyList)
566
            })
567
        );
568
    }
569
570
    /// Parse a series of numbers, separated by commas, terminated by optional
571
    /// comma and eof. Used to test that the terminator "wins" when it and the
572
    /// separator can match the same string.
573
    fn parse_comma_separated(input: &str) -> IResult<&str, Vec<i64>, ErrorTree<&str>> {
574
        parse_separated_terminated(
575
            digit1.parse_from_str(),
576
            char(','),
577
            char(',').opt().all_consuming(),
578
            Vec::new,
579
            |vec, num| express!(vec.push(num)),
580
        )
581
        .parse(input)
582
    }
583
584
    #[test]
585
    fn empty_terminator_wins() {
586
        assert_eq!(
587
            parse_comma_separated("1,2,3,4").unwrap(),
588
            ("", vec![1, 2, 3, 4]),
589
        );
590
    }
591
592
    #[test]
593
    fn test_terminator_wins() {
594
        assert_eq!(
595
            parse_comma_separated("1,2,3,4,").unwrap(),
596
            ("", vec![1, 2, 3, 4]),
597
        )
598
    }
599
}