处理错误
引用自 龙书
大多数编程语言的规范并未描述编译器对错误的响应方式;错误处理由编译器设计者自行决定。 从一开始就规划好错误处理,既能简化编译器的结构,又能提升其错误处理能力。
一个完全可恢复的解析器无论遇到什么输入都能构建出抽象语法树(AST)。 对于诸如代码检查器或格式化工具这类工具,我们希望拥有一个完全可恢复的解析器,以便能够对程序的部分内容进行操作。
一个引发恐慌的解析器在遇到任何语法不匹配时都会终止运行,而部分可恢复的解析器则能从确定性文法中恢复。
例如,对于语法错误的 while true {} 语句,我们知道它缺少圆括号, 且该语句只能包含圆括号作为标点符号,因此我们仍可以返回一个有效的 AST 并指出其缺失的括号。
目前大多数 JavaScript 解析器都是部分可恢复的,因此我们也采用相同策略,构建一个部分可恢复的解析器。
INFO
Biome 的解析器是一个完全可恢复的解析器。
Rust 使用 Result 类型来返回和传播错误。 结合 ? 语法,解析函数将保持简洁明了。
通常我们会封装 Result 类型,以便后续替换错误:
pub type Result<T> = std::result::Result<T, ()>;我们的解析函数将返回 Result,例如:
pub fn parse_binding_pattern(&mut self, ctx: Context) -> Result<BindingPattern<'a>> {
match self.cur_kind() {
Kind::LCurly => self.parse_object_binding_pattern(ctx),
Kind::LBrack => self.parse_array_binding_pattern(ctx),
kind if kind.is_binding_identifier() => {
// ... 省略代码
}
_ => Err(()),
}
}我们可以添加一个 expect 函数,用于在当前标记不符合文法时返回错误:
/// 期望一个 `Kind`,否则返回错误
pub fn expect(&mut self, kind: Kind) -> Result<()> {
if !self.at(kind) {
return Err(())
}
self.advance();
Ok(())
}然后这样使用:
pub fn parse_paren_expression(&mut self, ctx: Context) -> Result<Expression> {
self.expect(Kind::LParen)?;
let expression = self.parse_expression(ctx)?;
self.expect(Kind::RParen)?;
Ok(expression)
}INFO
为完整性考虑,当词法分析过程中遇到意外的 char 时,词法分析函数 read_next_token 也应返回 Result。
Error 特征
为了返回具体的错误,我们需要填充 Result 中的 Err 部分:
pub type Result<T> = std::result::Result<T, SyntaxError>;
^^^^^^^^^^^
#[derive(Debug)]
pub enum SyntaxError {
UnexpectedToken(String),
AutoSemicolonInsertion(String),
UnterminatedMultiLineComment(String),
}我们将其命名为 SyntaxError,因为 ECMAScript 规范语法部分定义的所有“早期错误”都属于语法错误。
为了让它成为一个真正的 Error,需要实现 Error 特征。为了代码更清晰,我们可以使用 thiserror crate 提供的宏:
#[derive(Debug, Error)]
pub enum SyntaxError {
#[error("意外的标记")]
UnexpectedToken,
#[error("期望在语句后有一个分号或隐式分号,但未找到")]
AutoSemicolonInsertion,
#[error("未终止的多行注释")]
UnterminatedMultiLineComment,
}然后我们可以添加一个 expect 辅助函数,用于在标记不匹配时抛出错误:
/// 期望一个 `Kind`,否则返回错误
pub fn expect(&mut self, kind: Kind) -> Result<()> {
if self.at(kind) {
return Err(SyntaxError::UnexpectedToken);
}
self.advance(kind);
Ok(())
}现在 parse_debugger_statement 可以使用 expect 函数来正确管理错误:
fn parse_debugger_statement(&mut self) -> Result<Statement> {
let node = self.start_node();
self.expect(Kind::Debugger)?;
Ok(Statement::DebuggerStatement {
node: self.finish_node(node),
})
}注意 expect 后面的 ?, 这是名为“问号运算符”的语法糖,用于在 expect 函数返回 Err 时提前返回函数。
精美的错误报告
miette 是最出色的错误报告库之一, 它提供了一种美观的彩色输出

将 miette 添加到你的 Cargo.toml 中
[dependencies]
miette = { version = "5", features = ["fancy"] }我们可以用 miette 包装我们的 Error,而不修改解析器中定义的 Result 类型:
pub fn main() -> Result<()> {
let source_code = "".to_string();
let file_path = "test.js".to_string();
let mut parser = Parser::new(&source_code);
parser.parse().map_err(|error| {
miette::Error::new(error).with_source_code(miette::NamedSource::new(file_path, source_code))
})
}