Skip to main content

Module chapter_5

Module chapter_5 

Source
Available on crate feature unstable-doc only.
Expand description

§Chapter 5: Repetition

In chapter_3, we covered how to sequence different parsers into a tuple but sometimes you need to run a single parser multiple times, collecting the result into a container, like Vec.

Let’s collect the result of parse_digits:

use winnow::combinator::opt;
use winnow::combinator::repeat;
use winnow::combinator::terminated;

fn parse_list(input: &mut &str) -> Result<Vec<usize>> {
    let mut list = Vec::new();
    while let Some(output) = opt(terminated(parse_digits, opt(','))).parse_next(input)? {
        list.push(output);
    }
    Ok(list)
}

// ...

fn main() {
    let mut input = "0x1a2b,0x3c4d,0x5e6f Hello";

    let digits = parse_list.parse_next(&mut input).unwrap();

    assert_eq!(input, " Hello");
    assert_eq!(digits, vec![0x1a2b, 0x3c4d, 0x5e6f]);

    assert!(parse_digits(&mut "ghiWorld").is_err());
}

We can implement this declaratively with repeat:

use winnow::combinator::opt;
use winnow::combinator::repeat;
use winnow::combinator::terminated;

fn parse_list(input: &mut &str) -> Result<Vec<usize>> {
    repeat(0..,
        terminated(parse_digits, opt(','))
    ).parse_next(input)
}

You’ll notice that the above allows trailing ,. However, if that’s not desired, it can easily be fixed by using separated instead of repeat:

use winnow::combinator::separated;

fn parse_list(input: &mut &str) -> Result<Vec<usize>> {
    separated(0.., parse_digits, ",").parse_next(input)
}

// ...

fn main() {
    let mut input = "0x1a2b,0x3c4d,0x5e6f Hello";

    let digits = parse_list.parse_next(&mut input).unwrap();

    assert_eq!(input, " Hello");
    assert_eq!(digits, vec![0x1a2b, 0x3c4d, 0x5e6f]);

    assert!(parse_digits(&mut "ghiWorld").is_err());
}

If you look closely at separated and repeat, they aren’t limited to collecting the result into a Vec, but rather anything that implements the Accumulate trait. Accumulate is for instance also implemented for HashSet, String and ().

This lets us build more complex parsers than we did in chapter_2 by accumulating the results into a () and take-ing the consumed input.

take works by

  1. Creating a checkpoint
  2. Running the inner parser, in our case the parse_list parser, which will advance the input
  3. Returning the slice from the first checkpoint to the current position.

Since the result of parse_list gets thrown away, we accumulates into a () to not waste work creating an unused Vec.

fn take_list<'s>(input: &mut &'s str) -> Result<&'s str> {
    parse_list.take().parse_next(input)
}

fn parse_list(input: &mut &str) -> Result<()> {
    separated(0.., parse_digits, ",").parse_next(input)
}


fn main() {
    let mut input = "0x1a2b,0x3c4d,0x5e6f Hello";

    let digits = take_list.parse_next(&mut input).unwrap();

    assert_eq!(input, " Hello");
    assert_eq!(digits, "0x1a2b,0x3c4d,0x5e6f");

    assert!(parse_digits(&mut "ghiWorld").is_err());
}

See combinator for more repetition parsers.

Re-exports§

pub use super::chapter_4 as previous;
pub use super::chapter_6 as next;
pub use crate::_tutorial as table_of_contents;