Solution

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