Exercise: Rewriting with Result

The following implements a very simple parser for an expression language. However, it handles errors by panicking. Rewrite it to instead use idiomatic error handling and propagate errors to a return from main. Feel free to use thiserror and anyhow.

Hint: start by fixing error handling in the parse function. Once that is working correctly, update Tokenizer to implement Iterator<Item=Result<Token, TokenizerError>> and handle that in the parser.

  1. use std::iter::Peekable;
  2. use std::str::Chars;
  3. /// An arithmetic operator.
  4. #[derive(Debug, PartialEq, Clone, Copy)]
  5. enum Op {
  6.     Add,
  7.     Sub,
  8. }
  9. /// A token in the expression language.
  10. #[derive(Debug, PartialEq)]
  11. enum Token {
  12.     Number(String),
  13.     Identifier(String),
  14.     Operator(Op),
  15. }
  16. /// An expression in the expression language.
  17. #[derive(Debug, PartialEq)]
  18. enum Expression {
  19.     /// A reference to a variable.
  20.     Var(String),
  21.     /// A literal number.
  22.     Number(u32),
  23.     /// A binary operation.
  24.     Operation(Box<Expression>, Op, Box<Expression>),
  25. }
  26. fn tokenize(input: &str) -> Tokenizer {
  27.     return Tokenizer(input.chars().peekable());
  28. }
  29. struct Tokenizer<'a>(Peekable<Chars<'a>>);
  30. impl<'a> Tokenizer<'a> {
  31.     fn collect_number(&mut self, first_char: char) -> Token {
  32.         let mut num = String::from(first_char);
  33.         while let Some(&c @ '0'..='9') = self.0.peek() {
  34.             num.push(c);
  35.             self.0.next();
  36.         }
  37.         Token::Number(num)
  38.     }
  39.     fn collect_identifier(&mut self, first_char: char) -> Token {
  40.         let mut ident = String::from(first_char);
  41.         while let Some(&c @ ('a'..='z' | '_' | '0'..='9')) = self.0.peek() {
  42.             ident.push(c);
  43.             self.0.next();
  44.         }
  45.         Token::Identifier(ident)
  46.     }
  47. }
  48. impl<'a> Iterator for Tokenizer<'a> {
  49.     type Item = Token;
  50.     fn next(&mut self) -> Option<Token> {
  51.         let c = self.0.next()?;
  52.         match c {
  53.             '0'..='9' => Some(self.collect_number(c)),
  54.             'a'..='z' => Some(self.collect_identifier(c)),
  55.             '+' => Some(Token::Operator(Op::Add)),
  56.             '-' => Some(Token::Operator(Op::Sub)),
  57.             _ => panic!("Unexpected character {c}"),
  58.         }
  59.     }
  60. }
  61. fn parse(input: &str) -> Expression {
  62.     let mut tokens = tokenize(input);
  63.     fn parse_expr<'a>(tokens: &mut Tokenizer<'a>) -> Expression {
  64.         let Some(tok) = tokens.next() else {
  65.             panic!("Unexpected end of input");
  66.         };
  67.         let expr = match tok {
  68.             Token::Number(num) => {
  69.                 let v = num.parse().expect("Invalid 32-bit integer");
  70.                 Expression::Number(v)
  71.             }
  72.             Token::Identifier(ident) => Expression::Var(ident),
  73.             Token::Operator(_) => panic!("Unexpected token {tok:?}"),
  74.         };
  75.         // Look ahead to parse a binary operation if present.
  76.         match tokens.next() {
  77.             None => expr,
  78.             Some(Token::Operator(op)) => Expression::Operation(
  79.                 Box::new(expr),
  80.                 op,
  81.                 Box::new(parse_expr(tokens)),
  82.             ),
  83.             Some(tok) => panic!("Unexpected token {tok:?}"),
  84.         }
  85.     }
  86.     parse_expr(&mut tokens)
  87. }
  88. fn main() {
  89.     let expr = parse("10+foo+20-30");
  90.     println!("{expr:?}");
  91. }