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 } if !params.is_empty() => {
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.render())
}
}
impl Expr {
fn render(&self) -> String {
match self {
Self::None => String::from("None"),
Self::Neg { param } => format!("-{}", param.render_grouped()),
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, " + "),
Self::Sub { params } => match params.split_first() {
Some((first, rest)) => {
let mut result = first.render();
for param in rest {
result.push_str(" - ");
result.push_str(¶m.render_grouped());
}
result
}
None => String::new(),
},
Self::Coalesce { params } => format!("COALESCE({})", Self::join(params, ", ")),
Self::Min { params } => format!("MIN({})", Self::join(params, ", ")),
Self::Max { params } => format!("MAX({})", Self::join(params, ", ")),
}
}
fn render_grouped(&self) -> String {
match self {
Self::Add { params } | Self::Sub { params } if params.len() > 1 => {
format!("({})", self.render())
}
_ => self.render(),
}
}
fn join(params: &[Expr], separator: &str) -> String {
params
.iter()
.map(Self::render)
.collect::<Vec<_>>()
.join(separator)
}
}
#[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)",
)
}
}