From c4eca9defeeddf2d146317ae08d24b89b67a7c86 Mon Sep 17 00:00:00 2001 From: Camden Dixie O'Brien Date: Sat, 26 Sep 2026 13:32:41 +0100 Subject: [PATCH] Add canonicalisation pass --- src/bin/demo.rs | 5 +- src/eval.rs | 2 + src/eval/canonicalise.rs | 140 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 src/eval/canonicalise.rs diff --git a/src/bin/demo.rs b/src/bin/demo.rs index 4e44226..a10a912 100644 --- a/src/bin/demo.rs +++ b/src/bin/demo.rs @@ -6,7 +6,10 @@ fn main() -> std::io::Result<()> { eval::Normaliser::new(), eval::Differentiator::new('x'), eval::ConstFolder::new(), - eval::Simplifier::new()); + eval::Simplifier::new(), + eval::ConstFolder::new(), + eval::Canonicaliser::new() + ); let mut repl = Repl::new(std::io::stdin(), std::io::stdout(), eval); repl.run() } diff --git a/src/eval.rs b/src/eval.rs index d32c284..1b01066 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -3,12 +3,14 @@ pub mod const_fold; pub mod compose; pub mod differentiate; pub mod simplify; +pub mod canonicalise; pub use normalise::*; pub use const_fold::*; pub use compose::*; pub use differentiate::*; pub use simplify::*; +pub use canonicalise::*; #[derive(Debug, PartialEq)] pub enum Error { diff --git a/src/eval/canonicalise.rs b/src/eval/canonicalise.rs new file mode 100644 index 0000000..c3671b5 --- /dev/null +++ b/src/eval/canonicalise.rs @@ -0,0 +1,140 @@ +use crate::expression::Expression; +use crate::eval::{Error, Evaluator}; + +pub struct Canonicaliser; + +impl Canonicaliser { + pub fn new() -> Self { + Self {} + } +} + +impl Evaluator for Canonicaliser { + type Expr = Expression; + type Error = Error; + + fn eval(&mut self, input: Expression) -> Result { + Ok(canonicalise(input)) + } +} + +fn canonicalise(expr: Expression) -> Expression { + match expr { + Expression::Sum(x, y) => recurse(Expression::Sum, x, y), + Expression::Product(x, y) => recurse(product, x, y), + Expression::Exponent(x, y) => recurse(Expression::Exponent, x, y), + _ => expr + } +} + +fn product( + x: std::boxed::Box, + y: std::boxed::Box) + -> Expression +{ + if let Some(_) = y.integer() { + Expression::Product(y, x) + } else { + Expression::Product(x, y) + } +} + +fn recurse( + f: impl Fn( + std::boxed::Box, + std::boxed::Box) -> Expression, + x: std::boxed::Box, + y: std::boxed::Box) + -> Expression +{ + f(boxed(x), boxed(y)) +} + +fn boxed( + mut expr: std::boxed::Box) -> std::boxed::Box +{ + *expr = canonicalise(*expr); + expr +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn product_integer_symbol_is_unchanged() { + let input = Expression::product( + Expression::Integer(1), + Expression::Symbol('x')); + let result = Canonicaliser::new().eval(input).unwrap(); + assert_eq!( + result, + Expression::product( + Expression::Integer(1), + Expression::Symbol('x'))); + } + + #[test] + fn product_symbol_integer_becomes_product_integer_symbol() { + let input = Expression::product( + Expression::Symbol('x'), + Expression::Integer(1)); + let result = Canonicaliser::new().eval(input).unwrap(); + assert_eq!( + result, + Expression::product( + Expression::Integer(1), + Expression::Symbol('x'))); + } + + #[test] + fn product_symbol_integer_nested_in_sum_is_reordered() { + let input = Expression::sum( + Expression::Integer(1), + Expression::product( + Expression::Symbol('x'), + Expression::Integer(1))); + let result = Canonicaliser::new().eval(input).unwrap(); + assert_eq!( + result, + Expression::sum( + Expression::Integer(1), + Expression::product( + Expression::Integer(1), + Expression::Symbol('x')))); + } + + #[test] + fn product_symbol_integer_nested_in_product_is_reordered() { + let input = Expression::product( + Expression::Integer(1), + Expression::product( + Expression::Symbol('x'), + Expression::Integer(1))); + let result = Canonicaliser::new().eval(input).unwrap(); + assert_eq!( + result, + Expression::product( + Expression::Integer(1), + Expression::product( + Expression::Integer(1), + Expression::Symbol('x')))); + } + + #[test] + fn exponent_symbol_integer_nested_in_exponent_is_reordered() { + let input = Expression::exponent( + Expression::Integer(1), + Expression::product( + Expression::Symbol('x'), + Expression::Integer(1))); + let result = Canonicaliser::new().eval(input).unwrap(); + assert_eq!( + result, + Expression::exponent( + Expression::Integer(1), + Expression::product( + Expression::Integer(1), + Expression::Symbol('x')))); + } +}