use crate::core::expression::{
eval_interp_str, ArithmeticExpression, BooleanLiteral, BuiltinVariable, ComparisonExpression,
DefaultToExpression, Dot, DynamicKeyName, ExistsExpression, HashLiteral, Identifier,
ListLiteral, LocalLambdaExpression, MethodCall, NotExpression, NumOp, NumberLiteral,
ParentheticalExpression, Range, StringLiteral, UnaryPlusMinusExpression,
};
use crate::core::{Expr, ExprKind};
use crate::error::{Result, TemplateError};
use crate::span::Span;
use crate::template::TModel;
use crate::value::TNumber;
use bigdecimal::ToPrimitive;
pub fn eval(env: &mut crate::core::Environment, expr: &Expr) -> Result<TModel> {
let r = eval_inner(env, expr);
match r {
Err(e) => Err(attach_eval_location(e, env, expr.span)),
ok => ok,
}
}
fn attach_eval_location(
e: TemplateError,
env: &crate::core::Environment,
span: Span,
) -> TemplateError {
if e.has_location() {
return e;
}
e.with_location(&env.current_template_name, span)
}
fn eval_inner(env: &mut crate::core::Environment, expr: &Expr) -> Result<TModel> {
match &expr.kind {
ExprKind::Str(s) => StringLiteral::new(s.clone()).eval(env),
ExprKind::InterpStr(parts) => eval_interp_str(env, parts),
ExprKind::Num(n) => NumberLiteral::new(n.clone()).eval(env),
ExprKind::Bool(b) => BooleanLiteral::new(*b).eval(env),
ExprKind::Ident(name) => Identifier::new(name.clone()).eval(env),
ExprKind::BuiltinVar(v) => BuiltinVariable::new(*v).eval(env),
ExprKind::Dot { target, name } => Dot::new((**target).clone(), name.clone()).eval(env),
ExprKind::DynKey { target, key } => {
DynamicKeyName::new((**target).clone(), (**key).clone()).eval(env)
}
ExprKind::Call { callee, args } => {
MethodCall::new((**callee).clone(), args.clone()).eval(env)
}
ExprKind::UnaryMinus(t) => UnaryPlusMinusExpression::new((**t).clone()).eval(env),
ExprKind::Not(t) => NotExpression::new((**t).clone()).eval(env),
ExprKind::Add(a, b) => {
crate::core::expression::AddConcatExpression::new((**a).clone(), (**b).clone())
.eval(env)
}
ExprKind::Sub(a, b) => {
ArithmeticExpression::new((**a).clone(), (**b).clone(), NumOp::Sub).eval(env)
}
ExprKind::Mul(a, b) => {
ArithmeticExpression::new((**a).clone(), (**b).clone(), NumOp::Mul).eval(env)
}
ExprKind::Div(a, b) => {
ArithmeticExpression::new((**a).clone(), (**b).clone(), NumOp::Div).eval(env)
}
ExprKind::Mod(a, b) => {
ArithmeticExpression::new((**a).clone(), (**b).clone(), NumOp::Mod).eval(env)
}
ExprKind::Eq(a, b) => {
ComparisonExpression::new((**a).clone(), (**b).clone(), CmpOp::Eq).eval(env)
}
ExprKind::NotEq(a, b) => {
ComparisonExpression::new((**a).clone(), (**b).clone(), CmpOp::NotEq).eval(env)
}
ExprKind::Gt(a, b) => {
ComparisonExpression::new((**a).clone(), (**b).clone(), CmpOp::Gt).eval(env)
}
ExprKind::Gte(a, b) => {
ComparisonExpression::new((**a).clone(), (**b).clone(), CmpOp::Gte).eval(env)
}
ExprKind::Lt(a, b) => {
ComparisonExpression::new((**a).clone(), (**b).clone(), CmpOp::Lt).eval(env)
}
ExprKind::Lte(a, b) => {
ComparisonExpression::new((**a).clone(), (**b).clone(), CmpOp::Lte).eval(env)
}
ExprKind::And(a, b) => {
crate::core::expression::AndExpression::new((**a).clone(), (**b).clone()).eval(env)
}
ExprKind::Or(a, b) => {
crate::core::expression::OrExpression::new((**a).clone(), (**b).clone()).eval(env)
}
ExprKind::Range { start, end, kind } => Range::new(
(**start).clone(),
end.as_ref().map(|e| (**e).clone()),
*kind,
)
.eval(env),
ExprKind::Default { target, default } => {
DefaultToExpression::new((**target).clone(), default.as_ref().map(|d| (**d).clone()))
.eval(env)
}
ExprKind::Exists(t) => ExistsExpression::new((**t).clone()).eval(env),
ExprKind::BuiltIn { target, name, args } => {
crate::core::expression::eval_builtin(env, target, name, args)
}
ExprKind::ListLit(items) => ListLiteral::new(items.clone()).eval(env),
ExprKind::HashLit(pairs) => HashLiteral::new(pairs.clone()).eval(env),
ExprKind::Lambda { params, body } => {
LocalLambdaExpression::new(params.clone(), (**body).clone()).eval(env)
}
ExprKind::Paren(inner) => ParentheticalExpression::new((**inner).clone()).eval(env),
}
}
pub(crate) fn model_to_boolean(env: &crate::core::Environment, m: &TModel) -> Result<bool> {
if let Some(b) = &m.boolean {
return b.as_boolean();
}
if !env.settings.classic_compatible {
return Err(TemplateError::type_mismatch("boolean", m.type_name));
}
if m.is_nothing() {
return Ok(false);
}
if let Some(s) = &m.scalar {
return Ok(!s.as_string()?.is_empty());
}
if let Some(seq) = &m.sequence {
return Ok(seq.size()? != 0);
}
if let Some(col) = &m.collection {
return Ok(col.iterator()?.next().is_some());
}
if let Some(ex) = &m.hash_ex {
return Ok(ex.size()? != 0);
}
Ok(true)
}
pub(crate) use crate::core::expression::check_legacy_escaping_ban;
pub(crate) use crate::core::expression::compare_numbers;
pub use crate::core::expression::{compare_models, CmpOp};
pub(crate) fn eval_lenient(env: &mut crate::core::Environment, target: &Expr) -> Result<TModel> {
let catches = matches!(&target.kind, ExprKind::Paren(_) | ExprKind::Ident(_));
match eval(env, target) {
Ok(m) => Ok(m),
Err(TemplateError::InvalidReference { .. }) if catches => Ok(TModel::nothing()),
Err(e) => Err(e),
}
}
pub(crate) fn trunc_i64(n: &TNumber) -> Option<i64> {
match n {
TNumber::Int(v) => Some(*v as i64),
TNumber::Long(v) => Some(*v),
TNumber::BigInt(v) => i64::try_from(v.clone()).ok(),
TNumber::Decimal(d) => i64::try_from(d.with_scale(0).as_bigint_and_scale().0.as_ref()).ok(),
TNumber::Float(v) => Some(*v as i64),
TNumber::Double(v) => Some(*v as i64),
}
}
pub(crate) fn to_int_exact(n: &TNumber) -> Option<i64> {
match n {
TNumber::Int(v) => Some(*v as i64),
TNumber::Long(v) => Some(*v),
TNumber::BigInt(v) => i64::try_from(v.clone()).ok(),
TNumber::Decimal(d) => {
if d.is_integer() {
d.to_i64()
} else {
None
}
}
TNumber::Float(v) => {
if v.is_finite() && v.fract() == 0.0 {
Some(*v as i64)
} else {
None
}
}
TNumber::Double(v) => {
if v.is_finite() && v.fract() == 0.0 {
Some(*v as i64)
} else {
None
}
}
}
}
#[cfg(test)]
#[path = "eval_tests.rs"]
mod eval_tests;