use crate::core::arithmetic_engine::{ArithmeticEngine, BigDecimalEngine};
use crate::core::eval::eval;
use crate::core::Expr;
use crate::error::{Result, TemplateError};
use crate::template::TModel;
#[derive(Clone, Copy)]
pub enum NumOp {
Sub,
Mul,
Div,
Mod,
}
impl NumOp {
pub fn symbol(self) -> &'static str {
match self {
NumOp::Sub => "-",
NumOp::Mul => "*",
NumOp::Div => "/",
NumOp::Mod => "%",
}
}
}
pub struct ArithmeticExpression {
pub left: Expr,
pub right: Expr,
pub op: NumOp,
}
impl ArithmeticExpression {
pub fn new(left: Expr, right: Expr, op: NumOp) -> Self {
ArithmeticExpression { left, right, op }
}
pub(crate) fn eval(&self, env: &mut crate::core::Environment) -> Result<TModel> {
eval_binary_number(env, &self.left, &self.right, self.op)
}
}
fn eval_binary_number(
env: &mut crate::core::Environment,
a: &Expr,
b: &Expr,
op: NumOp,
) -> Result<TModel> {
let l = eval(env, a)?;
if l.is_nothing() {
return Err(TemplateError::invalid_reference(
crate::core::environment::expr_desc(a),
));
}
let r = eval(env, b)?;
if r.is_nothing() {
return Err(TemplateError::invalid_reference(
crate::core::environment::expr_desc(b),
));
}
let l = l
.get_number()
.map_err(|e| blame_number_operand(e, env, op.symbol(), "left-hand operand", a))?;
let r = r
.get_number()
.map_err(|e| blame_number_operand(e, env, op.symbol(), "right-hand operand", b))?;
let engine = BigDecimalEngine::default();
let out = match op {
NumOp::Sub => engine.sub(&l, &r)?,
NumOp::Mul => engine.mul(&l, &r)?,
NumOp::Div => engine.div(&l, &r)?,
NumOp::Mod => engine.mod_op(&l, &r)?,
};
Ok(TModel::from_number(out))
}
fn blame_number_operand(
e: TemplateError,
env: &crate::core::Environment,
op: &str,
side: &str,
blamed: &Expr,
) -> TemplateError {
match e {
TemplateError::TypeMismatch {
expected,
actual,
ctx,
} => TemplateError::TypeMismatch {
expected,
actual,
ctx: Box::new(crate::error::ErrorCtx {
blamer: Some(format!("For \"{op}\" {side}: ")),
blamed_expr: Some(crate::core::environment::expr_desc(blamed)),
span: blamed.span,
template_name: Some(env.current_template_name.clone()),
..*ctx
}),
},
other => other,
}
}