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 Diagnostics in the ir module:

fn main() {
pub struct Diagnostics(Diagnostic);

#[derive(new, Clone, Debug)]
pub struct Diagnostic {
    pub start: usize,
    pub end: 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 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 pushing a diagnostic:

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(),

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

fn main() {
let accumulated: Vec<Diagnostic> =
                      //            -----------
                      //     Use turbofish to specify
                      //     the diagnostics type.

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