From a9f737a1ad02a8f7295bf830c100f0a24a980a3e Mon Sep 17 00:00:00 2001 From: Camden Dixie O'Brien Date: Sat, 26 Sep 2026 09:42:37 +0100 Subject: [PATCH] Implement parser using Pratt parsing --- src/lib.rs | 1 + src/parse.rs | 487 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/token.rs | 11 ++ 3 files changed, 499 insertions(+) create mode 100644 src/parse.rs diff --git a/src/lib.rs b/src/lib.rs index 4139ad5..2701531 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,3 +3,4 @@ pub mod expression; pub mod binding; pub mod lex; +pub mod parse; diff --git a/src/parse.rs b/src/parse.rs new file mode 100644 index 0000000..b177bc0 --- /dev/null +++ b/src/parse.rs @@ -0,0 +1,487 @@ +use crate::token::Token; +use crate::expression::Expression; +use crate::binding::{Binding, Power}; + +#[derive(Debug)] +pub enum Error { + Io(std::io::Error), + InvalidSyntax, +} + +pub fn parse(input: I) -> Result +where I: Iterator> +{ + expr(&mut input.peekable(), Binding::left(Power::Minimum)) +} + +fn expr( + input: &mut std::iter::Peekable, + limit: Binding) -> Result +where I: Iterator> +{ + let t1 = input.next() + .ok_or(Error::InvalidSyntax)? + .map_err(|err| Error::Io(err))?; + let mut left = nud(input, t1)?; + + loop { + match input.peek() { + Some(Ok(op)) => { + if Binding::of_operator(op) > limit { + let op_copy = op.clone(); + left = led(input, left, op_copy)?; + } else { + break Ok(left) + } + }, + Some(Err(_)) => { + let err = input.next().unwrap().unwrap_err(); + break Err(Error::Io(err)); + }, + None => break Ok(left), + } + } +} + +fn nud( + input: &mut std::iter::Peekable, + token: Token) -> Result +where I: Iterator> +{ + match token { + Token::Symbol(c) => Ok(Expression::Symbol(c)), + Token::Integer(x) => Ok(Expression::Integer(x as i32)), + Token::Plus => expr(input, Binding::right(Power::Multiplication)), + Token::Minus => { + let operand = + expr(input, Binding::right(Power::Multiplication))?; + Ok(match operand { + Expression::Integer(x) => Expression::Integer(-x), + _ => Expression::product(Expression::Integer(-1), operand), + }) + } + Token::OpenParen => { + let operand = expr(input, Binding::left(Power::Minimum))?; + match input.next() { + Some(Ok(Token::CloseParen)) => Ok(operand), + _ => Err(Error::InvalidSyntax), + } + } + _ => Err(Error::InvalidSyntax), + } +} + +fn led( + input: &mut std::iter::Peekable, + left: Expression, + op: Token) -> Result +where I: Iterator> +{ + if op.starts_expression() { + let right = expr(input, Binding::of_operator(&op))?; + Ok(Expression::product(left, right)) + } else { + input.next(); + let right = expr(input, Binding::of_operator(&op))?; + Ok(match op { + Token::Plus => Expression::sum(left, right), + Token::Minus => Expression::difference(left, right), + Token::Times => Expression::product(left, right), + Token::Divide => Expression::quotient(left, right), + Token::Caret => Expression::exponent(left, right), + _ => unreachable!(), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn symbol_token_parses_to_symbol() { + let tokens = vec![ Token::Symbol('x') ]; + let expr = parse(tokens.into_iter().map(|t| Ok(t))).unwrap(); + assert_eq!(expr, Expression::Symbol('x')); + } + + #[test] + fn integer_token_parses_to_integer() { + let tokens = vec![ Token::Integer(42) ]; + let expr = parse(tokens.into_iter().map(|t| Ok(t))).unwrap(); + assert_eq!(expr, Expression::Integer(42)); + } + + #[test] + fn x_plus_y_parses_as_sum() { + let tokens = vec![ + Token::Symbol('x'), + Token::Plus, + Token::Symbol('y'), + ]; + let expr = parse(tokens.into_iter().map(|t| Ok(t))).unwrap(); + assert_eq!( + expr, + Expression::sum( + Expression::Symbol('x'), Expression::Symbol('y'))); + } + + #[test] + fn x_times_y_parses_as_product() { + let tokens = vec![ + Token::Symbol('x'), + Token::Times, + Token::Symbol('y'), + ]; + let expr = parse(tokens.into_iter().map(|t| Ok(t))).unwrap(); + assert_eq!( + expr, + Expression::product( + Expression::Symbol('x'), Expression::Symbol('y'))); + } + + #[test] + fn x_plus_y_times_z_parses_as_sum_of_product() { + let tokens = vec![ + Token::Symbol('x'), + Token::Plus, + Token::Symbol('y'), + Token::Times, + Token::Symbol('z'), + ]; + let expr = parse(tokens.into_iter().map(|t| Ok(t))).unwrap(); + assert_eq!( + expr, + Expression::sum( + Expression::Symbol('x'), + Expression::product( + Expression::Symbol('y'), Expression::Symbol('z')))); + } + + #[test] + fn x_times_y_plus_z_parses_as_sum_of_product() { + let tokens = vec![ + Token::Symbol('x'), + Token::Times, + Token::Symbol('y'), + Token::Plus, + Token::Symbol('z'), + ]; + let expr = parse(tokens.into_iter().map(|t| Ok(t))).unwrap(); + assert_eq!( + expr, + Expression::sum( + Expression::product( + Expression::Symbol('x'), Expression::Symbol('y')), + Expression::Symbol('z'))); + } + + #[test] + fn x_caret_y_times_z_parses_as_product_of_exponent() { + let tokens = vec![ + Token::Symbol('x'), + Token::Caret, + Token::Symbol('y'), + Token::Times, + Token::Symbol('z'), + ]; + let expr = parse(tokens.into_iter().map(|t| Ok(t))).unwrap(); + assert_eq!( + expr, + Expression::product( + Expression::exponent( + Expression::Symbol('x'), Expression::Symbol('y')), + Expression::Symbol('z'))); + } + + #[test] + fn x_minus_y_minus_z_parses_as_left_associative() { + let tokens = vec![ + Token::Symbol('x'), + Token::Minus, + Token::Symbol('y'), + Token::Minus, + Token::Symbol('z'), + ]; + let expr = parse(tokens.into_iter().map(|t| Ok(t))).unwrap(); + assert_eq!( + expr, + Expression::difference( + Expression::difference( + Expression::Symbol('x'), Expression::Symbol('y')), + Expression::Symbol('z'))); + } + + #[test] + fn x_divide_y_divide_z_parses_as_left_associative() { + let tokens = vec![ + Token::Symbol('x'), + Token::Divide, + Token::Symbol('y'), + Token::Divide, + Token::Symbol('z'), + ]; + let expr = parse(tokens.into_iter().map(|t| Ok(t))).unwrap(); + assert_eq!( + expr, + Expression::quotient( + Expression::quotient( + Expression::Symbol('x'), Expression::Symbol('y')), + Expression::Symbol('z'))); + } + + #[test] + fn x_caret_y_caret_z_parses_as_right_associative() { + let tokens = vec![ + Token::Symbol('x'), + Token::Caret, + Token::Symbol('y'), + Token::Caret, + Token::Symbol('z'), + ]; + let expr = parse(tokens.into_iter().map(|t| Ok(t))).unwrap(); + assert_eq!( + expr, + Expression::exponent( + Expression::Symbol('x'), + Expression::exponent( + Expression::Symbol('y'), Expression::Symbol('z')),)); + } + + #[test] + fn minus_6_parses_as_integer() { + let tokens = vec![ Token::Minus, Token::Integer(6) ]; + let expr = parse(tokens.into_iter().map(|t| Ok(t))).unwrap(); + assert_eq!(expr, Expression::Integer(-6)); + } + + #[test] + fn minus_x_parses_as_product() { + let tokens = vec![ Token::Minus, Token::Symbol('x') ]; + let expr = parse(tokens.into_iter().map(|t| Ok(t))).unwrap(); + assert_eq!( + expr, + Expression::product( + Expression::Integer(-1), Expression::Symbol('x'))); + } + + #[test] + fn plus_x_parses_as_symbol() { + let tokens = vec![ Token::Plus, Token::Symbol('x') ]; + let expr = parse(tokens.into_iter().map(|t| Ok(t))).unwrap(); + assert_eq!(expr, Expression::Symbol('x')); + } + + #[test] + fn minus_x_times_y_parses_with_higher_precedence_for_unary_minus() { + let tokens = vec![ + Token::Minus, + Token::Symbol('x'), + Token::Times, + Token::Symbol('y'), + ]; + let expr = parse(tokens.into_iter().map(|t| Ok(t))).unwrap(); + assert_eq!( + expr, + Expression::product( + Expression::product( + Expression::Integer(-1), Expression::Symbol('x')), + Expression::Symbol('y'))); + } + + #[test] + fn minus_x_caret_y_parses_with_higher_precedence_for_caret() { + let tokens = vec![ + Token::Minus, + Token::Symbol('x'), + Token::Caret, + Token::Symbol('y'), + ]; + let expr = parse(tokens.into_iter().map(|t| Ok(t))).unwrap(); + assert_eq!( + expr, + Expression::product( + Expression::Integer(-1), + Expression::exponent( + Expression::Symbol('x'), Expression::Symbol('y')))); + } + + #[test] + fn x_times_oparen_y_plus_z_cparen_parses_as_product_of_sum() { + let tokens = vec![ + Token::Symbol('x'), + Token::Times, + Token::OpenParen, + Token::Symbol('y'), + Token::Plus, + Token::Symbol('z'), + Token::CloseParen, + ]; + let expr = parse(tokens.into_iter().map(|t| Ok(t))).unwrap(); + assert_eq!( + expr, + Expression::product( + Expression::Symbol('x'), + Expression::sum( + Expression::Symbol('y'), Expression::Symbol('z')))); + } + + #[test] + fn x_y_parses_as_product() { + let tokens = vec![ + Token::Symbol('x'), + Token::Symbol('y'), + ]; + let expr = parse(tokens.into_iter().map(|t| Ok(t))).unwrap(); + assert_eq!( + expr, + Expression::product( + Expression::Symbol('x'), Expression::Symbol('y'))); + } + + #[test] + fn input_2_x_parses_as_product() { + let tokens = vec![ + Token::Integer(2), + Token::Symbol('x'), + ]; + let expr = parse(tokens.into_iter().map(|t| Ok(t))).unwrap(); + assert_eq!( + expr, + Expression::product( + Expression::Integer(2), Expression::Symbol('x'))); + } + + #[test] + fn x_2_parses_as_product() { + let tokens = vec![ + Token::Symbol('x'), + Token::Integer(2), + ]; + let expr = parse(tokens.into_iter().map(|t| Ok(t))).unwrap(); + assert_eq!( + expr, + Expression::product( + Expression::Symbol('x'), Expression::Integer(2))); + } + + #[test] + fn complex_expression_1() { + let tokens = vec![ + Token::Integer(2), + Token::Symbol('x'), + Token::Caret, + Token::Integer(3), + Token::Minus, + Token::Symbol('y'), + Token::Divide, + Token::Symbol('z'), + Token::Caret, + Token::Integer(2), + ]; + let expr = parse(tokens.into_iter().map(|t| Ok(t))).unwrap(); + assert_eq!( + expr, + Expression::difference( + Expression::product( + Expression::Integer(2), + Expression::exponent( + Expression::Symbol('x'), + Expression::Integer(3))), + Expression::quotient( + Expression::Symbol('y'), + Expression::exponent( + Expression::Symbol('z'), + Expression::Integer(2))))); + } + + #[test] + fn complex_expression_2() { + let tokens = vec![ + Token::Symbol('x'), + Token::OpenParen, + Token::Symbol('x'), + Token::Plus, + Token::Symbol('y'), + Token::Minus, + Token::Integer(1), + Token::CloseParen, + Token::Plus, + Token::Symbol('y'), + Token::Caret, + Token::OpenParen, + Token::Integer(3), + Token::Minus, + Token::Symbol('x'), + Token::CloseParen, + ]; + let expr = parse(tokens.into_iter().map(|t| Ok(t))).unwrap(); + assert_eq!( + expr, + Expression::sum( + Expression::product( + Expression::Symbol('x'), + Expression::difference( + Expression::sum( + Expression::Symbol('x'), + Expression::Symbol('y')), + Expression::Integer(1))), + Expression::exponent( + Expression::Symbol('y'), + Expression::difference( + Expression::Integer(3), + Expression::Symbol('x'))))); + } + + #[test] + fn unclosed_paren_is_invalid_syntax() { + let tokens = vec![ + Token::OpenParen, + Token::Symbol('x'), + Token::Plus, + Token::Symbol('y'), + ]; + let result = parse(tokens.into_iter().map(|t| Ok(t))); + std::assert_matches!(result, Err(Error::InvalidSyntax)); + } + + #[test] + fn close_paren_x_is_invalid_syntax() { + let tokens = vec![ + Token::CloseParen, + Token::Symbol('x'), + ]; + let result = parse(tokens.into_iter().map(|t| Ok(t))); + std::assert_matches!(result, Err(Error::InvalidSyntax)); + } + + #[test] + fn times_x_is_invalid_syntax() { + let tokens = vec![ + Token::Times, + Token::Symbol('x'), + ]; + let result = parse(tokens.into_iter().map(|t| Ok(t))); + std::assert_matches!(result, Err(Error::InvalidSyntax)); + } + + #[test] + fn divide_x_is_invalid_syntax() { + let tokens = vec![ + Token::Divide, + Token::Symbol('x'), + ]; + let result = parse(tokens.into_iter().map(|t| Ok(t))); + std::assert_matches!(result, Err(Error::InvalidSyntax)); + } + + #[test] + fn caret_x_is_invalid_syntax() { + let tokens = vec![ + Token::Caret, + Token::Symbol('x'), + ]; + let result = parse(tokens.into_iter().map(|t| Ok(t))); + std::assert_matches!(result, Err(Error::InvalidSyntax)); + } +} diff --git a/src/token.rs b/src/token.rs index d78c64e..830a53f 100644 --- a/src/token.rs +++ b/src/token.rs @@ -10,3 +10,14 @@ pub enum Token { Divide, Caret, } + +impl Token { + pub fn starts_expression(&self) -> bool { + match self { + Token::OpenParen => true, + Token::Symbol(_) => true, + Token::Integer(_) => true, + _ => false, + } + } +}