diff --git a/src/lib.rs b/src/lib.rs index 2701531..d115a78 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,3 +4,4 @@ pub mod binding; pub mod lex; pub mod parse; +pub mod print; diff --git a/src/print.rs b/src/print.rs new file mode 100644 index 0000000..5345682 --- /dev/null +++ b/src/print.rs @@ -0,0 +1,389 @@ +use crate::expression::Expression; +use crate::binding::{Binding, Power}; + +#[derive(Clone, Copy, Debug)] +enum Position { + Left, + Right, +} + +impl Position { + fn binds(&self, a: Binding, b: Binding) -> bool + { + match self { + Position::Left => b <= a, + Position::Right => a >= b, + } + } +} + +impl std::fmt::Display for Expression { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + print(f, self, Binding::left(Power::Minimum), Position::Left) + } +} + +fn print( + f: &mut std::fmt::Formatter<'_>, + expr: &Expression, + limit: Binding, + position: Position) -> std::fmt::Result { + let binding = Binding::of_expression(expr); + if position.binds(binding, limit) { + print_dispatch(f, expr, binding) + } else { + write!(f, "(")?; + print_dispatch(f, expr, binding)?; + write!(f, ")") + } +} + +fn print_dispatch( + f: &mut std::fmt::Formatter<'_>, + expr: &Expression, + limit: Binding) -> std::fmt::Result { + match expr { + Expression::Integer(x) => write!(f, "{}", x), + Expression::Symbol(c) => write!(f, "{}", c), + Expression::Sum(x, y) => print_sum(f, x, y, limit), + Expression::Difference(x, y) => print_difference(f, x, y, limit), + Expression::Product(x, y) => print_product(f, x, y, limit), + Expression::Quotient(x, y) => print_quotient(f, x, y, limit), + Expression::Exponent(x, y) => print_exponent(f, x, y, limit), + } +} + +fn print_sum( + f: &mut std::fmt::Formatter<'_>, + x: &Expression, + y: &Expression, + limit: Binding) -> std::fmt::Result { + print(f, x, limit, Position::Left)?; + write!(f, " + ")?; + print(f, y, limit, Position::Right) +} + +fn print_difference( + f: &mut std::fmt::Formatter<'_>, + x: &Expression, + y: &Expression, + limit: Binding) -> std::fmt::Result { + print(f, x, limit, Position::Left)?; + write!(f, " - ")?; + print(f, y, limit, Position::Right) +} + +fn print_product( + f: &mut std::fmt::Formatter<'_>, + x: &Expression, + y: &Expression, + limit: Binding) -> std::fmt::Result { + print(f, x, limit, Position::Left)?; + let y_string = print_to_string(y, limit, Position::Right); + if y_string.starts_with(is_numerical) { + write!(f, "*{}", y_string) + } else { + write!(f, "{}", y_string) + } +} + +fn print_quotient( + f: &mut std::fmt::Formatter<'_>, + x: &Expression, + y: &Expression, + limit: Binding) -> std::fmt::Result { + print(f, x, limit, Position::Left)?; + write!(f, "/")?; + print(f, y, limit, Position::Right) +} + +fn print_exponent( + f: &mut std::fmt::Formatter<'_>, + x: &Expression, + y: &Expression, + limit: Binding) -> std::fmt::Result { + print(f, x, limit, Position::Left)?; + write!(f, "^")?; + print(f, y, limit, Position::Right) +} + +fn print_to_string(x: &Expression, lim: Binding, pos: Position) -> String { + std::fmt::from_fn(|f| print(f, x, lim, pos)).to_string() +} + +fn is_numerical(c: char) -> bool { + c.is_ascii_digit() || c == '-' +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn integer_1_is_printed_as_1() { + let expr = Expression::Integer(1); + assert_eq!(expr.to_string(), "1"); + } + + #[test] + fn symbol_x_is_printed_as_x() { + let expr = Expression::Symbol('x'); + assert_eq!(expr.to_string(), "x"); + } + + #[test] + fn sum_x_y_is_printed_as_x_plus_y() { + let expr = Expression::sum( + Expression::Symbol('x'), + Expression::Symbol('y')); + assert_eq!(expr.to_string(), "x + y"); + } + + #[test] + fn difference_x_y_is_printed_as_x_minus_y() { + let expr = Expression::difference( + Expression::Symbol('x'), + Expression::Symbol('y')); + assert_eq!(expr.to_string(), "x - y"); + } + + #[test] + fn product_x_y_is_printed_as_xy() { + let expr = Expression::product( + Expression::Symbol('x'), + Expression::Symbol('y')); + assert_eq!(expr.to_string(), "xy"); + } + + #[test] + fn product_1_2_is_printed_as_1_times_2() { + let expr = Expression::product( + Expression::Integer(1), + Expression::Integer(2)); + assert_eq!(expr.to_string(), "1*2"); + } + + #[test] + fn quotient_x_y_is_printed_as_x_slash_y() { + let expr = Expression::quotient( + Expression::Symbol('x'), + Expression::Symbol('y')); + assert_eq!(expr.to_string(), "x/y"); + } + + #[test] + fn exponent_x_y_is_printed_as_x_caret_y() { + let expr = Expression::exponent( + Expression::Symbol('x'), + Expression::Symbol('y')); + assert_eq!(expr.to_string(), "x^y"); + } + + #[test] + fn product_of_sums_prints_with_parens() { + let expr = Expression::product( + Expression::sum( + Expression::Symbol('a'), + Expression::Symbol('b')), + Expression::sum( + Expression::Symbol('c'), + Expression::Symbol('d'))); + assert_eq!(expr.to_string(), "(a + b)(c + d)"); + } + + #[test] + fn sum_of_products_prints_without_parens() { + let expr = Expression::sum( + Expression::product( + Expression::Symbol('a'), + Expression::Symbol('b')), + Expression::product( + Expression::Symbol('c'), + Expression::Symbol('d'))); + assert_eq!(expr.to_string(), "ab + cd"); + } + + #[test] + fn sum_of_sum_x_y_and_z_prints_without_parens() { + let expr = Expression::sum( + Expression::sum( + Expression::Symbol('x'), + Expression::Symbol('y')), + Expression::Symbol('z')); + assert_eq!(expr.to_string(), "x + y + z"); + } + + #[test] + fn sum_x_sum_y_z_prints_without_parens() { + let expr = Expression::sum( + Expression::Symbol('x'), + Expression::sum( + Expression::Symbol('y'), + Expression::Symbol('z'))); + assert_eq!(expr.to_string(), "x + y + z"); + } + + #[test] + fn product_of_product_x_y_and_z_prints_without_parens() { + let expr = Expression::product( + Expression::product( + Expression::Symbol('x'), + Expression::Symbol('y')), + Expression::Symbol('z')); + assert_eq!(expr.to_string(), "xyz"); + } + + #[test] + fn product_x_product_y_z_prints_without_parens() { + let expr = Expression::product( + Expression::Symbol('x'), + Expression::product( + Expression::Symbol('y'), + Expression::Symbol('z'))); + assert_eq!(expr.to_string(), "xyz"); + } + + #[test] + fn difference_of_difference_x_y_and_z_prints_without_parens() { + let expr = Expression::difference( + Expression::difference( + Expression::Symbol('x'), + Expression::Symbol('y')), + Expression::Symbol('z')); + assert_eq!(expr.to_string(), "x - y - z"); + } + + #[test] + fn difference_x_difference_y_z_prints_with_parens() { + let expr = Expression::difference( + Expression::Symbol('x'), + Expression::difference( + Expression::Symbol('y'), + Expression::Symbol('z'))); + assert_eq!(expr.to_string(), "x - (y - z)"); + } + + #[test] + fn quotient_of_quotient_x_y_and_z_prints_without_parens() { + let expr = Expression::quotient( + Expression::quotient( + Expression::Symbol('x'), + Expression::Symbol('y')), + Expression::Symbol('z')); + assert_eq!(expr.to_string(), "x/y/z"); + } + + #[test] + fn quotient_x_quotient_y_z_prints_with_parens() { + let expr = Expression::quotient( + Expression::Symbol('x'), + Expression::quotient( + Expression::Symbol('y'), + Expression::Symbol('z'))); + assert_eq!(expr.to_string(), "x/(y/z)"); + } + + #[test] + fn exponent_x_exponent_y_z_prints_without_parens() { + let expr = Expression::exponent( + Expression::Symbol('x'), + Expression::exponent( + Expression::Symbol('y'), + Expression::Symbol('z'))); + assert_eq!(expr.to_string(), "x^y^z"); + } + + #[test] + fn exponent_of_exponent_x_y_and_z_prints_with_parens() { + let expr = Expression::exponent( + Expression::exponent( + Expression::Symbol('x'), + Expression::Symbol('y')), + Expression::Symbol('z')); + assert_eq!(expr.to_string(), "(x^y)^z"); + } + + #[test] + fn complex_expression_1() { + let 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)))); + assert_eq!(expr.to_string(), "2x^3 - y/z^2"); + } + + #[test] + fn complex_expression_2() { + let 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')))); + assert_eq!(expr.to_string(), "x(x + y - 1) + y^(3 - x)"); + } + + #[test] + fn product_1_product_2_3_is_printed_with_times() { + let expr = Expression::product( + Expression::Integer(1), + Expression::product( + Expression::Integer(2), + Expression::Integer(3))); + assert_eq!(expr.to_string(), "1*2*3"); + } + + #[test] + fn product_1_product_2_x_is_printed_with_times() { + let expr = Expression::product( + Expression::Integer(1), + Expression::product( + Expression::Integer(2), + Expression::Symbol('x'),)); + assert_eq!(expr.to_string(), "1*2x"); + } + + #[test] + fn product_x_product_2_3_is_printed_with_times() { + let expr = Expression::product( + Expression::Symbol('x'), + Expression::product( + Expression::Integer(2), + Expression::Integer(3))); + assert_eq!(expr.to_string(), "x*2*3"); + } + + #[test] + fn product_1_minus_2_is_printed_with_times() { + let expr = Expression::product( + Expression::Integer(1), + Expression::Integer(-2)); + assert_eq!(expr.to_string(), "1*-2"); + } + + #[test] + fn product_sum_1_2_sum_3_3_is_printed_without_times() { + let expr = Expression::product( + Expression::sum( + Expression::Integer(1), + Expression::Integer(2)), + Expression::sum( + Expression::Integer(3), + Expression::Integer(4))); + assert_eq!(expr.to_string(), "(1 + 2)(3 + 4)"); + } +}