跳到內容

處理錯誤

引用自龍書

大多數程式語言規範並未描述編譯器應如何回應錯誤;錯誤處理留給編譯器設計者。從一開始就規劃好錯誤處理,既可以簡化編譯器的結構,又可以改善其對錯誤的處理。

一個完全可恢復的解析器,無論我們丟給它什麼,都可以建構出 AST。對於程式碼檢查器或格式化程式等工具,人們會希望有一個完全可恢復的解析器,以便我們可以對程式的部分內容進行操作。

一個會發生錯誤的解析器,如果存在任何語法不匹配,就會中止;而部分可恢復的解析器,則會從確定性的語法中恢復。

例如,給定一個語法不正確的 while 陳述式 while true {},我們知道它缺少圓括號,而且它唯一能有的標點符號是圓括號,所以我們仍然可以傳回有效的 AST 並指出它缺少括號。

大多數現有的 JavaScript 解析器都是部分可恢復的,所以我們也會做同樣的事情,建構一個部分可恢復的解析器。

資訊

Biome 解析器是一個完全可恢復的解析器。

Rust 具有 Result 類型,用於傳回和傳播錯誤。結合 ? 語法,解析函數將保持簡單且乾淨。

通常會包裝 Result 類型,以便我們可以稍後替換錯誤

rust
pub type Result<T> = std::result::Result<T, ()>;

我們的解析函數將傳回 Result,例如

rust
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 函數,如果目前的 token 與語法不符,則傳回錯誤

rust
/// Expect a `Kind` or return error
pub fn expect(&mut self, kind: Kind) -> Result<()> {
    if !self.at(kind) {
        return Err(())
    }
    self.advance();
    Ok(())
}

並像這樣使用它

rust
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 特徵

為了傳回特定的錯誤,我們需要填寫 ResultErr 部分

rust
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 使用巨集

rust
#[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 輔助函數,如果 token 不匹配,則拋出錯誤

rust
/// 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 函數進行正確的錯誤管理

rust
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 是最出色的錯誤報告 crate 之一,它提供花俏的彩色輸出

miette

miette 加入您的 Cargo.toml

toml
[dependencies]
miette = { version = "5", features = ["fancy"] }

我們可以使用 miette 包裝我們的 Error,而不修改解析器中定義的 Result 類型

rust
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))
    })
}

以 MIT 授權發布。