Expand description
A simple example explaining how to parse input data and emit errors.
This page shows how to parse a sequence of space-separated decimal numbers.
Let’s first define the Number type, and a simple parser for it:
use lisbeth_error::{
span::{Span, SpannedStr},
error::AnnotatedError,
};
struct Number {
span: Span,
value: u32,
}
// This will be usefull for our tests
impl PartialEq<u32> for Number {
fn eq(&self, other: &u32) -> bool {
self.value == *other
}
}
fn number<'a>(input: SpannedStr<'a>) -> Result<(Number, SpannedStr<'a>), AnnotatedError> {
let (matched, tail) = input.take_while(char::is_numeric);
if matched.content().is_empty() {
let mut first = true;
let err_span = input.take_while(|c| {
let tmp = !c.is_whitespace() || first;
first = false;
tmp
}).0;
let report = AnnotatedError::new(err_span.span(), "Expected number")
.with_annotation(
err_span.span(),
format!("Expected number, found `{}`.", err_span.content()),
);
return Err(report);
}
let value = matched.content().parse().unwrap();
let span = matched.span();
let number = Number { span, value };
Ok((number, tail))
}Now, we need to parse spaces. Let’s do so:
fn space<'a>(input: SpannedStr<'a>) -> Result<SpannedStr<'a>, AnnotatedError> {
let first_char = input.content().chars().next();
let first_char = match first_char {
Some(chr) => chr,
None => {
let report = AnnotatedError::new(
input.span(),
"Expected ` `, found EOF"
);
return Err(report);
},
};
if first_char != ' ' {
let err_span = input.split_at(first_char.len_utf8()).0;
let report = AnnotatedError::new(err_span.span(), "Expected ` `.")
.with_annotation(
err_span.span(),
format!("Expected ` `, found `{}`.", err_span.content()),
);
return Err(report);
}
let (_, tail) = input.split_at(1);
Ok(tail)
}Once we can parse both a number and a space, let’s define ssn (short for
“space-separated numbers”), that will call our parsers in the correct order:
fn ssn<'a>(mut input: SpannedStr<'a>) -> Result<Vec<Number>, AnnotatedError> {
let mut nbrs = Vec::new();
while !input.content().is_empty() {
let (nbr, tail) = number(input)?;
nbrs.push(nbr);
// Return if nothing is left
if tail.content().is_empty() {
break;
}
let tail = space(input)?;
input = tail;
}
Ok(nbrs)
}Let’s test ssn:
let input = SpannedStr::input_file("42 101 13");
assert_eq!(ssn(input).unwrap(), [42, 101, 13]);Now we need to be able to display errors to stderr:
use lisbeth_error::reporter::ErrorReporter;
// We intentionnaly don't return anything, so that the code stays as short
// as possible.
fn parse(file_name: String, content: String) {
let file = ErrorReporter::input_file(
file_name.to_string(),
content.to_string(),
);
match ssn(file.spanned_str()) {
Ok(numbers) => println!("Parsing successfull"),
Err(e) => eprintln!("{}", file.format_error(&e)),
}
}§Error produced
When the input file contains a word:
Error: Expected number
--> numbers.ssn:1:7
|
1 | 42 31 abc 101
| ^^^
| Expected number, found `abc`.-------'
|When the input file contains too much space:
Error: Expected number
--> numbers.ssn:1:4
|
1 | 42 31 101
| ^^^
| Expected number, found ` 31`.----'
|The found ' 31' message can be improved by determining a better
err_span in the number function.