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.