Implement constant folding

This commit is contained in:
cdo committed 2026-09-26 11:43:53 +01:00
1 parent f58d37b27b
commit 48f58d87fc
2 files changed
+607

No files matched your search

+1
View File
@@ -1,4 +1,5 @@
pub mod normalise;
pub mod const_fold;
#[derive(Debug)]
pub enum Error {
+606
View File
@@ -0,0 +1,606 @@
use crate::expression::Expression;
use crate::eval::{Error, Evaluator};
pub struct ConstFolder;
impl ConstFolder {
pub fn new() -> Self {
ConstFolder {}
}
}
impl Evaluator for ConstFolder {
type Expr = Expression;
type Error = Error;
fn eval(&mut self, input: Expression) -> Result<Expression, Error> {
Ok(const_fold(input))
}
}
fn const_fold(expr: Expression) -> Expression {
match expr {
Expression::Integer(_) => expr,
Expression::Symbol(_) => expr,
Expression::Sum(x, y) =>
fold_assoc(
x, y, 0, &add, &is_sum, &extract_sum, Expression::Sum),
Expression::Product(x, y) =>
fold_assoc(
x, y, 1, &multiply, &is_product, &extract_product,
Expression::Product),
Expression::Difference(x, y) =>
try_fold(x, y, subtract, Expression::Difference),
Expression::Quotient(x, y) =>
try_fold(x, y, simplify, Expression::Quotient),
Expression::Exponent(x, y) =>
try_fold(x, y, power, Expression::Exponent),
}
}
fn fold_assoc(
x: std::boxed::Box<Expression>,
y: std::boxed::Box<Expression>,
identity: i32,
eval: &impl Fn(i32, i32) -> i32,
is_op: &impl Fn(&Expression) -> bool,
extract: &impl Fn(
std::boxed::Box<Expression>)
-> Option<(
std::boxed::Box<Expression>, std::boxed::Box<Expression>)>,
cons: impl Fn(
std::boxed::Box<Expression>,
std::boxed::Box<Expression>) -> Expression)
-> Expression {
let mut terms = Vec::new();
let consts = eval(
collect_assoc(x, &mut terms, identity, eval, is_op, extract),
collect_assoc(y, &mut terms, identity, eval, is_op, extract));
build_expr(terms, consts, identity, cons)
}
fn collect_assoc(
mut expr: std::boxed::Box<Expression>,
terms: &mut Vec<std::boxed::Box<Expression>>,
identity: i32,
eval: &impl Fn(i32, i32) -> i32,
is_op: &impl Fn(&Expression) -> bool,
extract: &impl Fn(
std::boxed::Box<Expression>)
-> Option<(
std::boxed::Box<Expression>, std::boxed::Box<Expression>)>)
-> i32
{
expr = const_fold_boxed(expr);
if let Expression::Integer(x) = *expr {
x
} else if is_op(expr.as_ref()) {
let (x, y) = extract(expr).unwrap();
eval(
collect_assoc(x, terms, identity, eval, is_op, extract),
collect_assoc(y, terms, identity, eval, is_op, extract))
} else {
terms.push(expr);
identity
}
}
fn build_expr(
mut terms: Vec<std::boxed::Box<Expression>>,
consts: i32,
identity: i32,
cons: impl Fn(
std::boxed::Box<Expression>,
std::boxed::Box<Expression>) -> Expression)
-> Expression {
let integer = Expression::Integer(consts);
if terms.is_empty() {
return integer;
}
let mut tail = if consts != identity {
integer
} else {
*terms.pop().unwrap()
};
loop {
match terms.pop() {
None => break tail,
Some(term) =>
tail = cons(term, std::boxed::Box::new(tail)),
}
}
}
fn try_fold(
x: std::boxed::Box<Expression>,
y: std::boxed::Box<Expression>,
op: impl FnOnce(i32, i32) -> Expression,
cons: impl FnOnce(
std::boxed::Box<Expression>,
std::boxed::Box<Expression>) -> Expression)
-> Expression {
let folded_x = const_fold_boxed(x);
let folded_y = const_fold_boxed(y);
match (folded_x.integer(), folded_y.integer()) {
(Some(x_value), Some(y_value)) => op(x_value, y_value),
_ => cons(folded_x, folded_y),
}
}
fn const_fold_boxed(
mut b: std::boxed::Box<Expression>) -> std::boxed::Box<Expression> {
*b = const_fold(*b);
b
}
fn is_sum(expr: &Expression) -> bool {
if let Expression::Sum(_, _) = expr {
true
} else {
false
}
}
fn extract_sum(expr: std::boxed::Box<Expression>)
-> Option<(std::boxed::Box<Expression>, std::boxed::Box<Expression>)>
{
if let Expression::Sum(x, y) = *expr {
Some((x, y))
} else {
None
}
}
fn is_product(expr: &Expression) -> bool {
if let Expression::Product(_, _) = expr {
true
} else {
false
}
}
fn extract_product(expr: std::boxed::Box<Expression>)
-> Option<(std::boxed::Box<Expression>, std::boxed::Box<Expression>)>
{
if let Expression::Product(x, y) = *expr {
Some((x, y))
} else {
None
}
}
fn add(x: i32, y: i32) -> i32 {
x + y
}
fn multiply(x: i32, y: i32) -> i32 {
x * y
}
fn subtract(x: i32, y: i32) -> Expression {
Expression::Integer(x - y)
}
fn power(x: i32, y: i32) -> Expression {
if y >= 0 {
Expression::Integer(x.pow(y as u32))
} else {
Expression::quotient(
Expression::Integer(1),
Expression::Integer(x.pow(-y as u32)))
}
}
fn simplify(num: i32, denom: i32) -> Expression {
let factor = gcd(num, denom);
if denom / factor == 1 {
Expression::Integer(num / factor)
} else if factor != 1 {
Expression::quotient(
Expression::Integer(num / factor),
Expression::Integer(denom / factor))
} else {
Expression::quotient(
Expression::Integer(num),
Expression::Integer(denom))
}
}
fn gcd(mut x: i32, mut y: i32) -> i32 {
while y != 0 {
(x, y) = (y, x % y);
}
x
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn integer_is_unchanged() {
let input = Expression::Integer(0);
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(result, Expression::Integer(0));
}
#[test]
fn symbol_is_unchanged() {
let input = Expression::Symbol('x');
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(result, Expression::Symbol('x'));
}
#[test]
fn sum_of_symbol_and_symbol_is_unchanged() {
let input = Expression::sum(
Expression::Symbol('x'),
Expression::Symbol('y'));
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(
result,
Expression::sum(
Expression::Symbol('x'),
Expression::Symbol('y')));
}
#[test]
fn sum_of_symbol_and_integer_is_unchanged() {
let input = Expression::sum(
Expression::Symbol('x'),
Expression::Integer(1));
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(
result,
Expression::sum(
Expression::Symbol('x'),
Expression::Integer(1)));
}
#[test]
fn sum_of_integers_is_evaluated() {
let input = Expression::sum(
Expression::Integer(1),
Expression::Integer(2));
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(
result,
Expression::Integer(3));
}
#[test]
fn difference_of_symbol_and_symbol_is_unchanged() {
let input = Expression::difference(
Expression::Symbol('x'),
Expression::Symbol('y'));
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(
result,
Expression::difference(
Expression::Symbol('x'),
Expression::Symbol('y')));
}
#[test]
fn difference_of_symbol_and_integer_is_unchanged() {
let input = Expression::difference(
Expression::Symbol('x'),
Expression::Integer(1));
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(
result,
Expression::difference(
Expression::Symbol('x'),
Expression::Integer(1)));
}
#[test]
fn difference_of_integers_is_evaluated() {
let input = Expression::difference(
Expression::Integer(2),
Expression::Integer(1));
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(
result,
Expression::Integer(1));
}
#[test]
fn product_of_symbol_and_symbol_is_unchanged() {
let input = Expression::product(
Expression::Symbol('x'),
Expression::Symbol('y'));
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(
result,
Expression::product(
Expression::Symbol('x'),
Expression::Symbol('y')));
}
#[test]
fn product_of_symbol_and_integer_is_unchanged() {
let input = Expression::product(
Expression::Symbol('x'),
Expression::Integer(2));
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(
result,
Expression::product(
Expression::Symbol('x'),
Expression::Integer(2)));
}
#[test]
fn product_of_integers_is_evaluated() {
let input = Expression::product(
Expression::Integer(3),
Expression::Integer(2));
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(
result,
Expression::Integer(6));
}
#[test]
fn exponent_of_symbol_and_symbol_is_unchanged() {
let input = Expression::exponent(
Expression::Symbol('x'),
Expression::Symbol('y'));
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(
result,
Expression::exponent(
Expression::Symbol('x'),
Expression::Symbol('y')));
}
#[test]
fn exponent_of_symbol_and_integer_is_unchanged() {
let input = Expression::exponent(
Expression::Symbol('x'),
Expression::Integer(1));
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(
result,
Expression::exponent(
Expression::Symbol('x'),
Expression::Integer(1)));
}
#[test]
fn exponent_of_integer_and_positive_integer_is_evaluated() {
let input = Expression::exponent(
Expression::Integer(3),
Expression::Integer(2));
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(result, Expression::Integer(9));
}
#[test]
fn exponent_of_integer_and_zero_is_evaluated() {
let input = Expression::exponent(
Expression::Integer(3),
Expression::Integer(0));
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(result, Expression::Integer(1));
}
#[test]
fn exponent_of_integer_and_positive_integer_is_partially_evaluated() {
let input = Expression::exponent(
Expression::Integer(3),
Expression::Integer(-2));
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(
result,
Expression::quotient(
Expression::Integer(1),
Expression::Integer(9)));
}
#[test]
fn quotient_of_symbol_and_symbol_is_unchanged() {
let input = Expression::quotient(
Expression::Symbol('x'),
Expression::Symbol('y'));
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(
result,
Expression::quotient(
Expression::Symbol('x'),
Expression::Symbol('y')));
}
#[test]
fn quotient_of_symbol_and_integer_is_unchanged() {
let input = Expression::quotient(
Expression::Symbol('x'),
Expression::Integer(1));
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(
result,
Expression::quotient(
Expression::Symbol('x'),
Expression::Integer(1)));
}
#[test]
fn quotient_of_divisible_integers_is_evaluated() {
let input = Expression::quotient(
Expression::Integer(6),
Expression::Integer(2));
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(
result,
Expression::Integer(3));
}
#[test]
fn quotient_of_coprime_integers_is_unchanged() {
let input = Expression::quotient(
Expression::Integer(9),
Expression::Integer(8));
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(
result,
Expression::quotient(
Expression::Integer(9),
Expression::Integer(8)));
}
#[test]
fn quotient_of_integers_with_shared_factor_is_simplified() {
let input = Expression::quotient(
Expression::Integer(6),
Expression::Integer(8));
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(
result,
Expression::quotient(
Expression::Integer(3),
Expression::Integer(4)));
}
#[test]
fn quotient_of_integer_with_one_is_evaluated() {
let input = Expression::quotient(
Expression::Integer(5),
Expression::Integer(1));
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(result, Expression::Integer(5));
}
#[test]
fn sum_of_integer_and_sum_of_integers_is_evaluated() {
let input = Expression::sum(
Expression::Integer(1),
Expression::sum(
Expression::Integer(2),
Expression::Integer(3)));
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(result, Expression::Integer(6));
}
#[test]
fn sum_of_symbol_and_sum_of_integers_is_partially_evaluated() {
let input = Expression::sum(
Expression::Symbol('x'),
Expression::sum(
Expression::Integer(2),
Expression::Integer(3)));
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(
result,
Expression::sum(
Expression::Symbol('x'),
Expression::Integer(5)));
}
#[test]
fn sum_of_sum_of_symbol_and_int_and_int_is_partially_evaluated() {
let input = Expression::sum(
Expression::sum(
Expression::Symbol('x'),
Expression::Integer(1)),
Expression::Integer(1));
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(
result,
Expression::sum(
Expression::Symbol('x'),
Expression::Integer(2)));
}
#[test]
fn product_of_integer_and_product_of_integers_is_evaluated() {
let input = Expression::product(
Expression::Integer(2),
Expression::product(
Expression::Integer(3),
Expression::Integer(4)));
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(result, Expression::Integer(24));
}
#[test]
fn product_of_symbol_and_product_of_integers_is_partially_evaluated() {
let input = Expression::product(
Expression::Symbol('x'),
Expression::product(
Expression::Integer(2),
Expression::Integer(3)));
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(
result,
Expression::product(
Expression::Symbol('x'),
Expression::Integer(6)));
}
#[test]
fn product_of_product_of_symbol_and_int_and_int_is_partially_evaluated()
{
let input = Expression::product(
Expression::product(
Expression::Symbol('x'),
Expression::Integer(2)),
Expression::Integer(2));
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(
result,
Expression::product(
Expression::Symbol('x'),
Expression::Integer(4)));
}
#[test]
fn sum_of_symbol_with_zero_simplifies()
{
let input = Expression::sum(
Expression::Symbol('x'),
Expression::Integer(0));
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(result, Expression::Symbol('x'));
}
#[test]
fn product_of_symbol_with_one_simplifies()
{
let input = Expression::product(
Expression::Symbol('x'),
Expression::Integer(1));
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(result, Expression::Symbol('x'));
}
#[test]
fn sum_of_products_of_integers_is_evaluated() {
let input = Expression::sum(
Expression::product(
Expression::Integer(2),
Expression::Integer(3)),
Expression::product(
Expression::Integer(4),
Expression::Integer(5)));
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(result, Expression::Integer(26));
}
#[test]
fn product_of_sums_of_integers_is_evaluated() {
let input = Expression::product(
Expression::sum(
Expression::Integer(1),
Expression::Integer(2)),
Expression::sum(
Expression::Integer(3),
Expression::Integer(4)));
let result = ConstFolder::new().eval(input).unwrap();
assert_eq!(result, Expression::Integer(21));
}
}