Lidando com erros
Citando o Dragon Book:
A maior parte das especificações não diz como o compilador deve reagir a erros; isso fica para o projetista. Planejar erro desde o início simplifica o compilador e melhora mensagens/caminhos de recuperação.
Parser totalmente recuperável: monta AST quase sempre. Ferramentas como linter ou formatter adoram — dá para agir mesmo com trechos inválidos.
Parser que desiste no primeiro problema vs. recuperação parcial em casos previsíveis.
Exemplo: while true {} falta parênteses — sabemos quais símbolos cabem; podemos montar AST e marcar erro de forma pontual.
A maioria dos parsers JS faz recuperação parcial — seguiremos o mesmo modelo.
INFO
O parser do Biome é totalmente recuperável.
Rust oferece Result + ? para manter helpers legíveis.
Costuma-se tipar assim até escolher o erro final:
pub type Result<T> = std::result::Result<T, ()>;Funções retornando Result, por exemplo:
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(()),
}
}Helper expect quando o token precisa coincidir:
/// 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)
}INFO
Por simetria, read_next_token no léxico também pode retornar Result se encontrar byte inesperado.
Trait Error
Preencha a parte Err de Result com tipos concretos:
pub type Result<T> = std::result::Result<T, SyntaxError>;
^^^^^^^^^^^
#[derive(Debug)]
pub enum SyntaxError {
UnexpectedToken(String),
AutoSemicolonInsertion(String),
UnterminatedMultiLineComment(String),
}Chamamos de SyntaxError porque todos os early errors gramaticais da ECMA são erros sintáticos.
Implemente Error; com macros do thiserror o código fica mais limpo:
#[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,
}Então você pode usar um expect que propaga erro se o token não bater:
/// 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(())
}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),
})
}Observe o ? após expect: é o question mark operator para retornar cedo quando expect devolver Err.
Relatório bonito (miette)
miette imprime falhas bonitas:

[dependencies]
miette = { version = "5", features = ["fancy"] }Encapsula o erro sem mudar todas as assinaturas:
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))
})
}