Implement Expression printing

This commit is contained in:
cdo committed 2026-09-26 10:19:36 +01:00
1 parent 66c24de9e9
commit 9fa10e4f2b
2 files changed
+390

No files matched your search

+1
View File
@@ -4,3 +4,4 @@ pub mod binding;
pub mod lex; pub mod lex;
pub mod parse; pub mod parse;
pub mod print;
+389
View File
@@ -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)");
}
}