Implement constant folding
This commit is contained in:
1 parent
f58d37b27b
commit
48f58d87fc
2 files changed
+607
No files matched your search
@@ -1,4 +1,5 @@
|
||||
pub mod normalise;
|
||||
pub mod const_fold;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user