use crate::Node;
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum Expr {
None,
Neg { param: Box<Expr> },
Number { value: f64 },
Component { component_id: u64 },
Add { params: Vec<Expr> },
Sub { params: Vec<Expr> },
Coalesce { params: Vec<Expr> },
Min { params: Vec<Expr> },
Max { params: Vec<Expr> },
}
impl std::ops::Add for Expr {
type Output = Self;
fn add(self, rhs: Self) -> Self {
match (self, rhs) {
(Self::None, other) | (other, Self::None) => other,
(Self::Neg { param: lhs }, Self::Neg { param: rhs }) => -(*lhs + *rhs),
(other, Self::Neg { param }) | (Self::Neg { param }, other) => other - *param,
(Self::Add { params: mut lhs }, Self::Add { params: mut rhs }) => {
lhs.append(&mut rhs);
Self::Add { params: lhs }
}
(Self::Add { mut params }, rhs) => {
params.push(rhs);
Self::Add { params }
}
(lhs, Self::Add { mut params }) => {
params.insert(0, lhs);
Self::Add { params }
}
(lhs, rhs) => Self::Add {
params: vec![lhs, rhs],
},
}
}
}
impl std::ops::Sub for Expr {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
match (self, rhs) {
(Self::None, other) => -other,
(other, Self::None) => other,
(sub @ Self::Sub { .. }, Self::Neg { param }) => sub + *param,
(Self::Neg { param }, sub @ Self::Sub { .. }) => -sub - *param,
(Self::Sub { mut params }, rhs) => {
params.push(rhs);
Self::Sub { params }
}
(Self::Neg { param: lhs }, Self::Neg { param: rhs }) => Self::Sub {
params: vec![*rhs, *lhs],
},
(Self::Neg { param }, value) => -(*param + value),
(lhs, Self::Neg { param }) => lhs + *param,
(lhs, rhs) => Self::Sub {
params: vec![lhs, rhs],
},
}
}
}
impl std::ops::Neg for Expr {
type Output = Self;
fn neg(self) -> Self {
match self {
Self::None => Self::None,
Expr::Neg { param: inner } => *inner,
Expr::Sub { mut params } => {
let first = params.remove(0);
Expr::Add { params } - first
}
_ => Expr::Neg {
param: Box::new(self),
},
}
}
}
impl<N: Node> From<&N> for Expr {
fn from(node: &N) -> Self {
Self::Component {
component_id: node.component_id(),
}
}
}
impl Expr {
#[must_use]
pub(crate) fn number(value: f64) -> Self {
Self::Number { value }
}
#[must_use]
pub(crate) fn component(component_id: u64) -> Self {
Self::Component { component_id }
}
#[must_use]
pub(crate) fn coalesce(self, other: Expr) -> Self {
match (self, other) {
(Expr::None, other) | (other, Expr::None) => other,
(
Expr::Coalesce { mut params },
Expr::Coalesce {
params: other_params,
},
) => {
params.extend(other_params);
Self::Coalesce { params }
}
(Expr::Coalesce { mut params }, other) => {
params.push(other);
Self::Coalesce { params }
}
(
param,
Expr::Coalesce {
params: other_params,
},
) => {
let mut params = vec![param];
params.extend(other_params);
Self::Coalesce { params }
}
(first, second) => {
Self::Coalesce {
params: vec![first, second],
}
}
}
}
#[must_use]
pub(crate) fn min(self, other: Expr) -> Self {
match (self, other) {
(Expr::None, expr) | (expr, Expr::None) => expr,
(
Expr::Min { mut params },
Expr::Min {
params: other_params,
},
) => {
params.extend(other_params);
Self::Min { params }
}
(Expr::Min { mut params }, other) | (other, Expr::Min { mut params }) => {
params.push(other);
Self::Min { params }
}
(first, second) => {
Self::Min {
params: vec![first, second],
}
}
}
}
#[must_use]
pub(crate) fn max(self, other: Expr) -> Self {
match (self, other) {
(Expr::None, expr) | (expr, Expr::None) => expr,
(
Expr::Max { mut params },
Expr::Max {
params: other_params,
},
) => {
params.extend(other_params);
Self::Max { params }
}
(Expr::Max { mut params }, other) | (other, Expr::Max { mut params }) => {
params.push(other);
Self::Max { params }
}
(first, second) => {
Self::Max {
params: vec![first, second],
}
}
}
}
}
impl std::fmt::Display for Expr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.generate_string(false))
}
}
#[derive(PartialEq)]
enum BracketComponents {
First,
Rest,
All,
None,
}
impl Expr {
fn generate_string(&self, bracket_whole: bool) -> String {
match self {
Self::None => String::from("None"),
Self::Neg { param } => format!("-{}", param.generate_string(true)),
Self::Number { value } => {
if value.fract() == 0.0 {
format!("{value:.1}")
} else {
format!("{value}")
}
}
Self::Component { component_id } => format!("#{component_id}"),
Self::Add { params } => {
Self::join_params(params, " + ", None, BracketComponents::None, bracket_whole)
}
Self::Sub { params } => {
Self::join_params(params, " - ", None, BracketComponents::Rest, bracket_whole)
}
Self::Coalesce { params } => Self::join_params(
params,
", ",
Some("COALESCE"),
BracketComponents::None,
false,
),
Self::Min { params } => {
Self::join_params(params, ", ", Some("MIN"), BracketComponents::None, false)
}
Self::Max { params } => {
Self::join_params(params, ", ", Some("MAX"), BracketComponents::None, false)
}
}
}
fn join_params(
params: &[Expr],
separator: &str,
prefix: Option<&str>,
bracket_components: BracketComponents,
bracket_whole: bool,
) -> String {
let (mut result, suffix) = match prefix {
Some(prefix) => (format!("{prefix}("), String::from(")")),
None => (String::new(), String::new()),
};
let mut num_components = 0;
for expression in params.iter() {
if num_components > 0 {
result.push_str(separator);
}
if (bracket_components == BracketComponents::First && num_components == 0)
|| (bracket_components == BracketComponents::Rest && num_components > 0)
|| (bracket_components == BracketComponents::All)
{
result.push_str(&expression.generate_string(true));
} else {
result.push_str(&expression.generate_string(false));
}
num_components += 1;
}
if bracket_whole && num_components > 1 {
String::from("(") + &result + &suffix + ")"
} else {
result + &suffix
}
}
}
#[cfg(test)]
mod tests {
use super::Expr;
#[track_caller]
fn assert_expr(exprs: &[Expr], expected: &str) {
for expr in exprs {
assert_eq!(expr.to_string(), expected);
}
}
#[test]
fn test_arithmatic() {
let comp = Expr::component;
assert_expr(
&[
comp(10) + comp(11) + comp(12) + comp(13),
comp(10) - -comp(11) + (comp(12) + comp(13)),
(comp(10) + comp(11)) - -(comp(12) - -comp(13)),
],
"#10 + #11 + #12 + #13",
);
assert_expr(
&[
-(comp(10) + comp(11) + comp(12)),
-comp(10) - comp(11) - comp(12),
-comp(10) - (comp(11) + comp(12)),
-(comp(10) + comp(11)) - comp(12),
],
"-(#10 + #11 + #12)",
);
assert_expr(
&[
comp(11) - comp(10),
comp(11) + -comp(10),
-comp(10) + comp(11),
-comp(10) - -comp(11),
],
"#11 - #10",
);
assert_expr(
&[
(comp(11) + comp(12)) - comp(10),
(comp(11) + comp(12)) + -comp(10),
-comp(10) + (comp(11) + comp(12)),
-comp(10) - -(comp(11) + comp(12)),
],
"#11 + #12 - #10",
);
assert_expr(
&[
(comp(11) - comp(12)) - comp(10),
(comp(11) - comp(12)) + -comp(10),
-comp(10) + (comp(11) - comp(12)),
-comp(10) - -(comp(11) - comp(12)),
],
"#11 - #12 - #10",
);
assert_expr(
&[
comp(11) - comp(12) + comp(10),
(comp(11) - comp(12)) - -comp(10),
(comp(11) - comp(12)) + comp(10),
-(comp(12) - comp(11)) + comp(10),
],
"#11 - #12 + #10",
);
assert_expr(
&[
(comp(11) + comp(12)) - (comp(10) + comp(13)),
(comp(11) + comp(12)) + -(comp(10) + comp(13)),
-(comp(10) + comp(13)) + (comp(11) + comp(12)),
-(comp(10) + comp(13)) - -(comp(11) + comp(12)),
],
"#11 + #12 - (#10 + #13)",
);
assert_expr(
&[
(comp(11) - comp(12)) - (comp(10) + comp(13)),
(comp(11) - comp(12)) + -(comp(10) + comp(13)),
-(comp(10) + comp(13)) + (comp(11) - comp(12)),
-(comp(10) + comp(13)) - -(comp(11) - comp(12)),
],
"#11 - #12 - (#10 + #13)",
);
assert_expr(
&[(comp(11) + comp(12)) - (comp(10) - comp(13))],
"#11 + #12 - (#10 - #13)",
);
assert_expr(
&[(comp(11) + comp(12)) + -(comp(10) - comp(13))],
"#11 + #12 + #13 - #10",
);
assert_expr(
&[
-(comp(10) - comp(13)) + (comp(11) + comp(12)),
-(comp(10) - comp(13)) - -(comp(11) + comp(12)),
],
"#13 - #10 + #11 + #12",
);
}
#[test]
fn test_functions() {
let comp = Expr::component;
let coalesce = Expr::coalesce;
let number = Expr::number;
assert_expr(
&[
comp(1) - (coalesce(comp(5), comp(7) + comp(6)) + coalesce(comp(2), comp(3)))
+ coalesce(
number(0.0).max(comp(5)),
number(0.0).max(comp(7)) + number(0.0).max(comp(6)),
),
],
concat!(
"#1 - (COALESCE(#5, #7 + #6) + COALESCE(#2, #3)) + ",
"COALESCE(MAX(0.0, #5), MAX(0.0, #7) + MAX(0.0, #6))"
),
);
assert_expr(
&[number(0.0).min(comp(5)).min(comp(7) + comp(6))
- coalesce(comp(5), comp(7) + comp(6))
.max(comp(7))
.max(number(22.44))],
"MIN(0.0, #5, #7 + #6) - MAX(COALESCE(#5, #7 + #6), #7, 22.44)",
)
}
}