diff --git a/src/eval.rs b/src/eval.rs index 48ca0a8..f3548a5 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -1,3 +1,9 @@ +pub mod normalise; + +#[derive(Debug)] +pub enum Error { +} + pub trait Evaluator { type Expr; type Error; diff --git a/src/eval/normalise.rs b/src/eval/normalise.rs new file mode 100644 index 0000000..7576e34 --- /dev/null +++ b/src/eval/normalise.rs @@ -0,0 +1,305 @@ +use crate::expression::Expression; +use crate::eval::{Error, Evaluator}; + +pub struct Normaliser; + +impl Normaliser { + pub fn new() -> Self { + Normaliser {} + } +} + +impl Evaluator for Normaliser { + type Expr = Expression; + type Error = Error; + + fn eval(&mut self, input: Expression) -> Result { + Ok(normalise(input)) + } +} + +fn normalise(expr: Expression) -> Expression { + match expr { + Expression::Integer(_) => expr, + Expression::Symbol(_) => expr, + + Expression::Sum(x, y) => + Expression::Sum(normalise_boxed(x), normalise_boxed(y)), + Expression::Product(x, y) => + Expression::Product(normalise_boxed(x), normalise_boxed(y)), + Expression::Exponent(x, y) => + Expression::Exponent(normalise_boxed(x), normalise_boxed(y)), + + Expression::Difference(x, y) => + Expression::Sum( + normalise_boxed(x), negation(normalise_boxed(y))), + Expression::Quotient(x, y) => { + if x.integer().is_some() && y.integer().is_some() { + Expression::Quotient(x, y) + } else { + Expression::Product( + normalise_boxed(x), reciprocal(normalise_boxed(y))) + } + }, + } +} + +fn normalise_boxed( + mut b: std::boxed::Box) -> std::boxed::Box { + *b = normalise(*b); + b +} + +fn negation( + expr: std::boxed::Box) -> std::boxed::Box { + let res = Expression::Product( + std::boxed::Box::new(Expression::Integer(-1)), expr); + std::boxed::Box::new(res) +} + +fn reciprocal( + expr: std::boxed::Box) -> std::boxed::Box { + let res = if expr.is_constant() { + Expression::Quotient( + std::boxed::Box::new(Expression::Integer(1)), expr) + } else { + Expression::Exponent( + expr, std::boxed::Box::new(Expression::Integer(-1))) + }; + std::boxed::Box::new(res) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn integer_is_unchanged() { + let input = Expression::Integer(0); + let result = Normaliser::new().eval(input).unwrap(); + assert_eq!(result, Expression::Integer(0)); + } + + #[test] + fn symbol_is_unchanged() { + let input = Expression::Symbol('x'); + let result = Normaliser::new().eval(input).unwrap(); + assert_eq!(result, Expression::Symbol('x')); + } + + #[test] + fn sum_of_integers_is_unchanged() { + let input = Expression::sum( + Expression::Symbol('x'), + Expression::Symbol('x')); + let result = Normaliser::new().eval(input).unwrap(); + assert_eq!( + result, + Expression::sum( + Expression::Symbol('x'), + Expression::Symbol('x'))); + } + + #[test] + fn product_of_integers_is_unchanged() { + let input = Expression::product( + Expression::Symbol('x'), + Expression::Symbol('x')); + let result = Normaliser::new().eval(input).unwrap(); + assert_eq!( + result, + Expression::product( + Expression::Symbol('x'), + Expression::Symbol('x'))); + } + + #[test] + fn exponent_of_integers_is_unchanged() { + let input = Expression::exponent( + Expression::Symbol('x'), + Expression::Symbol('x')); + let result = Normaliser::new().eval(input).unwrap(); + assert_eq!( + result, + Expression::exponent( + Expression::Symbol('x'), + Expression::Symbol('x'))); + } + + #[test] + fn difference_is_transformed_to_sum() { + let input = Expression::difference( + Expression::Symbol('x'), + Expression::Symbol('x')); + let result = Normaliser::new().eval(input).unwrap(); + assert_eq!( + result, + Expression::sum( + Expression::Symbol('x'), + Expression::product( + Expression::Integer(-1), + Expression::Symbol('x')))); + } + + #[test] + fn quotient_of_symbols_is_transformed_to_product() { + let input = Expression::quotient( + Expression::Symbol('x'), + Expression::Symbol('x')); + let result = Normaliser::new().eval(input).unwrap(); + assert_eq!( + result, + Expression::product( + Expression::Symbol('x'), + Expression::exponent( + Expression::Symbol('x'), + Expression::Integer(-1)))); + } + + #[test] + fn quotient_of_integer_and_symbol_is_transformed_to_product() { + let input = Expression::quotient( + Expression::Integer(2), + Expression::Symbol('x')); + let result = Normaliser::new().eval(input).unwrap(); + assert_eq!( + result, + Expression::product( + Expression::Integer(2), + Expression::exponent( + Expression::Symbol('x'), + Expression::Integer(-1)))); + } + + #[test] + fn quotient_of_symbol_and_integer_is_transformed_to_product() { + let input = Expression::quotient( + Expression::Symbol('x'), + Expression::Integer(2)); + let result = Normaliser::new().eval(input).unwrap(); + assert_eq!( + result, + Expression::product( + Expression::Symbol('x'), + Expression::quotient( + Expression::Integer(1), + Expression::Integer(2)))); + } + + #[test] + fn differences_inside_sum_are_normalised() { + let input = Expression::sum( + Expression::difference( + Expression::Symbol('x'), + Expression::Symbol('x')), + Expression::difference( + Expression::Symbol('x'), + Expression::Symbol('x'))); + let result = Normaliser::new().eval(input).unwrap(); + assert_eq!( + result, + Expression::sum( + Expression::sum( + Expression::Symbol('x'), + Expression::product( + Expression::Integer(-1), + Expression::Symbol('x'))), + Expression::sum( + Expression::Symbol('x'), + Expression::product( + Expression::Integer(-1), + Expression::Symbol('x'))))); + } + + #[test] + fn differences_inside_exponent_are_normalised() { + let input = Expression::exponent( + Expression::difference( + Expression::Symbol('x'), + Expression::Symbol('x')), + Expression::difference( + Expression::Symbol('x'), + Expression::Symbol('x'))); + let result = Normaliser::new().eval(input).unwrap(); + assert_eq!( + result, + Expression::exponent( + Expression::sum( + Expression::Symbol('x'), + Expression::product( + Expression::Integer(-1), + Expression::Symbol('x'))), + Expression::sum( + Expression::Symbol('x'), + Expression::product( + Expression::Integer(-1), + Expression::Symbol('x'))))); + } + + #[test] + fn differences_inside_difference_are_normalised() { + let input = Expression::difference( + Expression::difference( + Expression::Symbol('x'), + Expression::Symbol('x')), + Expression::difference( + Expression::Symbol('x'), + Expression::Symbol('x'))); + let result = Normaliser::new().eval(input).unwrap(); + assert_eq!( + result, + Expression::sum( + Expression::sum( + Expression::Symbol('x'), + Expression::product( + Expression::Integer(-1), + Expression::Symbol('x'))), + Expression::product( + Expression::Integer(-1), + Expression::sum( + Expression::Symbol('x'), + Expression::product( + Expression::Integer(-1), + Expression::Symbol('x')))))); + } + + #[test] + fn differences_inside_quotient_are_normalised() { + let input = Expression::quotient( + Expression::difference( + Expression::Symbol('x'), + Expression::Symbol('x')), + Expression::difference( + Expression::Symbol('x'), + Expression::Symbol('x'))); + let result = Normaliser::new().eval(input).unwrap(); + assert_eq!( + result, + Expression::product( + Expression::sum( + Expression::Symbol('x'), + Expression::product( + Expression::Integer(-1), + Expression::Symbol('x'))), + Expression::exponent( + Expression::sum( + Expression::Symbol('x'), + Expression::product( + Expression::Integer(-1), + Expression::Symbol('x'))), + Expression::Integer(-1)))); + } + + #[test] + fn quotient_of_integers_is_unchanged() { + let input = Expression::quotient( + Expression::Integer(1), + Expression::Integer(2)); + let result = Normaliser::new().eval(input).unwrap(); + assert_eq!( + result, + Expression::quotient( + Expression::Integer(1), + Expression::Integer(2))); + } +} diff --git a/src/expression.rs b/src/expression.rs index 6694b08..7d58f86 100644 --- a/src/expression.rs +++ b/src/expression.rs @@ -35,6 +35,14 @@ impl Expression { std::boxed::Box::new(left), std::boxed::Box::new(right)) } + pub fn integer(&self) -> Option { + if let Expression::Integer(x) = self { + Some(*x) + } else { + None + } + } + pub fn is_constant(&self) -> bool { match self { Expression::Integer(_) => true,