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;