Skip to main content

winnow/_tutorial/
chapter_5.rs

1//! # Chapter 5: Repetition
2//!
3//! In [`chapter_3`], we covered how to sequence different parsers into a tuple but sometimes you need to run a
4//! single parser multiple times, collecting the result into a container, like `Vec`.
5//!
6//! Let's collect the result of `parse_digits`:
7//! ```rust
8//! # use winnow::prelude::*;
9//! # use winnow::Result;
10//! # use winnow::token::take_while;
11//! # use winnow::combinator::dispatch;
12//! # use winnow::token::take;
13//! # use winnow::combinator::fail;
14//! use winnow::combinator::opt;
15//! use winnow::combinator::repeat;
16//! use winnow::combinator::terminated;
17//!
18//! fn parse_list(input: &mut &str) -> Result<Vec<usize>> {
19//!     let mut list = Vec::new();
20//!     while let Some(output) = opt(terminated(parse_digits, opt(','))).parse_next(input)? {
21//!         list.push(output);
22//!     }
23//!     Ok(list)
24//! }
25//!
26//! // ...
27//! # fn parse_digits(input: &mut &str) -> Result<usize> {
28//! #     dispatch!(take(2usize);
29//! #         "0b" => parse_bin_digits.try_map(|s| usize::from_str_radix(s, 2)),
30//! #         "0o" => parse_oct_digits.try_map(|s| usize::from_str_radix(s, 8)),
31//! #         "0d" => parse_dec_digits.try_map(|s| usize::from_str_radix(s, 10)),
32//! #         "0x" => parse_hex_digits.try_map(|s| usize::from_str_radix(s, 16)),
33//! #         _ => fail,
34//! #     ).parse_next(input)
35//! # }
36//! #
37//! # fn parse_bin_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
38//! #     take_while(1.., (
39//! #         ('0'..='1'),
40//! #     )).parse_next(input)
41//! # }
42//! #
43//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
44//! #     take_while(1.., (
45//! #         ('0'..='7'),
46//! #     )).parse_next(input)
47//! # }
48//! #
49//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
50//! #     take_while(1.., (
51//! #         ('0'..='9'),
52//! #     )).parse_next(input)
53//! # }
54//! #
55//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
56//! #     take_while(1.., (
57//! #         ('0'..='9'),
58//! #         ('A'..='F'),
59//! #         ('a'..='f'),
60//! #     )).parse_next(input)
61//! # }
62//!
63//! fn main() {
64//!     let mut input = "0x1a2b,0x3c4d,0x5e6f Hello";
65//!
66//!     let digits = parse_list.parse_next(&mut input).unwrap();
67//!
68//!     assert_eq!(input, " Hello");
69//!     assert_eq!(digits, vec![0x1a2b, 0x3c4d, 0x5e6f]);
70//!
71//!     assert!(parse_digits(&mut "ghiWorld").is_err());
72//! }
73//! ```
74//!
75//! We can implement this declaratively with [`repeat`]:
76//! ```rust
77//! # use winnow::prelude::*;
78//! # use winnow::Result;
79//! # use winnow::token::take_while;
80//! # use winnow::combinator::dispatch;
81//! # use winnow::token::take;
82//! # use winnow::combinator::fail;
83//! use winnow::combinator::opt;
84//! use winnow::combinator::repeat;
85//! use winnow::combinator::terminated;
86//!
87//! fn parse_list(input: &mut &str) -> Result<Vec<usize>> {
88//!     repeat(0..,
89//!         terminated(parse_digits, opt(','))
90//!     ).parse_next(input)
91//! }
92//! #
93//! # fn parse_digits(input: &mut &str) -> Result<usize> {
94//! #     dispatch!(take(2usize);
95//! #         "0b" => parse_bin_digits.try_map(|s| usize::from_str_radix(s, 2)),
96//! #         "0o" => parse_oct_digits.try_map(|s| usize::from_str_radix(s, 8)),
97//! #         "0d" => parse_dec_digits.try_map(|s| usize::from_str_radix(s, 10)),
98//! #         "0x" => parse_hex_digits.try_map(|s| usize::from_str_radix(s, 16)),
99//! #         _ => fail,
100//! #     ).parse_next(input)
101//! # }
102//! #
103//! # fn parse_bin_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
104//! #     take_while(1.., (
105//! #         ('0'..='1'),
106//! #     )).parse_next(input)
107//! # }
108//! #
109//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
110//! #     take_while(1.., (
111//! #         ('0'..='7'),
112//! #     )).parse_next(input)
113//! # }
114//! #
115//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
116//! #     take_while(1.., (
117//! #         ('0'..='9'),
118//! #     )).parse_next(input)
119//! # }
120//! #
121//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
122//! #     take_while(1.., (
123//! #         ('0'..='9'),
124//! #         ('A'..='F'),
125//! #         ('a'..='f'),
126//! #     )).parse_next(input)
127//! # }
128//! #
129//! # fn main() {
130//! #     let mut input = "0x1a2b,0x3c4d,0x5e6f Hello";
131//! #
132//! #     let digits = parse_list.parse_next(&mut input).unwrap();
133//! #
134//! #     assert_eq!(input, " Hello");
135//! #     assert_eq!(digits, vec![0x1a2b, 0x3c4d, 0x5e6f]);
136//! #
137//! #     assert!(parse_digits(&mut "ghiWorld").is_err());
138//! # }
139//! ```
140//!
141//! You'll notice that the above allows trailing `,`. However, if that's not desired, it
142//! can easily be fixed by using [`separated`] instead of [`repeat`]:
143//! ```rust
144//! # use winnow::prelude::*;
145//! # use winnow::Result;
146//! # use winnow::token::take_while;
147//! # use winnow::combinator::dispatch;
148//! # use winnow::token::take;
149//! # use winnow::combinator::fail;
150//! use winnow::combinator::separated;
151//!
152//! fn parse_list(input: &mut &str) -> Result<Vec<usize>> {
153//!     separated(0.., parse_digits, ",").parse_next(input)
154//! }
155//!
156//! // ...
157//! # fn parse_digits(input: &mut &str) -> Result<usize> {
158//! #     dispatch!(take(2usize);
159//! #         "0b" => parse_bin_digits.try_map(|s| usize::from_str_radix(s, 2)),
160//! #         "0o" => parse_oct_digits.try_map(|s| usize::from_str_radix(s, 8)),
161//! #         "0d" => parse_dec_digits.try_map(|s| usize::from_str_radix(s, 10)),
162//! #         "0x" => parse_hex_digits.try_map(|s| usize::from_str_radix(s, 16)),
163//! #         _ => fail,
164//! #     ).parse_next(input)
165//! # }
166//! #
167//! # fn parse_bin_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
168//! #     take_while(1.., (
169//! #         ('0'..='1'),
170//! #     )).parse_next(input)
171//! # }
172//! #
173//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
174//! #     take_while(1.., (
175//! #         ('0'..='7'),
176//! #     )).parse_next(input)
177//! # }
178//! #
179//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
180//! #     take_while(1.., (
181//! #         ('0'..='9'),
182//! #     )).parse_next(input)
183//! # }
184//! #
185//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
186//! #     take_while(1.., (
187//! #         ('0'..='9'),
188//! #         ('A'..='F'),
189//! #         ('a'..='f'),
190//! #     )).parse_next(input)
191//! # }
192//!
193//! fn main() {
194//!     let mut input = "0x1a2b,0x3c4d,0x5e6f Hello";
195//!
196//!     let digits = parse_list.parse_next(&mut input).unwrap();
197//!
198//!     assert_eq!(input, " Hello");
199//!     assert_eq!(digits, vec![0x1a2b, 0x3c4d, 0x5e6f]);
200//!
201//!     assert!(parse_digits(&mut "ghiWorld").is_err());
202//! }
203//! ```
204//!
205//! If you look closely at [`separated`] and [`repeat`], they aren't limited to collecting
206//! the result into a `Vec`, but rather anything that implements the [`Accumulate`] trait.
207//! [`Accumulate`] is for instance also implemented for [`HashSet`], [`String`] and `()`.
208//!
209//! This lets us build more complex parsers than we did in
210//! [`chapter_2`] by accumulating the results into a `()` and [`take`][Parser::take]-ing
211//! the consumed input.
212//!
213//! `take` works by
214//! 1. Creating a [`checkpoint`][Stream::checkpoint]
215//! 2. Running the inner parser, in our case the `parse_list` parser, which will advance the input
216//! 3. Returning the slice from the first checkpoint to the current position.
217//!
218//! Since the result of `parse_list` gets thrown away, we
219//! accumulates into a `()` to not waste work creating an unused `Vec`.
220//!
221//! ```rust
222//! # use winnow::prelude::*;
223//! # use winnow::Result;
224//! # use winnow::token::take_while;
225//! # use winnow::combinator::dispatch;
226//! # use winnow::token::take;
227//! # use winnow::combinator::fail;
228//! # use winnow::combinator::separated;
229//! #
230//! fn take_list<'s>(input: &mut &'s str) -> Result<&'s str> {
231//!     parse_list.take().parse_next(input)
232//! }
233//!
234//! fn parse_list(input: &mut &str) -> Result<()> {
235//!     separated(0.., parse_digits, ",").parse_next(input)
236//! }
237//!
238//! # fn parse_digits(input: &mut &str) -> Result<usize> {
239//! #     dispatch!(take(2usize);
240//! #         "0b" => parse_bin_digits.try_map(|s| usize::from_str_radix(s, 2)),
241//! #         "0o" => parse_oct_digits.try_map(|s| usize::from_str_radix(s, 8)),
242//! #         "0d" => parse_dec_digits.try_map(|s| usize::from_str_radix(s, 10)),
243//! #         "0x" => parse_hex_digits.try_map(|s| usize::from_str_radix(s, 16)),
244//! #         _ => fail,
245//! #     ).parse_next(input)
246//! # }
247//! #
248//! # fn parse_bin_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
249//! #     take_while(1.., (
250//! #         ('0'..='1'),
251//! #     )).parse_next(input)
252//! # }
253//! #
254//! # fn parse_oct_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
255//! #     take_while(1.., (
256//! #         ('0'..='7'),
257//! #     )).parse_next(input)
258//! # }
259//! #
260//! # fn parse_dec_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
261//! #     take_while(1.., (
262//! #         ('0'..='9'),
263//! #     )).parse_next(input)
264//! # }
265//! #
266//! # fn parse_hex_digits<'s>(input: &mut &'s str) -> Result<&'s str> {
267//! #     take_while(1.., (
268//! #         ('0'..='9'),
269//! #         ('A'..='F'),
270//! #         ('a'..='f'),
271//! #     )).parse_next(input)
272//! # }
273//!
274//! fn main() {
275//!     let mut input = "0x1a2b,0x3c4d,0x5e6f Hello";
276//!
277//!     let digits = take_list.parse_next(&mut input).unwrap();
278//!
279//!     assert_eq!(input, " Hello");
280//!     assert_eq!(digits, "0x1a2b,0x3c4d,0x5e6f");
281//!
282//!     assert!(parse_digits(&mut "ghiWorld").is_err());
283//! }
284//! ```
285//! See [`combinator`] for more repetition parsers.
286
287#![allow(unused_imports)]
288use super::chapter_2;
289use super::chapter_3;
290use crate::combinator;
291use crate::combinator::repeat;
292use crate::combinator::separated;
293use crate::stream::Accumulate;
294use crate::stream::Stream;
295use crate::Parser;
296use std::collections::HashSet;
297
298pub use super::chapter_4 as previous;
299pub use super::chapter_6 as next;
300pub use crate::_tutorial as table_of_contents;