Defining the parser: reporting errors

The last interesting case in the parser is how to handle a parse error. Because salsa functions are memoized and may not execute, they should not have side-effects, so we don't just want to call eprintln!. If we did so, the error would only be reported the first time the function was called.

Salsa defines a mechanism for managing this called an accumulator. In our case, we define an accumulator struct called Diagnostics in the ir module:


#![allow(unused)]
fn main() {
#[salsa::accumulator]
pub struct Diagnostics(Diagnostic);

#[derive(Clone, Debug)]
pub struct Diagnostic {
    pub position: usize,
    pub message: String,
}
}

Accumulator structs are always newtype structs with a single field, in this case of type Diagnostic. Memoized functions can push Diagnostic values onto the accumulator. Later, you can invoke a method to find all the values that were pushed by the memoized functions or any function that it called (e.g., we could get the set of Diagnostic values produced by the parse_statements function).

The Parser::report_error method contains an example of pushing a diagnostic:


#![allow(unused)]
fn main() {
    /// Report an error diagnostic at the current position.
    fn report_error(&self) {
        Diagnostics::push(
            self.db,
            Diagnostic {
                position: self.position,
                message: "unexpected character".to_string(),
            },
        );
    }
}

To get the set of diagnostics produced by parse_errors, or any other memoized function, we invoke the associated accumulated function:


#![allow(unused)]
fn main() {
let accumulated: Vec<Diagnostic> =
    parse_statements::accumulated::<Diagnostics>(db);
                      //            -----------
                      //     Use turbofish to specify
                      //     the diagnostics type.
}

accumulated takes the database db as argument and returns a Vec.