定义解析器:报告错误

解析器里最后一个有意思的问题,是如何处理解析错误。 因为 Salsa 函数是会缓存的,而且可能根本不会执行, 所以它们不应该产生副作用, 因此我们不能直接调用 eprintln!。 如果这么做,错误只会在函数第一次执行时被打印, 而在后续直接返回缓存值的调用里就不会再出现。

Salsa 为此提供了一种机制,叫做累加器(accumulator)。 在我们的例子里,我们在 ir 模块中定义了一个名为 Diagnostics 的累加器结构体:

#![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,
}
}

累加器结构体总是一个只有单个字段的 newtype, 这里字段类型是 Diagnostic。 记忆化函数可以把 Diagnostic 值 push 到这个累加器里。 之后,你可以通过一个方法找出这些记忆化函数及其调用的函数所 push 的所有值。 例如,我们就可以拿到 parse_statements 产生的那组 Diagnostic 值。

Parser::report_error 方法里就有一个 push 诊断信息的例子:

#![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);
    }
}

要获取 parse_statements 或其他记忆化函数产生的诊断集合,

#![allow(unused)]
fn main() {
let accumulated: Vec<Diagnostic> =
    parse_statements::accumulated::<Diagnostics>(db);
                      //            -----------
                      //     用 turbofish 指定
                      //     诊断类型。
}

accumulated 以数据库 db 为参数,并返回一个 Vec