use crate::analysis::expr::ast::{BinaryOp, Expr, LiteralValue, Span, UnaryOp};
#[must_use]
pub fn evaluate(expr: &Expr) -> Option<LiteralValue> {
match expr {
Expr::Literal { value, .. } => Some(value.clone()),
Expr::FieldRef { .. } | Expr::Call { .. } => None,
Expr::Unary { op, expr, .. } => {
let inner = evaluate(expr)?;
match op {
UnaryOp::Negate => match inner {
LiteralValue::Integer(v) => Some(LiteralValue::Integer(-v)),
LiteralValue::Decimal(v) => Some(LiteralValue::Decimal(-v)),
_ => None,
},
UnaryOp::Not => match inner {
LiteralValue::Boolean(v) => Some(LiteralValue::Boolean(!v)),
_ => None,
},
}
}
Expr::Binary {
op, left, right, ..
} => {
let left_val = evaluate(left)?;
let right_val = evaluate(right)?;
evaluate_binary(*op, &left_val, &right_val)
}
}
}
#[must_use]
pub fn evaluate_registry_call(callee: &str, args: &[LiteralValue]) -> Option<LiteralValue> {
match callee {
"dtcs:lower" | "dtcs:upper" => {
let LiteralValue::String(s) = args.first()? else {
return None;
};
let out = if callee == "dtcs:lower" {
s.to_lowercase()
} else {
s.to_uppercase()
};
Some(LiteralValue::String(out))
}
"dtcs:concat" => {
let mut out = String::new();
for arg in args {
let LiteralValue::String(s) = arg else {
return None;
};
out.push_str(s);
}
Some(LiteralValue::String(out))
}
"dtcs:length" => {
let value = args.first()?;
let len = match value {
LiteralValue::String(s) => s.chars().count() as i64,
_ => return None,
};
Some(LiteralValue::Integer(len))
}
_ => None,
}
}
fn evaluate_binary(
op: BinaryOp,
left: &LiteralValue,
right: &LiteralValue,
) -> Option<LiteralValue> {
match op {
BinaryOp::Add => match (left, right) {
(LiteralValue::Integer(a), LiteralValue::Integer(b)) => {
Some(LiteralValue::Integer(a.checked_add(*b)?))
}
(LiteralValue::Decimal(a), LiteralValue::Decimal(b)) => {
Some(LiteralValue::Decimal(a + b))
}
(LiteralValue::String(a), LiteralValue::String(b)) => {
Some(LiteralValue::String(format!("{a}{b}")))
}
(LiteralValue::Integer(a), LiteralValue::Decimal(b)) => {
Some(LiteralValue::Decimal(*a as f64 + b))
}
(LiteralValue::Decimal(a), LiteralValue::Integer(b)) => {
Some(LiteralValue::Decimal(a + *b as f64))
}
_ => None,
},
BinaryOp::Sub => match (left, right) {
(LiteralValue::Integer(a), LiteralValue::Integer(b)) => {
Some(LiteralValue::Integer(a.checked_sub(*b)?))
}
(LiteralValue::Decimal(a), LiteralValue::Decimal(b)) => {
Some(LiteralValue::Decimal(a - b))
}
(LiteralValue::Integer(a), LiteralValue::Decimal(b)) => {
Some(LiteralValue::Decimal(*a as f64 - b))
}
(LiteralValue::Decimal(a), LiteralValue::Integer(b)) => {
Some(LiteralValue::Decimal(a - *b as f64))
}
_ => None,
},
BinaryOp::Mul => match (left, right) {
(LiteralValue::Integer(a), LiteralValue::Integer(b)) => {
Some(LiteralValue::Integer(a.checked_mul(*b)?))
}
(LiteralValue::Decimal(a), LiteralValue::Decimal(b)) => {
Some(LiteralValue::Decimal(a * b))
}
(LiteralValue::Integer(a), LiteralValue::Decimal(b)) => {
Some(LiteralValue::Decimal(*a as f64 * b))
}
(LiteralValue::Decimal(a), LiteralValue::Integer(b)) => {
Some(LiteralValue::Decimal(a * *b as f64))
}
_ => None,
},
BinaryOp::Div => match (left, right) {
(LiteralValue::Integer(a), LiteralValue::Integer(b)) if *b != 0 => {
Some(LiteralValue::Integer(a / b))
}
(LiteralValue::Decimal(a), LiteralValue::Decimal(b)) if *b != 0.0 => {
Some(LiteralValue::Decimal(a / b))
}
(LiteralValue::Integer(a), LiteralValue::Decimal(b)) if *b != 0.0 => {
Some(LiteralValue::Decimal(*a as f64 / b))
}
(LiteralValue::Decimal(a), LiteralValue::Integer(b)) if *b != 0 => {
Some(LiteralValue::Decimal(a / *b as f64))
}
_ => None,
},
BinaryOp::Eq => Some(LiteralValue::Boolean(left == right)),
BinaryOp::Neq => Some(LiteralValue::Boolean(left != right)),
BinaryOp::Lt | BinaryOp::Lte | BinaryOp::Gt | BinaryOp::Gte => {
compare_ordered(op, left, right).map(LiteralValue::Boolean)
}
BinaryOp::And => match (left, right) {
(LiteralValue::Boolean(a), LiteralValue::Boolean(b)) => {
Some(LiteralValue::Boolean(*a && *b))
}
_ => None,
},
BinaryOp::Or => match (left, right) {
(LiteralValue::Boolean(a), LiteralValue::Boolean(b)) => {
Some(LiteralValue::Boolean(*a || *b))
}
_ => None,
},
}
}
fn compare_ordered(op: BinaryOp, left: &LiteralValue, right: &LiteralValue) -> Option<bool> {
use BinaryOp::{Gt, Gte, Lt, Lte};
let ordering = match (left, right) {
(LiteralValue::Integer(a), LiteralValue::Integer(b)) => a.cmp(b),
(LiteralValue::Decimal(a), LiteralValue::Decimal(b)) => a.partial_cmp(b)?,
(LiteralValue::String(a), LiteralValue::String(b)) => a.cmp(b),
(LiteralValue::Boolean(a), LiteralValue::Boolean(b)) => a.cmp(b),
_ => return None,
};
Some(match op {
Lt => ordering == std::cmp::Ordering::Less,
Lte => ordering != std::cmp::Ordering::Greater,
Gt => ordering == std::cmp::Ordering::Greater,
Gte => ordering != std::cmp::Ordering::Less,
_ => return None,
})
}
#[must_use]
pub fn is_zero(value: &LiteralValue) -> bool {
match value {
LiteralValue::Integer(0) => true,
LiteralValue::Decimal(v) => *v == 0.0,
_ => false,
}
}
#[must_use]
pub fn is_one(value: &LiteralValue) -> bool {
match value {
LiteralValue::Integer(1) => true,
LiteralValue::Decimal(v) => *v == 1.0,
_ => false,
}
}
#[must_use]
pub fn is_true(value: &LiteralValue) -> bool {
matches!(value, LiteralValue::Boolean(true))
}
#[must_use]
pub fn is_false(value: &LiteralValue) -> bool {
matches!(value, LiteralValue::Boolean(false))
}
#[must_use]
pub fn literal_expr(value: LiteralValue, span: Span) -> Expr {
Expr::Literal { value, span }
}