Module handbook

Module handbook 

Source
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.