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, but not on subsequent calls in the situation where the simply returns its memoized value.

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

#![allow(unused)]
fn main() {
#[salsa::accumulator]
#[derive(Debug)]
#[allow(dead_code)] // Debug impl uses them
pub struct Diagnostic {
    pub start: usize,
    pub end: usize,
    pub message: String,
}
}

Memoized functions can accumulate Diagnostic values. Later, you can invoke a method to find all the values that were accumulated by the tracked functions or any functions that they 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 accumulating a diagnostic:

#![allow(unused)]
fn main() {
    /// Report an error diagnostic at the current position.
    fn report_error(&self) {
        let next_position = match self.peek() {
            Some(ch) => self.position + ch.len_utf8(),
            None => self.position,
        };
        Diagnostic {
            start: self.position,
            end: next_position,
            message: "unexpected character".to_string(),
        }
        .accumulate(self.db);
    }
}

To get the diagnostics produced by parse_statements, or any other tracked function, we invoke the associated accumulated function:

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

accumulated takes the database followed by the tracked function's query arguments and returns a Vec of references.