错误处理
引用自 《编译原理与技术》
大多数编程语言的规范未描述编译器对错误的响应方式;错误处理由编译器设计人员决定。从一开始就规划好错误处理既可简化编译器的结构,又能改进其对错误的处理。
无论我们抛出什么,一个完全可恢复的解析器都可以构建 AST。对于诸如 Linter 或格式化程序之类的工具,人们希望有一个完全可恢复的解析器,以便我们能够处理程序的一部分。
一个恐慌的解析器在发生任何语法不匹配时都会中止,而一个部分可恢复的解析器会从确定性语法中恢复。
例如,给定语法上有误的 while 语句 while true {}
,我们知道它缺少圆括号,且它唯一可以有的标点符号就是圆括号,因此我们仍然可以返回一个有效的 AST 并指出其缺少的括号。
目前大多数 JavaScript 解析器都是部分可恢复的,因此我们也会这样做并构建一个部分可恢复的解析器。
信息
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() => {
// ... code omitted
}
_ => Err(()),
}
}
我们可以添加一个 expect
函数,如果当前标记与语法不匹配,则返回错误
/// Expect a `Kind` or return error
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)
}
信息
为了完整性,词法分析函数 read_next_token
也应该在词法分析时找到意外的 char
时返回 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
箱子的宏
#[derive(Debug, Error)]
pub enum SyntaxError {
#[error("Unexpected Token")]
UnexpectedToken,
#[error("Expected a semicolon or an implicit semicolon after a statement, but found none")]
AutoSemicolonInsertion,
#[error("Unterminated multi-line comment")]
UnterminatedMultiLineComment,
}
然后我们可以添加一个 expect
助手函数,以便在令牌不匹配时抛出错误
/// Expect a `Kind` or return error
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))
})
}