Skip to main content

winnow/_tutorial/
chapter_7.rs

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