Exercício: Reescrevendo com Result
O seguinte implementa um analisador muito simples para uma linguagem de expressão. No entanto, ele lida com erros disparando um pânico. Reescreva-o para usar o tratamento de erros idiomático e propagar erros para um retorno de main. Sinta-se à vontade para usar thiserror e anyhow.
DICA: comece corrigindo o tratamento de erros na função parse. Depois que isso estiver funcionando corretamente, atualize Tokenizer para implementar Iterator<Item=Result<Token, TokenizerError>> e trate isso no analisador (parser).
use std::iter::Peekable;
use std::str::Chars;
/// Um operador aritmético.
#[derive(Debug, PartialEq, Clone, Copy)]
enum Op {
Add,
Sub,
}
/// Um token na linguagem de expressão.
#[derive(Debug, PartialEq)]
enum Token {
Number(String),
Identifier(String),
Operator(Op),
}
/// Uma expressão na linguagem de expressão.
#[derive(Debug, PartialEq)]
enum Expression {
/// Uma referência a uma variável.
Var(String),
/// Um número literal.
Number(u32),
/// Uma operação binária.
Operation(Box<Expression>, Op, Box<Expression>),
}
fn tokenize(input: &str) -> Tokenizer {
return Tokenizer(input.chars().peekable());
}
struct Tokenizer<'a>(Peekable<Chars<'a>>);
impl<'a> Tokenizer<'a> {
fn collect_number(&mut self, first_char: char) -> Token {
let mut num = String::from(first_char);
while let Some(&c @ '0'..='9') = self.0.peek() {
num.push(c);
self.0.next();
}
Token::Number(num)
}
fn collect_identifier(&mut self, first_char: char) -> Token {
let mut ident = String::from(first_char);
while let Some(&c @ ('a'..='z' | '_' | '0'..='9')) = self.0.peek() {
ident.push(c);
self.0.next();
}
Token::Identifier(ident)
}
}
impl<'a> Iterator for Tokenizer<'a> {
type Item = Token;
fn next(&mut self) -> Option<Token> {
let c = self.0.next()?;
match c {
'0'..='9' => Some(self.collect_number(c)),
'a'..='z' => Some(self.collect_identifier(c)),
'+' => Some(Token::Operator(Op::Add)),
'-' => Some(Token::Operator(Op::Sub)),
_ => panic!("Caractere inesperado {c}"),
}
}
}
fn parse(input: &str) -> Expression {
let mut tokens = tokenize(input);
fn parse_expr<'a>(tokens: &mut Tokenizer<'a>) -> Expression {
let Some(tok) = tokens.next() else {
panic!("Fim de entrada inesperado");
};
let expr = match tok {
Token::Number(num) => {
let v = num.parse().expect("Inteiro de 32 bits inválido'");
Expression::Number(v)
}
Token::Identifier(ident) => Expression::Var(ident),
Token::Operator(_) => panic!("Token inesperado {tok:?}"),
};
// Olhe adiante para analisar uma operação binária, se presente.
match tokens.next() {
None => expr,
Some(Token::Operator(op)) => Expression::Operation(
Box::new(expr),
op,
Box::new(parse_expr(tokens)),
),
Some(tok) => panic!("Token inesperado {tok:?}"),
}
}
parse_expr(&mut tokens)
}
fn main() {
let expr = parse("10+foo+20-30");
println!("{expr:?}");
}