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
- Creating a
checkpoint - Running the inner parser, in our case the
parse_listparser, which will advance the input - 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.