use crate::ast::{
BinaryOperator, Literal, NumberLiteral, NumberValue, PrimitiveType, UnaryOperator,
};
use crate::ir::{IrExpr, ResolvedType};
pub(super) fn fold_binary_op(
left: &Literal,
op: BinaryOperator,
right: &Literal,
ty: &ResolvedType,
) -> Option<IrExpr> {
match (left, right) {
(Literal::Number(l), Literal::Number(r)) => fold_numeric_pair(*l, op, *r, ty),
(Literal::Boolean(l), Literal::Boolean(r)) => fold_boolean_pair(*l, op, *r),
(Literal::String(l), Literal::String(r)) => fold_string_pair(l, op, r),
_ => None,
}
}
fn fold_numeric_pair(
l: NumberLiteral,
op: BinaryOperator,
r: NumberLiteral,
ty: &ResolvedType,
) -> Option<IrExpr> {
if let (NumberValue::Integer(li), NumberValue::Integer(ri)) = (l.value, r.value) {
return fold_integer_pair(l, li, op, ri, ty);
}
fold_float_pair(l, l.value.as_f64(), op, r.value.as_f64(), ty)
}
fn fold_integer_pair(
l: NumberLiteral,
li: i128,
op: BinaryOperator,
ri: i128,
ty: &ResolvedType,
) -> Option<IrExpr> {
let combine = |v: i128| {
Literal::Number(NumberLiteral::from_lex(
NumberValue::Integer(v),
l.suffix,
l.kind,
))
};
let result = match op {
BinaryOperator::Add => li.checked_add(ri).map(combine),
BinaryOperator::Sub => li.checked_sub(ri).map(combine),
BinaryOperator::Mul => li.checked_mul(ri).map(combine),
BinaryOperator::Div if ri != 0 => li.checked_div(ri).map(combine),
BinaryOperator::Mod if ri != 0 => li.checked_rem(ri).map(combine),
BinaryOperator::Lt => Some(Literal::Boolean(li < ri)),
BinaryOperator::Le => Some(Literal::Boolean(li <= ri)),
BinaryOperator::Gt => Some(Literal::Boolean(li > ri)),
BinaryOperator::Ge => Some(Literal::Boolean(li >= ri)),
BinaryOperator::Eq => Some(Literal::Boolean(li == ri)),
BinaryOperator::Ne => Some(Literal::Boolean(li != ri)),
BinaryOperator::Div
| BinaryOperator::Mod
| BinaryOperator::And
| BinaryOperator::Or
| BinaryOperator::Range => None,
};
result.map(|value| build_numeric_result(value, ty))
}
fn fold_float_pair(
l: NumberLiteral,
lv: f64,
op: BinaryOperator,
rv: f64,
ty: &ResolvedType,
) -> Option<IrExpr> {
let combine = |v: f64| {
Literal::Number(NumberLiteral::from_lex(
NumberValue::Float(v),
l.suffix,
l.kind,
))
};
let result = match op {
BinaryOperator::Add => Some(combine(lv + rv)),
BinaryOperator::Sub => Some(combine(lv - rv)),
BinaryOperator::Mul => Some(combine(lv * rv)),
BinaryOperator::Div if rv != 0.0 => Some(combine(lv / rv)),
#[expect(
clippy::modulo_arithmetic,
reason = "f64 modulo with rv != 0 guard mirrors BinaryOp::Mod runtime semantics"
)]
BinaryOperator::Mod if rv != 0.0 => Some(combine(lv % rv)),
BinaryOperator::Lt => Some(Literal::Boolean(lv < rv)),
BinaryOperator::Le => Some(Literal::Boolean(lv <= rv)),
BinaryOperator::Gt => Some(Literal::Boolean(lv > rv)),
BinaryOperator::Ge => Some(Literal::Boolean(lv >= rv)),
#[expect(
clippy::float_cmp,
reason = "IEEE 754 equality is intentional for constant folding"
)]
BinaryOperator::Eq => Some(Literal::Boolean(lv == rv)),
#[expect(
clippy::float_cmp,
reason = "IEEE 754 inequality is intentional for constant folding"
)]
BinaryOperator::Ne => Some(Literal::Boolean(lv != rv)),
BinaryOperator::Div
| BinaryOperator::Mod
| BinaryOperator::And
| BinaryOperator::Or
| BinaryOperator::Range => None,
};
result.map(|value| build_numeric_result(value, ty))
}
fn build_numeric_result(value: Literal, ty: &ResolvedType) -> IrExpr {
let result_ty = match &value {
Literal::Boolean(_) => ResolvedType::Primitive(PrimitiveType::Boolean),
Literal::String(_)
| Literal::Number(_)
| Literal::Regex { .. }
| Literal::Path(_)
| Literal::Nil => ty.clone(),
};
IrExpr::Literal {
value,
ty: result_ty,
span: crate::ir::IrSpan::default(),
}
}
fn fold_boolean_pair(l: bool, op: BinaryOperator, r: bool) -> Option<IrExpr> {
let result = match op {
BinaryOperator::And => Some(Literal::Boolean(l && r)),
BinaryOperator::Or => Some(Literal::Boolean(l || r)),
BinaryOperator::Eq => Some(Literal::Boolean(l == r)),
BinaryOperator::Ne => Some(Literal::Boolean(l != r)),
BinaryOperator::Add
| BinaryOperator::Sub
| BinaryOperator::Mul
| BinaryOperator::Div
| BinaryOperator::Mod
| BinaryOperator::Lt
| BinaryOperator::Gt
| BinaryOperator::Le
| BinaryOperator::Ge
| BinaryOperator::Range => None,
};
result.map(|value| IrExpr::Literal {
value,
ty: ResolvedType::Primitive(PrimitiveType::Boolean),
span: crate::ir::IrSpan::default(),
})
}
fn fold_string_pair(l: &str, op: BinaryOperator, r: &str) -> Option<IrExpr> {
if op == BinaryOperator::Add {
Some(IrExpr::Literal {
value: Literal::String(format!("{l}{r}")),
ty: ResolvedType::Primitive(PrimitiveType::String),
span: crate::ir::IrSpan::default(),
})
} else {
None
}
}
pub(super) fn fold_unary_op(
op: UnaryOperator,
operand: &Literal,
ty: &ResolvedType,
) -> Option<IrExpr> {
match operand {
Literal::Number(n) => {
if op == UnaryOperator::Neg {
let new_value = match n.value {
NumberValue::Integer(v) => NumberValue::Integer(v.checked_neg()?),
NumberValue::Float(f) => NumberValue::Float(-f),
};
Some(IrExpr::Literal {
value: Literal::Number(NumberLiteral::from_lex(new_value, n.suffix, n.kind)),
ty: ty.clone(),
span: crate::ir::IrSpan::default(),
})
} else {
None
}
}
Literal::Boolean(b) => {
if op == UnaryOperator::Not {
Some(IrExpr::Literal {
value: Literal::Boolean(!b),
ty: ResolvedType::Primitive(PrimitiveType::Boolean),
span: crate::ir::IrSpan::default(),
})
} else {
None
}
}
Literal::String(_) | Literal::Regex { .. } | Literal::Path(_) | Literal::Nil => None,
}
}