定义解析器:报告错误
解析器里最后一个有意思的问题,是如何处理解析错误。
因为 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。