Implement Expression printing
This commit is contained in:
1 parent
66c24de9e9
commit
9fa10e4f2b
2 files changed
+390
No files matched your search
@@ -4,3 +4,4 @@ pub mod binding;
|
||||
|
||||
pub mod lex;
|
||||
pub mod parse;
|
||||
pub mod print;
|
||||
+389
@@ -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)");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user