lisbeth_error/
handbook.rs

1//! A simple example explaining how to parse input data and emit errors.
2//!
3//! This page shows how to parse a sequence of space-separated decimal numbers.
4//! Let's first define the `Number` type, and a simple parser for it:
5//!
6//! ```rust
7//! use lisbeth_error::{
8//!     span::{Span, SpannedStr},
9//!     error::AnnotatedError,
10//! };
11//!
12//! struct Number {
13//!     span: Span,
14//!     value: u32,
15//! }
16//!
17//! // This will be usefull for our tests
18//! impl PartialEq<u32> for Number {
19//!     fn eq(&self, other: &u32) -> bool {
20//!         self.value == *other
21//!     }
22//! }
23//!
24//! fn number<'a>(input: SpannedStr<'a>) -> Result<(Number, SpannedStr<'a>), AnnotatedError> {
25//!     let (matched, tail) = input.take_while(char::is_numeric);
26//!
27//!     if matched.content().is_empty() {
28//!         let mut first = true;
29//!         let err_span = input.take_while(|c| {
30//!             let tmp = !c.is_whitespace() || first;
31//!             first = false;
32//!             tmp
33//!         }).0;
34//!         let report = AnnotatedError::new(err_span.span(), "Expected number")
35//!             .with_annotation(
36//!                 err_span.span(),
37//!                 format!("Expected number, found `{}`.", err_span.content()),
38//!             );
39//!
40//!         return Err(report);
41//!     }
42//!
43//!     let value = matched.content().parse().unwrap();
44//!     let span = matched.span();
45//!
46//!     let number = Number { span, value };
47//!
48//!     Ok((number, tail))
49//! }
50//! ```
51//!
52//! Now, we need to parse spaces. Let's do so:
53//!
54//! ```rust
55//! # use lisbeth_error::{
56//! #     span::{Span, SpannedStr},
57//! #     error::AnnotatedError,
58//! # };
59//! #
60//! fn space<'a>(input: SpannedStr<'a>) -> Result<SpannedStr<'a>, AnnotatedError> {
61//!     let first_char = input.content().chars().next();
62//!
63//!     let first_char = match first_char {
64//!         Some(chr) => chr,
65//!         None => {
66//!             let report = AnnotatedError::new(
67//!                 input.span(),
68//!                 "Expected ` `, found EOF"
69//!             );
70//!             return Err(report);
71//!         },
72//!     };
73//!
74//!     if first_char != ' ' {
75//!         let err_span = input.split_at(first_char.len_utf8()).0;
76//!
77//!         let report = AnnotatedError::new(err_span.span(), "Expected ` `.")
78//!             .with_annotation(
79//!                 err_span.span(),
80//!                 format!("Expected ` `, found `{}`.", err_span.content()),
81//!             );
82//!
83//!         return Err(report);
84//!     }
85//!
86//!     let (_, tail) = input.split_at(1);
87//!     Ok(tail)
88//! }
89//! ```
90//!
91//! Once we can parse both a number and a space, let's define `ssn` (short for
92//! "space-separated numbers"), that will call our parsers in the correct order:
93//!
94//! ```rust
95//! # use lisbeth_error::{
96//! #     span::{Span, SpannedStr},
97//! #     error::AnnotatedError,
98//! # };
99//! #
100//! # struct Number;
101//! #
102//! # fn number<'a>(input: SpannedStr<'a>) -> Result<(Number, SpannedStr<'a>), AnnotatedError> {
103//! #     todo!();
104//! # }
105//! #
106//! # fn space<'a>(input: SpannedStr<'a>) -> Result<SpannedStr<'a>, AnnotatedError> {
107//! #     todo!();
108//! # }
109//! fn ssn<'a>(mut input: SpannedStr<'a>) -> Result<Vec<Number>, AnnotatedError> {
110//!     let mut nbrs = Vec::new();
111//!
112//!     while !input.content().is_empty() {
113//!         let (nbr, tail) = number(input)?;
114//!         nbrs.push(nbr);
115//!
116//!         // Return if nothing is left
117//!         if tail.content().is_empty() {
118//!             break;
119//!         }
120//!
121//!         let tail = space(input)?;
122//!
123//!         input = tail;
124//!     }
125//!    
126//!     Ok(nbrs)
127//! }
128//! ```
129//!
130//! Let's test `ssn`:
131//!
132//! ```rust
133//! # use lisbeth_error::{
134//! #     span::{Span, SpannedStr},
135//! #     error::AnnotatedError,
136//! # };
137//! #
138//! # #[derive(Debug, PartialEq)]
139//! # struct Number {
140//! #     span: Span,
141//! #     value: u32,
142//! # }
143//! #
144//! # impl PartialEq<u32> for Number {
145//! #    fn eq(&self, other: &u32) -> bool {
146//! #        self.value == *other
147//! #    }
148//! # }
149//! #
150//! # fn number<'a>(input: SpannedStr<'a>) -> Result<(Number, SpannedStr<'a>), AnnotatedError> {
151//! #     let (matched, tail) = input.take_while(char::is_numeric);
152//! #
153//! #     if matched.content().is_empty() {
154//! #         let mut first = true;
155//! #         let err_span = input.take_while(|c| {
156//! #             let tmp = !c.is_whitespace() || first;
157//! #             first = false;
158//! #             tmp
159//! #         }).0;
160//! #
161//! #         let report = AnnotatedError::new(err_span.span(), "Expected number")
162//! #             .with_annotation(
163//! #                 err_span.span(),
164//! #                 format!("Expected number, found `{}`.", err_span.content()),
165//! #             );
166//! #
167//! #         return Err(report);
168//! #     }
169//! #
170//! #     let value = matched.content().parse().unwrap();
171//! #     let span = matched.span();
172//! #
173//! #     let number = Number { span, value };
174//! #
175//! #     Ok((number, tail))
176//! # }
177//! #
178//! # fn space<'a>(input: SpannedStr<'a>) -> Result<SpannedStr<'a>, AnnotatedError> {
179//! #     let first_char = input.content().chars().next();
180//! #
181//! #     let first_char = match first_char {
182//! #         Some(chr) => chr,
183//! #         None => {
184//! #             let report = AnnotatedError::new(input.span(), "Expected ` `, found EOF.");
185//! #             return Err(report);
186//! #         },
187//! #     };
188//! #
189//! #     if first_char != ' ' {
190//! #         let err_span = input.split_at(first_char.len_utf8()).0;
191//! #
192//! #         let report = AnnotatedError::new(err_span.span(), "Expected ` `")
193//! #             .with_annotation(
194//! #                 err_span.span(),
195//! #                 format!("Expected ` `, found `{}`.", err_span.content()),
196//! #             );
197//! #
198//! #         return Err(report);
199//! #     }
200//! #
201//! #     let (_, tail) = input.split_at(1);
202//! #     Ok(tail)
203//! # }
204//! # fn ssn<'a>(mut input: SpannedStr<'a>) -> Result<Vec<Number>, AnnotatedError> {
205//! #     let mut nbrs = Vec::new();
206//! #
207//! #     while !input.content().is_empty() {
208//! #         let (nbr, tail) = number(input)?;
209//! #         nbrs.push(nbr);
210//! #
211//! #         // Return if nothing is left
212//! #         if tail.content().is_empty() {
213//! #             break;
214//! #         }
215//! #
216//! #         let tail = space(tail)?;
217//! #
218//! #         input = tail;
219//! #     }
220//! #
221//! #     Ok(nbrs)
222//! # }
223//! #
224//! let input = SpannedStr::input_file("42 101 13");
225//! assert_eq!(ssn(input).unwrap(), [42, 101, 13]);
226//! ```
227//!
228//! Now we need to be able to display errors to stderr:
229//! ```rust
230//! # use lisbeth_error::{
231//! #     span::{Span, SpannedStr},
232//! #     error::AnnotatedError,
233//! # };
234//! #
235//! # #[derive(Debug, PartialEq)]
236//! # struct Number {
237//! #     span: Span,
238//! #     value: u32,
239//! # }
240//! #
241//! # impl PartialEq<u32> for Number {
242//! #    fn eq(&self, other: &u32) -> bool {
243//! #        self.value == *other
244//! #    }
245//! # }
246//! #
247//! # fn number<'a>(input: SpannedStr<'a>) -> Result<(Number, SpannedStr<'a>), AnnotatedError> {
248//! #     let (matched, tail) = input.take_while(char::is_numeric);
249//! #
250//! #     if matched.content().is_empty() {
251//! #         let mut first = true;
252//! #         let err_span = input.take_while(|c| {
253//! #             let tmp = !c.is_whitespace() || first;
254//! #             first = false;
255//! #             tmp
256//! #         }).0;
257//! #
258//! #         let report = AnnotatedError::new(err_span.span(), "Expected number")
259//! #             .with_annotation(
260//! #                 err_span.span(),
261//! #                 format!("Expected number, found `{}`.", err_span.content()),
262//! #             );
263//! #
264//! #         return Err(report);
265//! #     }
266//! #
267//! #     let value = matched.content().parse().unwrap();
268//! #     let span = matched.span();
269//! #
270//! #     let number = Number { span, value };
271//! #
272//! #     Ok((number, tail))
273//! # }
274//! #
275//! # fn space<'a>(input: SpannedStr<'a>) -> Result<SpannedStr<'a>, AnnotatedError> {
276//! #     let first_char = input.content().chars().next();
277//! #
278//! #     let first_char = match first_char {
279//! #         Some(chr) => chr,
280//! #         None => {
281//! #             let report = AnnotatedError::new(input.span(), "Expected ` `, found EOF.");
282//! #             return Err(report);
283//! #         },
284//! #     };
285//! #
286//! #     if first_char != ' ' {
287//! #         let err_span = input.split_at(first_char.len_utf8()).0;
288//! #
289//! #         let report = AnnotatedError::new(err_span.span(), "Expected ` `")
290//! #             .with_annotation(
291//! #                 err_span.span(),
292//! #                 format!("Expected ` `, found `{}`.", err_span.content()),
293//! #             );
294//! #
295//! #         return Err(report);
296//! #     }
297//! #
298//! #     let (_, tail) = input.split_at(1);
299//! #     Ok(tail)
300//! # }
301//! # fn ssn<'a>(mut input: SpannedStr<'a>) -> Result<Vec<Number>, AnnotatedError> {
302//! #     let mut nbrs = Vec::new();
303//! #
304//! #     while !input.content().is_empty() {
305//! #         let (nbr, tail) = number(input)?;
306//! #         nbrs.push(nbr);
307//! #
308//! #         // Return if nothing is left
309//! #         if tail.content().is_empty() {
310//! #             break;
311//! #         }
312//! #
313//! #         let tail = space(tail)?;
314//! #
315//! #         input = tail;
316//! #     }
317//! #
318//! #     Ok(nbrs)
319//! # }
320//! use lisbeth_error::reporter::ErrorReporter;
321//!
322//! // We intentionnaly don't return anything, so that the code stays as short
323//! // as possible.
324//! fn parse(file_name: String, content: String) {
325//!     let file = ErrorReporter::input_file(
326//!         file_name.to_string(),
327//!         content.to_string(),
328//!     );
329//!
330//!     match ssn(file.spanned_str()) {
331//!         Ok(numbers) => println!("Parsing successfull"),
332//!         Err(e) => eprintln!("{}", file.format_error(&e)),
333//!
334//!     }
335//! }
336//! ```
337//!
338//! # Error produced
339//!
340//! When the input file contains a word:
341//!
342//! ```none
343//! Error: Expected number
344//!  --> numbers.ssn:1:7
345//!      |
346//!    1 |                               42 31 abc 101
347//!      |                                     ^^^
348//!      | Expected number, found `abc`.-------'
349//!      |
350//! ```
351//!
352//! When the input file contains too much space:
353//!
354//! ```none
355//! Error: Expected number
356//!  --> numbers.ssn:1:4
357//!      |
358//!    1 |                               42  31 101
359//!      |                                  ^^^
360//!      | Expected number, found ` 31`.----'
361//!      |
362//! ```
363//!
364//! The `found ' 31'` message can be improved by determining a better
365//! `err_span` in the `number` function.