• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! # Chapter 7: Error Reporting
2 //!
3 //! ## Context
4 //!
5 //! With [`Parser::parse`] we get errors that point to the failure but don't explain the reason for
6 //! the failure:
7 //! ```rust
8 //! # use winnow::prelude::*;
9 //! # use winnow::token::take_while;
10 //! # use winnow::combinator::alt;
11 //! # use winnow::token::take;
12 //! # use winnow::combinator::fail;
13 //! # use winnow::Parser;
14 //! #
15 //! # #[derive(Debug, PartialEq, Eq)]
16 //! # pub struct Hex(usize);
17 //! #
18 //! # impl std::str::FromStr for Hex {
19 //! #     type Err = String;
20 //! #
21 //! #     fn from_str(input: &str) -> Result<Self, Self::Err> {
22 //! #         parse_digits
23 //! #             .try_map(|(t, v)| match t {
24 //! #                "0b" => usize::from_str_radix(v, 2),
25 //! #                "0o" => usize::from_str_radix(v, 8),
26 //! #                "0d" => usize::from_str_radix(v, 10),
27 //! #                "0x" => usize::from_str_radix(v, 16),
28 //! #                _ => unreachable!("`parse_digits` doesn't return `{t}`"),
29 //! #              })
30 //! #             .map(Hex)
31 //! #             .parse(input)
32 //! #             .map_err(|e| e.to_string())
33 //! #     }
34 //! # }
35 //! #
36 //! // ...
37 //!
38 //! # fn parse_digits<'s>(input: &mut &'s str) -> PResult<(&'s str, &'s str)> {
39 //! #     alt((
40 //! #         ("0b", parse_bin_digits),
41 //! #         ("0o", parse_oct_digits),
42 //! #         ("0d", parse_dec_digits),
43 //! #         ("0x", parse_hex_digits),
44 //! #     )).parse_next(input)
45 //! # }
46 //! #
47 //! # fn parse_bin_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
48 //! #     take_while(1.., (
49 //! #         ('0'..='1'),
50 //! #     )).parse_next(input)
51 //! # }
52 //! #
53 //! # fn parse_oct_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
54 //! #     take_while(1.., (
55 //! #         ('0'..='7'),
56 //! #     )).parse_next(input)
57 //! # }
58 //! #
59 //! # fn parse_dec_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
60 //! #     take_while(1.., (
61 //! #         ('0'..='9'),
62 //! #     )).parse_next(input)
63 //! # }
64 //! #
65 //! # fn parse_hex_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
66 //! #     take_while(1.., (
67 //! #         ('0'..='9'),
68 //! #         ('A'..='F'),
69 //! #         ('a'..='f'),
70 //! #     )).parse_next(input)
71 //! # }
72 //! fn main() {
73 //!     let input = "0xZZ";
74 //!     let error = "\
75 //! 0xZZ
76 //!   ^
77 //! ";
78 //!     assert_eq!(input.parse::<Hex>().unwrap_err(), error);
79 //! }
80 //! ```
81 //!
82 //! Back in [`chapter_1`], we glossed over the `Err` variant of [`PResult`].  `PResult<O>` is
83 //! actually short for `PResult<O, E=ContextError>` where [`ContextError`] is a relatively cheap
84 //! way of building up reasonable errors for humans.
85 //!
86 //! You can use [`Parser::context`] to annotate the error with custom types
87 //! while unwinding to further clarify the error:
88 //! ```rust
89 //! # use winnow::prelude::*;
90 //! # use winnow::token::take_while;
91 //! # use winnow::combinator::alt;
92 //! # use winnow::token::take;
93 //! # use winnow::combinator::fail;
94 //! # use winnow::Parser;
95 //! use winnow::error::StrContext;
96 //! use winnow::error::StrContextValue;
97 //!
98 //! #
99 //! # #[derive(Debug, PartialEq, Eq)]
100 //! # pub struct Hex(usize);
101 //! #
102 //! # impl std::str::FromStr for Hex {
103 //! #     type Err = String;
104 //! #
105 //! #     fn from_str(input: &str) -> Result<Self, Self::Err> {
106 //! #         parse_digits
107 //! #             .try_map(|(t, v)| match t {
108 //! #                "0b" => usize::from_str_radix(v, 2),
109 //! #                "0o" => usize::from_str_radix(v, 8),
110 //! #                "0d" => usize::from_str_radix(v, 10),
111 //! #                "0x" => usize::from_str_radix(v, 16),
112 //! #                _ => unreachable!("`parse_digits` doesn't return `{t}`"),
113 //! #              })
114 //! #             .map(Hex)
115 //! #             .parse(input)
116 //! #             .map_err(|e| e.to_string())
117 //! #     }
118 //! # }
119 //! #
120 //! fn parse_digits<'s>(input: &mut &'s str) -> PResult<(&'s str, &'s str)> {
121 //!     alt((
122 //!         ("0b", parse_bin_digits)
123 //!           .context(StrContext::Label("digit"))
124 //!           .context(StrContext::Expected(StrContextValue::Description("binary"))),
125 //!         ("0o", parse_oct_digits)
126 //!           .context(StrContext::Label("digit"))
127 //!           .context(StrContext::Expected(StrContextValue::Description("octal"))),
128 //!         ("0d", parse_dec_digits)
129 //!           .context(StrContext::Label("digit"))
130 //!           .context(StrContext::Expected(StrContextValue::Description("decimal"))),
131 //!         ("0x", parse_hex_digits)
132 //!           .context(StrContext::Label("digit"))
133 //!           .context(StrContext::Expected(StrContextValue::Description("hexadecimal"))),
134 //!     )).parse_next(input)
135 //! }
136 //!
137 //! // ...
138 //!
139 //! #
140 //! # fn parse_bin_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
141 //! #     take_while(1.., (
142 //! #         ('0'..='1'),
143 //! #     )).parse_next(input)
144 //! # }
145 //! #
146 //! # fn parse_oct_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
147 //! #     take_while(1.., (
148 //! #         ('0'..='7'),
149 //! #     )).parse_next(input)
150 //! # }
151 //! #
152 //! # fn parse_dec_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
153 //! #     take_while(1.., (
154 //! #         ('0'..='9'),
155 //! #     )).parse_next(input)
156 //! # }
157 //! #
158 //! # fn parse_hex_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
159 //! #     take_while(1.., (
160 //! #         ('0'..='9'),
161 //! #         ('A'..='F'),
162 //! #         ('a'..='f'),
163 //! #     )).parse_next(input)
164 //! # }
165 //! fn main() {
166 //!     let input = "0xZZ";
167 //!     let error = "\
168 //! 0xZZ
169 //!   ^
170 //! invalid digit
171 //! expected hexadecimal";
172 //!     assert_eq!(input.parse::<Hex>().unwrap_err(), error);
173 //! }
174 //! ```
175 //!
176 //! If you remember back to [`chapter_3`], [`alt`] will only report the last error.
177 //! So if the parsers fail for any reason, like a bad radix, it will be reported as an invalid
178 //! hexadecimal value:
179 //! ```rust
180 //! # use winnow::prelude::*;
181 //! # use winnow::token::take_while;
182 //! # use winnow::combinator::alt;
183 //! # use winnow::token::take;
184 //! # use winnow::combinator::fail;
185 //! # use winnow::Parser;
186 //! # use winnow::error::StrContext;
187 //! # use winnow::error::StrContextValue;
188 //! #
189 //! #
190 //! # #[derive(Debug, PartialEq, Eq)]
191 //! # pub struct Hex(usize);
192 //! #
193 //! # impl std::str::FromStr for Hex {
194 //! #     type Err = String;
195 //! #
196 //! #     fn from_str(input: &str) -> Result<Self, Self::Err> {
197 //! #         parse_digits
198 //! #             .try_map(|(t, v)| match t {
199 //! #                "0b" => usize::from_str_radix(v, 2),
200 //! #                "0o" => usize::from_str_radix(v, 8),
201 //! #                "0d" => usize::from_str_radix(v, 10),
202 //! #                "0x" => usize::from_str_radix(v, 16),
203 //! #                _ => unreachable!("`parse_digits` doesn't return `{t}`"),
204 //! #              })
205 //! #             .map(Hex)
206 //! #             .parse(input)
207 //! #             .map_err(|e| e.to_string())
208 //! #     }
209 //! # }
210 //! #
211 //! # fn parse_digits<'s>(input: &mut &'s str) -> PResult<(&'s str, &'s str)> {
212 //! #     alt((
213 //! #         ("0b", parse_bin_digits)
214 //! #           .context(StrContext::Label("digit"))
215 //! #           .context(StrContext::Expected(StrContextValue::Description("binary"))),
216 //! #         ("0o", parse_oct_digits)
217 //! #           .context(StrContext::Label("digit"))
218 //! #           .context(StrContext::Expected(StrContextValue::Description("octal"))),
219 //! #         ("0d", parse_dec_digits)
220 //! #           .context(StrContext::Label("digit"))
221 //! #           .context(StrContext::Expected(StrContextValue::Description("decimal"))),
222 //! #         ("0x", parse_hex_digits)
223 //! #           .context(StrContext::Label("digit"))
224 //! #           .context(StrContext::Expected(StrContextValue::Description("hexadecimal"))),
225 //! #     )).parse_next(input)
226 //! # }
227 //! #
228 //! # fn parse_bin_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
229 //! #     take_while(1.., (
230 //! #         ('0'..='1'),
231 //! #     )).parse_next(input)
232 //! # }
233 //! #
234 //! # fn parse_oct_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
235 //! #     take_while(1.., (
236 //! #         ('0'..='7'),
237 //! #     )).parse_next(input)
238 //! # }
239 //! #
240 //! # fn parse_dec_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
241 //! #     take_while(1.., (
242 //! #         ('0'..='9'),
243 //! #     )).parse_next(input)
244 //! # }
245 //! #
246 //! # fn parse_hex_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
247 //! #     take_while(1.., (
248 //! #         ('0'..='9'),
249 //! #         ('A'..='F'),
250 //! #         ('a'..='f'),
251 //! #     )).parse_next(input)
252 //! # }
253 //! fn main() {
254 //!     let input = "100";
255 //!     let error = "\
256 //! 100
257 //! ^
258 //! invalid digit
259 //! expected hexadecimal";
260 //!     assert_eq!(input.parse::<Hex>().unwrap_err(), error);
261 //! }
262 //! ```
263 //! We can improve this with [`fail`]:
264 //! ```rust
265 //! # use winnow::prelude::*;
266 //! # use winnow::token::take_while;
267 //! # use winnow::combinator::alt;
268 //! # use winnow::token::take;
269 //! # use winnow::combinator::fail;
270 //! # use winnow::Parser;
271 //! use winnow::error::StrContext;
272 //! use winnow::error::StrContextValue;
273 //!
274 //! #
275 //! # #[derive(Debug, PartialEq, Eq)]
276 //! # pub struct Hex(usize);
277 //! #
278 //! # impl std::str::FromStr for Hex {
279 //! #     type Err = String;
280 //! #
281 //! #     fn from_str(input: &str) -> Result<Self, Self::Err> {
282 //! #         parse_digits
283 //! #             .try_map(|(t, v)| match t {
284 //! #                "0b" => usize::from_str_radix(v, 2),
285 //! #                "0o" => usize::from_str_radix(v, 8),
286 //! #                "0d" => usize::from_str_radix(v, 10),
287 //! #                "0x" => usize::from_str_radix(v, 16),
288 //! #                _ => unreachable!("`parse_digits` doesn't return `{t}`"),
289 //! #              })
290 //! #             .map(Hex)
291 //! #             .parse(input)
292 //! #             .map_err(|e| e.to_string())
293 //! #     }
294 //! # }
295 //! #
296 //! fn parse_digits<'s>(input: &mut &'s str) -> PResult<(&'s str, &'s str)> {
297 //!     alt((
298 //!         ("0b", parse_bin_digits)
299 //!           .context(StrContext::Label("digit"))
300 //!           .context(StrContext::Expected(StrContextValue::Description("binary"))),
301 //!         ("0o", parse_oct_digits)
302 //!           .context(StrContext::Label("digit"))
303 //!           .context(StrContext::Expected(StrContextValue::Description("octal"))),
304 //!         ("0d", parse_dec_digits)
305 //!           .context(StrContext::Label("digit"))
306 //!           .context(StrContext::Expected(StrContextValue::Description("decimal"))),
307 //!         ("0x", parse_hex_digits)
308 //!           .context(StrContext::Label("digit"))
309 //!           .context(StrContext::Expected(StrContextValue::Description("hexadecimal"))),
310 //!         fail
311 //!           .context(StrContext::Label("radix prefix"))
312 //!           .context(StrContext::Expected(StrContextValue::StringLiteral("0b")))
313 //!           .context(StrContext::Expected(StrContextValue::StringLiteral("0o")))
314 //!           .context(StrContext::Expected(StrContextValue::StringLiteral("0d")))
315 //!           .context(StrContext::Expected(StrContextValue::StringLiteral("0x"))),
316 //!     )).parse_next(input)
317 //! }
318 //!
319 //! // ...
320 //!
321 //! #
322 //! # fn parse_bin_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
323 //! #     take_while(1.., (
324 //! #         ('0'..='1'),
325 //! #     )).parse_next(input)
326 //! # }
327 //! #
328 //! # fn parse_oct_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
329 //! #     take_while(1.., (
330 //! #         ('0'..='7'),
331 //! #     )).parse_next(input)
332 //! # }
333 //! #
334 //! # fn parse_dec_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
335 //! #     take_while(1.., (
336 //! #         ('0'..='9'),
337 //! #     )).parse_next(input)
338 //! # }
339 //! #
340 //! # fn parse_hex_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
341 //! #     take_while(1.., (
342 //! #         ('0'..='9'),
343 //! #         ('A'..='F'),
344 //! #         ('a'..='f'),
345 //! #     )).parse_next(input)
346 //! # }
347 //! fn main() {
348 //!     let input = "100";
349 //!     let error = "\
350 //! 100
351 //! ^
352 //! invalid radix prefix
353 //! expected `0b`, `0o`, `0d`, `0x`";
354 //!     assert_eq!(input.parse::<Hex>().unwrap_err(), error);
355 //! }
356 //! ```
357 //!
358 //! ## Error Cuts
359 //!
360 //! We still have the issue that we are falling-through when the radix is valid but the digits
361 //! don't match it:
362 //! ```rust
363 //! # use winnow::prelude::*;
364 //! # use winnow::token::take_while;
365 //! # use winnow::combinator::alt;
366 //! # use winnow::token::take;
367 //! # use winnow::combinator::fail;
368 //! # use winnow::Parser;
369 //! # use winnow::error::StrContext;
370 //! # use winnow::error::StrContextValue;
371 //! #
372 //! #
373 //! # #[derive(Debug, PartialEq, Eq)]
374 //! # pub struct Hex(usize);
375 //! #
376 //! # impl std::str::FromStr for Hex {
377 //! #     type Err = String;
378 //! #
379 //! #     fn from_str(input: &str) -> Result<Self, Self::Err> {
380 //! #         parse_digits
381 //! #             .try_map(|(t, v)| match t {
382 //! #                "0b" => usize::from_str_radix(v, 2),
383 //! #                "0o" => usize::from_str_radix(v, 8),
384 //! #                "0d" => usize::from_str_radix(v, 10),
385 //! #                "0x" => usize::from_str_radix(v, 16),
386 //! #                _ => unreachable!("`parse_digits` doesn't return `{t}`"),
387 //! #              })
388 //! #             .map(Hex)
389 //! #             .parse(input)
390 //! #             .map_err(|e| e.to_string())
391 //! #     }
392 //! # }
393 //! #
394 //! # fn parse_digits<'s>(input: &mut &'s str) -> PResult<(&'s str, &'s str)> {
395 //! #     alt((
396 //! #         ("0b", parse_bin_digits)
397 //! #           .context(StrContext::Label("digit"))
398 //! #           .context(StrContext::Expected(StrContextValue::Description("binary"))),
399 //! #         ("0o", parse_oct_digits)
400 //! #           .context(StrContext::Label("digit"))
401 //! #           .context(StrContext::Expected(StrContextValue::Description("octal"))),
402 //! #         ("0d", parse_dec_digits)
403 //! #           .context(StrContext::Label("digit"))
404 //! #           .context(StrContext::Expected(StrContextValue::Description("decimal"))),
405 //! #         ("0x", parse_hex_digits)
406 //! #           .context(StrContext::Label("digit"))
407 //! #           .context(StrContext::Expected(StrContextValue::Description("hexadecimal"))),
408 //! #         fail
409 //! #           .context(StrContext::Label("radix prefix"))
410 //! #           .context(StrContext::Expected(StrContextValue::StringLiteral("0b")))
411 //! #           .context(StrContext::Expected(StrContextValue::StringLiteral("0o")))
412 //! #           .context(StrContext::Expected(StrContextValue::StringLiteral("0d")))
413 //! #           .context(StrContext::Expected(StrContextValue::StringLiteral("0x"))),
414 //! #     )).parse_next(input)
415 //! # }
416 //! #
417 //! # fn parse_bin_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
418 //! #     take_while(1.., (
419 //! #         ('0'..='1'),
420 //! #     )).parse_next(input)
421 //! # }
422 //! #
423 //! # fn parse_oct_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
424 //! #     take_while(1.., (
425 //! #         ('0'..='7'),
426 //! #     )).parse_next(input)
427 //! # }
428 //! #
429 //! # fn parse_dec_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
430 //! #     take_while(1.., (
431 //! #         ('0'..='9'),
432 //! #     )).parse_next(input)
433 //! # }
434 //! #
435 //! # fn parse_hex_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
436 //! #     take_while(1.., (
437 //! #         ('0'..='9'),
438 //! #         ('A'..='F'),
439 //! #         ('a'..='f'),
440 //! #     )).parse_next(input)
441 //! # }
442 //! fn main() {
443 //!     let input = "0b5";
444 //!     let error = "\
445 //! 0b5
446 //! ^
447 //! invalid radix prefix
448 //! expected `0b`, `0o`, `0d`, `0x`";
449 //!     assert_eq!(input.parse::<Hex>().unwrap_err(), error);
450 //! }
451 //! ```
452 //!
453 //! Let's break down `PResult<O, E>` one step further:
454 //! ```rust
455 //! # use winnow::error::ErrorKind;
456 //! # use winnow::error::ErrMode;
457 //! pub type PResult<O, E = ErrorKind> = Result<O, ErrMode<E>>;
458 //! ```
459 //! [`PResult`] is just a fancy wrapper around `Result` that wraps our error in an [`ErrMode`]
460 //! type.
461 //!
462 //! [`ErrMode`] is an enum with [`Backtrack`] and [`Cut`] variants (ignore [`Incomplete`] as its only
463 //! relevant for [streaming][_topic::stream]). By default, errors are [`Backtrack`], meaning that
464 //! other parsing branches will be attempted on failure, like the next case of an [`alt`].  [`Cut`]
465 //! shortcircuits all other branches, immediately reporting the error.
466 //!
467 //! So we can get the correct `context` by modifying the above example with [`cut_err`]:
468 //! ```rust
469 //! # use winnow::prelude::*;
470 //! # use winnow::token::take_while;
471 //! # use winnow::combinator::alt;
472 //! # use winnow::token::take;
473 //! # use winnow::combinator::fail;
474 //! # use winnow::Parser;
475 //! # use winnow::error::StrContext;
476 //! # use winnow::error::StrContextValue;
477 //! use winnow::combinator::cut_err;
478 //!
479 //! #
480 //! # #[derive(Debug, PartialEq, Eq)]
481 //! # pub struct Hex(usize);
482 //! #
483 //! # impl std::str::FromStr for Hex {
484 //! #     type Err = String;
485 //! #
486 //! #     fn from_str(input: &str) -> Result<Self, Self::Err> {
487 //! #         parse_digits
488 //! #             .try_map(|(t, v)| match t {
489 //! #                "0b" => usize::from_str_radix(v, 2),
490 //! #                "0o" => usize::from_str_radix(v, 8),
491 //! #                "0d" => usize::from_str_radix(v, 10),
492 //! #                "0x" => usize::from_str_radix(v, 16),
493 //! #                _ => unreachable!("`parse_digits` doesn't return `{t}`"),
494 //! #              })
495 //! #             .map(Hex)
496 //! #             .parse(input)
497 //! #             .map_err(|e| e.to_string())
498 //! #     }
499 //! # }
500 //! #
501 //! fn parse_digits<'s>(input: &mut &'s str) -> PResult<(&'s str, &'s str)> {
502 //!     alt((
503 //!         ("0b", cut_err(parse_bin_digits))
504 //!           .context(StrContext::Label("digit"))
505 //!           .context(StrContext::Expected(StrContextValue::Description("binary"))),
506 //!         ("0o", cut_err(parse_oct_digits))
507 //!           .context(StrContext::Label("digit"))
508 //!           .context(StrContext::Expected(StrContextValue::Description("octal"))),
509 //!         ("0d", cut_err(parse_dec_digits))
510 //!           .context(StrContext::Label("digit"))
511 //!           .context(StrContext::Expected(StrContextValue::Description("decimal"))),
512 //!         ("0x", cut_err(parse_hex_digits))
513 //!           .context(StrContext::Label("digit"))
514 //!           .context(StrContext::Expected(StrContextValue::Description("hexadecimal"))),
515 //!         fail
516 //!           .context(StrContext::Label("radix prefix"))
517 //!           .context(StrContext::Expected(StrContextValue::StringLiteral("0b")))
518 //!           .context(StrContext::Expected(StrContextValue::StringLiteral("0o")))
519 //!           .context(StrContext::Expected(StrContextValue::StringLiteral("0d")))
520 //!           .context(StrContext::Expected(StrContextValue::StringLiteral("0x"))),
521 //!     )).parse_next(input)
522 //! }
523 //!
524 //! // ...
525 //!
526 //! #
527 //! # fn parse_bin_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
528 //! #     take_while(1.., (
529 //! #         ('0'..='1'),
530 //! #     )).parse_next(input)
531 //! # }
532 //! #
533 //! # fn parse_oct_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
534 //! #     take_while(1.., (
535 //! #         ('0'..='7'),
536 //! #     )).parse_next(input)
537 //! # }
538 //! #
539 //! # fn parse_dec_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
540 //! #     take_while(1.., (
541 //! #         ('0'..='9'),
542 //! #     )).parse_next(input)
543 //! # }
544 //! #
545 //! # fn parse_hex_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
546 //! #     take_while(1.., (
547 //! #         ('0'..='9'),
548 //! #         ('A'..='F'),
549 //! #         ('a'..='f'),
550 //! #     )).parse_next(input)
551 //! # }
552 //! fn main() {
553 //!     let input = "0b5";
554 //!     let error = "\
555 //! 0b5
556 //!   ^
557 //! invalid digit
558 //! expected binary";
559 //!     assert_eq!(input.parse::<Hex>().unwrap_err(), error);
560 //! }
561 //! ```
562 //!
563 //! ## Error Adaptation and Rendering
564 //!
565 //! While Winnow can provide basic rendering of errors, your application can have various demands
566 //! beyond the basics provided like
567 //! - Correctly reporting columns with unicode
568 //! - Conforming to a specific layout
569 //!
570 //! For example, to get rustc-like errors with [`annotate-snippets`](https://crates.io/crates/annotate-snippets):
571 //! ```rust
572 //! # use winnow::prelude::*;
573 //! # use winnow::token::take_while;
574 //! # use winnow::combinator::alt;
575 //! # use winnow::token::take;
576 //! # use winnow::combinator::fail;
577 //! # use winnow::Parser;
578 //! # use winnow::error::ParseError;
579 //! # use winnow::error::ContextError;
580 //! # use winnow::error::StrContext;
581 //! # use winnow::error::StrContextValue;
582 //! # use winnow::combinator::cut_err;
583 //! #
584 //! #
585 //! #[derive(Debug, PartialEq, Eq)]
586 //! pub struct Hex(usize);
587 //!
588 //! impl std::str::FromStr for Hex {
589 //!     type Err = HexError;
590 //!
591 //!     fn from_str(input: &str) -> Result<Self, Self::Err> {
592 //!         // ...
593 //! #         parse_digits
594 //! #             .try_map(|(t, v)| match t {
595 //! #                "0b" => usize::from_str_radix(v, 2),
596 //! #                "0o" => usize::from_str_radix(v, 8),
597 //! #                "0d" => usize::from_str_radix(v, 10),
598 //! #                "0x" => usize::from_str_radix(v, 16),
599 //! #                _ => unreachable!("`parse_digits` doesn't return `{t}`"),
600 //! #              })
601 //! #             .map(Hex)
602 //!             .parse(input)
603 //!             .map_err(|e| HexError::from_parse(e, input))
604 //!     }
605 //! }
606 //!
607 //! #[derive(Debug)]
608 //! pub struct HexError {
609 //!     message: String,
610 //!     // Byte spans are tracked, rather than line and column.
611 //!     // This makes it easier to operate on programmatically
612 //!     // and doesn't limit us to one definition for column count
613 //!     // which can depend on the output medium and application.
614 //!     span: std::ops::Range<usize>,
615 //!     input: String,
616 //! }
617 //!
618 //! impl HexError {
619 //!     fn from_parse(error: ParseError<&str, ContextError>, input: &str) -> Self {
620 //!         // The default renderer for `ContextError` is still used but that can be
621 //!         // customized as well to better fit your needs.
622 //!         let message = error.inner().to_string();
623 //!         let input = input.to_owned();
624 //!         let start = error.offset();
625 //!         // Assume the error span is only for the first `char`.
626 //!         // Semantic errors are free to choose the entire span returned by `Parser::with_span`.
627 //!         let end = (start + 1..).find(|e| input.is_char_boundary(*e)).unwrap_or(start);
628 //!         Self {
629 //!             message,
630 //!             span: start..end,
631 //!             input,
632 //!         }
633 //!     }
634 //! }
635 //!
636 //! impl std::fmt::Display for HexError {
637 //!     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
638 //!         let message = annotate_snippets::Level::Error.title(&self.message)
639 //!             .snippet(annotate_snippets::Snippet::source(&self.input)
640 //!                 .fold(true)
641 //!                 .annotation(annotate_snippets::Level::Error.span(self.span.clone()))
642 //!             );
643 //!         let renderer = annotate_snippets::Renderer::plain();
644 //!         let rendered = renderer.render(message);
645 //!         rendered.fmt(f)
646 //!     }
647 //! }
648 //!
649 //! impl std::error::Error for HexError {}
650 //!
651 //! # fn parse_digits<'s>(input: &mut &'s str) -> PResult<(&'s str, &'s str)> {
652 //! #     alt((
653 //! #         ("0b", cut_err(parse_bin_digits))
654 //! #           .context(StrContext::Label("digit"))
655 //! #           .context(StrContext::Expected(StrContextValue::Description("binary"))),
656 //! #         ("0o", cut_err(parse_oct_digits))
657 //! #           .context(StrContext::Label("digit"))
658 //! #           .context(StrContext::Expected(StrContextValue::Description("octal"))),
659 //! #         ("0d", cut_err(parse_dec_digits))
660 //! #           .context(StrContext::Label("digit"))
661 //! #           .context(StrContext::Expected(StrContextValue::Description("decimal"))),
662 //! #         ("0x", cut_err(parse_hex_digits))
663 //! #           .context(StrContext::Label("digit"))
664 //! #           .context(StrContext::Expected(StrContextValue::Description("hexadecimal"))),
665 //! #         fail
666 //! #           .context(StrContext::Label("radix prefix"))
667 //! #           .context(StrContext::Expected(StrContextValue::StringLiteral("0b")))
668 //! #           .context(StrContext::Expected(StrContextValue::StringLiteral("0o")))
669 //! #           .context(StrContext::Expected(StrContextValue::StringLiteral("0d")))
670 //! #           .context(StrContext::Expected(StrContextValue::StringLiteral("0x"))),
671 //! #     )).parse_next(input)
672 //! # }
673 //! #
674 //! # fn parse_bin_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
675 //! #     take_while(1.., (
676 //! #         ('0'..='1'),
677 //! #     )).parse_next(input)
678 //! # }
679 //! #
680 //! # fn parse_oct_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
681 //! #     take_while(1.., (
682 //! #         ('0'..='7'),
683 //! #     )).parse_next(input)
684 //! # }
685 //! #
686 //! # fn parse_dec_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
687 //! #     take_while(1.., (
688 //! #         ('0'..='9'),
689 //! #     )).parse_next(input)
690 //! # }
691 //! #
692 //! # fn parse_hex_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
693 //! #     take_while(1.., (
694 //! #         ('0'..='9'),
695 //! #         ('A'..='F'),
696 //! #         ('a'..='f'),
697 //! #     )).parse_next(input)
698 //! # }
699 //! fn main() {
700 //!     let input = "0b5";
701 //!     let error = "\
702 //! error: invalid digit
703 //! expected binary
704 //!   |
705 //! 1 | 0b5
706 //!   |   ^
707 //!   |";
708 //!     assert_eq!(input.parse::<Hex>().unwrap_err().to_string(), error);
709 //! }
710 //! ```
711 
712 #![allow(unused_imports)]
713 use super::chapter_1;
714 use super::chapter_3;
715 use crate::combinator::alt;
716 use crate::combinator::cut_err;
717 use crate::combinator::fail;
718 use crate::error::ContextError;
719 use crate::error::ErrMode;
720 use crate::error::ErrMode::*;
721 use crate::error::ErrorKind;
722 use crate::PResult;
723 use crate::Parser;
724 use crate::_topic;
725 
726 pub use super::chapter_6 as previous;
727 pub use super::chapter_8 as next;
728 pub use crate::_tutorial as table_of_contents;
729