use serde_json::Value;
use super::helpers::{
create_number_value, extract_datetime_value, extract_duration_value, safe_add, safe_divide,
safe_modulo, safe_multiply, safe_subtract,
};
use crate::config::NanHandling;
use crate::constants::INVALID_ARGS;
use crate::value_helpers::{coerce_to_number, try_coerce_to_integer};
use crate::{CompiledNode, ContextStack, DataLogic, Error, Result};
enum NanAction {
Skip,
ReturnNull,
}
#[inline]
fn handle_nan(engine: &DataLogic) -> Result<NanAction> {
match engine.config().arithmetic_nan_handling {
NanHandling::ThrowError => Err(crate::constants::nan_error()),
NanHandling::IgnoreValue | NanHandling::CoerceToZero => Ok(NanAction::Skip),
NanHandling::ReturnNull => Ok(NanAction::ReturnNull),
}
}
#[inline]
fn number_value(f: f64) -> Value {
if f.is_finite() && f.floor() == f && f >= i64::MIN as f64 && f <= i64::MAX as f64 {
Value::Number((f as i64).into())
} else {
create_number_value(f)
}
}
#[inline]
pub fn evaluate_add(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
) -> Result<Value> {
if args.is_empty() {
return Ok(Value::Number(0.into()));
}
if args.len() == 1 {
if matches!(&args[0], CompiledNode::Array { .. }) {
return Err(crate::constants::nan_error());
}
if let CompiledNode::Value { value, .. } = &args[0]
&& matches!(value, Value::Array(_))
{
return Err(crate::constants::nan_error());
}
let value = engine.evaluate_node(&args[0], context)?;
if let Value::Array(arr) = value {
if arr.is_empty() {
return Ok(Value::Number(0.into())); }
let mut all_integers = true;
let mut int_sum: i64 = 0;
let mut float_sum = 0.0;
for elem in &arr {
if let Some(i) = try_coerce_to_integer(elem, engine) {
if all_integers {
match int_sum.checked_add(i) {
Some(sum) => int_sum = sum,
None => {
all_integers = false;
float_sum = int_sum as f64 + i as f64;
}
}
} else {
float_sum = safe_add(float_sum, i as f64);
}
} else if let Some(f) = coerce_to_number(elem, engine) {
all_integers = false;
float_sum = safe_add(float_sum, f);
} else {
match handle_nan(engine)? {
NanAction::Skip => continue,
NanAction::ReturnNull => return Ok(Value::Null),
}
}
}
return if all_integers {
Ok(Value::Number(int_sum.into()))
} else {
Ok(number_value(float_sum))
};
}
}
if args.len() == 2 {
let first = engine.evaluate_node_cow(&args[0], context)?;
let second = engine.evaluate_node_cow(&args[1], context)?;
if let (Some(i1), Some(i2)) = (
try_coerce_to_integer(&first, engine),
try_coerce_to_integer(&second, engine),
) {
return match i1.checked_add(i2) {
Some(sum) => Ok(Value::Number(sum.into())),
None => Ok(number_value(i1 as f64 + i2 as f64)),
};
}
if let (Some(f1), Some(f2)) = (
coerce_to_number(&first, engine),
coerce_to_number(&second, engine),
) {
return Ok(number_value(safe_add(f1, f2)));
}
let first_dt = extract_datetime_value(first.as_ref());
let first_dur = if first_dt.is_none() {
extract_duration_value(first.as_ref())
} else {
None
};
let second_dur = extract_duration_value(second.as_ref());
if let (Some(dt), Some(dur)) = (&first_dt, &second_dur) {
let result = dt.add_duration(dur);
return Ok(Value::String(result.to_iso_string()));
}
if let (Some(dur1), Some(dur2)) = (&first_dur, &second_dur) {
let result = dur1.add(dur2);
return Ok(Value::String(result.to_string()));
}
let mut sum = 0.0f64;
for val in [first.as_ref(), second.as_ref()] {
if let Some(f) = coerce_to_number(val, engine) {
sum = safe_add(sum, f);
} else {
match handle_nan(engine)? {
NanAction::Skip => {}
NanAction::ReturnNull => return Ok(Value::Null),
}
}
}
return Ok(number_value(sum));
}
let mut all_integers = true;
let mut int_sum: i64 = 0;
let mut float_sum = 0.0;
for arg in args {
if matches!(arg, CompiledNode::Array { .. }) {
match handle_nan(engine)? {
NanAction::Skip => continue,
NanAction::ReturnNull => return Ok(Value::Null),
}
}
let value = engine.evaluate_node_cow(arg, context)?;
if matches!(value.as_ref(), Value::Array(_) | Value::Object(_)) {
match handle_nan(engine)? {
NanAction::Skip => continue,
NanAction::ReturnNull => return Ok(Value::Null),
}
}
if let Some(i) = try_coerce_to_integer(&value, engine) {
if all_integers {
match int_sum.checked_add(i) {
Some(sum) => int_sum = sum,
None => {
all_integers = false;
float_sum = int_sum as f64 + i as f64;
}
}
} else {
float_sum = safe_add(float_sum, i as f64);
}
} else if let Some(f) = coerce_to_number(&value, engine) {
if all_integers {
all_integers = false;
float_sum = int_sum as f64 + f;
} else {
float_sum = safe_add(float_sum, f);
}
} else {
match handle_nan(engine)? {
NanAction::Skip => continue,
NanAction::ReturnNull => return Ok(Value::Null),
}
}
}
if all_integers {
Ok(Value::Number(int_sum.into()))
} else {
Ok(number_value(float_sum))
}
}
#[inline]
pub fn evaluate_subtract(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
) -> Result<Value> {
if args.is_empty() {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let first = engine.evaluate_node(&args[0], context)?;
if args.len() == 1 {
if let Value::Array(arr) = first {
if arr.is_empty() {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let mut result =
coerce_to_number(&arr[0], engine).ok_or_else(crate::constants::nan_error)?;
for elem in &arr[1..] {
let num = coerce_to_number(elem, engine).ok_or_else(crate::constants::nan_error)?;
result = safe_subtract(result, num);
}
return Ok(number_value(result));
}
if let Value::Number(n) = &first {
if let Some(i) = n.as_i64() {
return Ok(Value::Number((-i).into()));
} else if let Some(f) = n.as_f64() {
return Ok(number_value(-f));
}
}
let first_num = coerce_to_number(&first, engine).ok_or_else(crate::constants::nan_error)?;
Ok(number_value(-first_num))
} else if args.len() == 2 {
let second = engine.evaluate_node_cow(&args[1], context)?;
if let (Some(i1), Some(i2)) = (
try_coerce_to_integer(&first, engine),
try_coerce_to_integer(&second, engine),
) {
return match i1.checked_sub(i2) {
Some(diff) => Ok(Value::Number(diff.into())),
None => Ok(number_value(i1 as f64 - i2 as f64)),
};
}
if let (Some(f1), Some(f2)) = (
coerce_to_number(&first, engine),
coerce_to_number(&second, engine),
) {
return Ok(number_value(safe_subtract(f1, f2)));
}
let first_dt = extract_datetime_value(&first);
let first_dur = if first_dt.is_none() {
extract_duration_value(&first)
} else {
None
};
let second_dt = extract_datetime_value(second.as_ref());
let second_dur = if second_dt.is_none() {
extract_duration_value(second.as_ref())
} else {
None
};
if let (Some(dt1), Some(dt2)) = (&first_dt, &second_dt) {
let result = dt1.diff(dt2);
return Ok(Value::String(result.to_string()));
}
if let (Some(dt), Some(dur)) = (&first_dt, &second_dur) {
let result = dt.sub_duration(dur);
return Ok(Value::String(result.to_iso_string()));
}
if let (Some(dur1), Some(dur2)) = (&first_dur, &second_dur) {
let result = dur1.sub(dur2);
return Ok(Value::String(result.to_string()));
}
if let (Some(i1), Some(i2)) = (
try_coerce_to_integer(&first, engine),
try_coerce_to_integer(&second, engine),
) {
match i1.checked_sub(i2) {
Some(result) => return Ok(Value::Number(result.into())),
None => {
}
}
}
let first_num = coerce_to_number(&first, engine).ok_or_else(crate::constants::nan_error)?;
let second_num =
coerce_to_number(&second, engine).ok_or_else(crate::constants::nan_error)?;
Ok(number_value(first_num - second_num))
} else {
let mut all_integers = true;
let mut int_result = if let Some(i) = try_coerce_to_integer(&first, engine) {
i
} else {
all_integers = false;
0
};
let mut float_result = if let Some(f) = coerce_to_number(&first, engine) {
f
} else {
return Ok(Value::Null);
};
for item in args.iter().skip(1) {
let value = engine.evaluate_node_cow(item, context)?;
if all_integers {
if let Some(i) = try_coerce_to_integer(&value, engine) {
match int_result.checked_sub(i) {
Some(result) => int_result = result,
None => {
all_integers = false;
float_result = int_result as f64 - i as f64;
}
}
} else if let Some(f) = coerce_to_number(&value, engine) {
all_integers = false;
float_result = int_result as f64 - f;
} else {
match handle_nan(engine)? {
NanAction::Skip => continue,
NanAction::ReturnNull => return Ok(Value::Null),
}
}
} else if let Some(f) = coerce_to_number(&value, engine) {
float_result = safe_subtract(float_result, f);
} else {
match handle_nan(engine)? {
NanAction::Skip => continue,
NanAction::ReturnNull => return Ok(Value::Null),
}
}
}
if all_integers {
Ok(Value::Number(int_result.into()))
} else {
Ok(number_value(float_result))
}
}
}
#[inline]
pub fn evaluate_multiply(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
) -> Result<Value> {
if args.is_empty() {
return Ok(Value::Number(1.into()));
}
if args.len() == 1 {
let value = engine.evaluate_node(&args[0], context)?;
if let Value::Array(arr) = value {
if arr.is_empty() {
return Ok(Value::Number(1.into())); }
let mut all_integers = true;
let mut int_product: i64 = 1;
let mut float_product = 1.0;
for elem in &arr {
if let Some(i) = try_coerce_to_integer(elem, engine) {
if all_integers {
match int_product.checked_mul(i) {
Some(p) => int_product = p,
None => {
all_integers = false;
float_product = int_product as f64 * i as f64;
}
}
} else {
float_product = safe_multiply(float_product, i as f64);
}
} else if let Some(f) = coerce_to_number(elem, engine) {
if all_integers {
float_product = int_product as f64 * f;
} else {
float_product = safe_multiply(float_product, f);
}
all_integers = false;
} else {
match handle_nan(engine)? {
NanAction::Skip => continue,
NanAction::ReturnNull => return Ok(Value::Null),
}
}
}
return if all_integers {
Ok(Value::Number(int_product.into()))
} else {
Ok(number_value(float_product))
};
}
}
if args.len() == 2 {
let first = engine.evaluate_node_cow(&args[0], context)?;
let second = engine.evaluate_node_cow(&args[1], context)?;
if let (Some(i1), Some(i2)) = (
try_coerce_to_integer(&first, engine),
try_coerce_to_integer(&second, engine),
) {
return match i1.checked_mul(i2) {
Some(product) => Ok(Value::Number(product.into())),
None => Ok(number_value(i1 as f64 * i2 as f64)),
};
}
if let (Some(f1), Some(f2)) = (
coerce_to_number(&first, engine),
coerce_to_number(&second, engine),
) {
return Ok(number_value(safe_multiply(f1, f2)));
}
let first_dur = extract_duration_value(first.as_ref());
if let Some(dur) = &first_dur
&& let Some(factor) = coerce_to_number(&second, engine)
{
let result = dur.multiply(factor);
return Ok(Value::String(result.to_string()));
}
if first_dur.is_none() {
let second_dur = extract_duration_value(second.as_ref());
if let Some(dur) = second_dur
&& let Some(factor) = coerce_to_number(&first, engine)
{
let result = dur.multiply(factor);
return Ok(Value::String(result.to_string()));
}
}
}
let mut all_integers = true;
let mut int_product: i64 = 1;
let mut float_product = 1.0;
for arg in args {
let value = engine.evaluate_node_cow(arg, context)?;
if let Some(i) = try_coerce_to_integer(&value, engine) {
if all_integers {
match int_product.checked_mul(i) {
Some(p) => int_product = p,
None => {
all_integers = false;
float_product = int_product as f64 * i as f64;
}
}
} else {
float_product = safe_multiply(float_product, i as f64);
}
} else if let Some(f) = coerce_to_number(&value, engine) {
if all_integers {
float_product = int_product as f64 * f;
} else {
float_product = safe_multiply(float_product, f);
}
all_integers = false;
} else {
match handle_nan(engine)? {
NanAction::Skip => {}
NanAction::ReturnNull => return Ok(Value::Null),
}
}
}
if all_integers {
Ok(Value::Number(int_product.into()))
} else {
Ok(number_value(float_product))
}
}
#[inline]
pub fn evaluate_divide(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
) -> Result<Value> {
if args.is_empty() {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
if args.len() == 1 {
let value = engine.evaluate_node(&args[0], context)?;
if let Value::Array(arr) = value {
if arr.is_empty() {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let mut result =
coerce_to_number(&arr[0], engine).ok_or_else(crate::constants::nan_error)?;
for elem in &arr[1..] {
let num = coerce_to_number(elem, engine).ok_or_else(crate::constants::nan_error)?;
if num == 0.0 {
return Err(crate::constants::nan_error());
}
result = safe_divide(result, num);
}
return Ok(number_value(result));
}
let num = coerce_to_number(&value, engine).ok_or_else(crate::constants::nan_error)?;
if num == 0.0 {
return Err(crate::constants::nan_error());
}
if let Some(i) = try_coerce_to_integer(&value, engine)
&& i != 0
{
if i == -1 {
return Ok(Value::Number((-1).into()));
}
if 1 % i == 0 {
return Ok(Value::Number((1 / i).into()));
}
}
return Ok(number_value(1.0 / num));
}
let first = engine.evaluate_node(&args[0], context)?;
if args.len() == 2 {
let second = engine.evaluate_node_cow(&args[1], context)?;
let first_dur = extract_duration_value(&first);
if let Some(dur) = first_dur
&& let Some(divisor) = coerce_to_number(&second, engine)
{
if divisor == 0.0 {
return Err(crate::constants::nan_error());
}
let result = dur.divide(divisor);
return Ok(Value::String(result.to_string()));
}
if let (Some(i1), Some(i2)) = (
try_coerce_to_integer(&first, engine),
try_coerce_to_integer(&second, engine),
) {
if i2 == 0 {
return Err(crate::constants::nan_error());
}
if i1 == i64::MIN && i2 == -1 {
return Ok(number_value(-(i64::MIN as f64)));
}
if i1 % i2 == 0 {
return Ok(Value::Number((i1 / i2).into()));
}
}
let first_num = coerce_to_number(&first, engine).ok_or_else(crate::constants::nan_error)?;
let second_num =
coerce_to_number(&second, engine).ok_or_else(crate::constants::nan_error)?;
if second_num == 0.0 {
return Err(crate::constants::nan_error());
}
Ok(number_value(first_num / second_num))
} else {
let mut all_integers = true;
let mut int_result = if let Some(i) = try_coerce_to_integer(&first, engine) {
i
} else {
all_integers = false;
0
};
let mut float_result =
coerce_to_number(&first, engine).ok_or_else(crate::constants::nan_error)?;
for item in args.iter().skip(1) {
let value = engine.evaluate_node_cow(item, context)?;
if all_integers {
if let Some(divisor) = try_coerce_to_integer(&value, engine) {
if divisor == 0 {
return Err(crate::constants::nan_error());
}
if int_result == i64::MIN && divisor == -1 {
all_integers = false;
float_result = -(i64::MIN as f64);
} else if int_result % divisor == 0 {
int_result /= divisor;
} else {
all_integers = false;
float_result = int_result as f64 / divisor as f64;
}
} else if let Some(divisor) = coerce_to_number(&value, engine) {
if divisor == 0.0 {
return Err(crate::constants::nan_error());
}
all_integers = false;
float_result = int_result as f64 / divisor;
} else {
return Ok(Value::Null);
}
} else {
let divisor =
coerce_to_number(&value, engine).ok_or_else(crate::constants::nan_error)?;
if divisor == 0.0 {
return Err(crate::constants::nan_error());
}
float_result = safe_divide(float_result, divisor);
}
}
if all_integers {
Ok(Value::Number(int_result.into()))
} else {
Ok(number_value(float_result))
}
}
}
#[inline]
pub fn evaluate_modulo(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
) -> Result<Value> {
if args.is_empty() {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
if args.len() == 1 {
let value = engine.evaluate_node(&args[0], context)?;
if let Value::Array(arr) = value {
if arr.is_empty() || arr.len() < 2 {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let mut result =
coerce_to_number(&arr[0], engine).ok_or_else(crate::constants::nan_error)?;
for elem in &arr[1..] {
let num = coerce_to_number(elem, engine).ok_or_else(crate::constants::nan_error)?;
if num == 0.0 {
return Err(crate::constants::nan_error());
}
result = safe_modulo(result, num);
}
return Ok(number_value(result));
}
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
if args.len() < 2 {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let first = engine.evaluate_node(&args[0], context)?;
if args.len() == 2 {
let second = engine.evaluate_node_cow(&args[1], context)?;
if let (Value::Number(n1), Value::Number(n2)) = (&first, second.as_ref())
&& let (Some(i1), Some(i2)) = (n1.as_i64(), n2.as_i64())
{
if i2 == 0 {
return Err(crate::constants::nan_error());
}
if i1 == i64::MIN && i2 == -1 {
return Ok(Value::Number(0.into()));
}
return Ok(Value::Number((i1 % i2).into()));
}
let first_num = coerce_to_number(&first, engine).ok_or_else(crate::constants::nan_error)?;
let second_num =
coerce_to_number(&second, engine).ok_or_else(crate::constants::nan_error)?;
if second_num == 0.0 {
return Err(crate::constants::nan_error());
}
Ok(number_value(first_num % second_num))
} else {
let mut result =
coerce_to_number(&first, engine).ok_or_else(crate::constants::nan_error)?;
for item in args.iter().skip(1) {
let value = engine.evaluate_node_cow(item, context)?;
let num = coerce_to_number(&value, engine).ok_or_else(crate::constants::nan_error)?;
if num == 0.0 {
return Err(crate::constants::nan_error());
}
result = safe_modulo(result, num);
}
Ok(number_value(result))
}
}
#[inline]
pub fn evaluate_max(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
) -> Result<Value> {
if args.is_empty() {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
if args.len() == 1 {
if matches!(&args[0], CompiledNode::Array { .. }) {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
if let CompiledNode::Value { value, .. } = &args[0]
&& matches!(value, Value::Array(_))
{
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let value = engine.evaluate_node(&args[0], context)?;
if let Value::Array(arr) = value {
if arr.is_empty() {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let mut max_value: Option<Value> = None;
let mut max_num = f64::NEG_INFINITY;
for elem in arr {
if let Value::Number(n) = &elem {
if let Some(f) = n.as_f64()
&& f > max_num
{
max_num = f;
max_value = Some(elem);
}
} else {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
}
return Ok(max_value.unwrap_or(Value::Null));
}
if !matches!(value, Value::Number(_)) {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
return Ok(value);
}
let mut max_value: Option<Value> = None;
let mut max_num = f64::NEG_INFINITY;
for arg in args {
let value = engine.evaluate_node(arg, context)?;
if let Value::Number(n) = &value {
if let Some(f) = n.as_f64()
&& f > max_num
{
max_num = f;
max_value = Some(value);
}
} else {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
}
Ok(max_value.unwrap_or(Value::Null))
}
#[inline]
pub fn evaluate_min(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
) -> Result<Value> {
if args.is_empty() {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
if args.len() == 1 {
if matches!(&args[0], CompiledNode::Array { .. }) {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
if let CompiledNode::Value { value, .. } = &args[0]
&& matches!(value, Value::Array(_))
{
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let value = engine.evaluate_node(&args[0], context)?;
if let Value::Array(arr) = value {
if arr.is_empty() {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let mut min_value: Option<Value> = None;
let mut min_num = f64::INFINITY;
for elem in arr {
if let Value::Number(n) = &elem {
if let Some(f) = n.as_f64()
&& f < min_num
{
min_num = f;
min_value = Some(elem);
}
} else {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
}
return Ok(min_value.unwrap_or(Value::Null));
}
if !matches!(value, Value::Number(_)) {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
return Ok(value);
}
let mut min_value: Option<Value> = None;
let mut min_num = f64::INFINITY;
for arg in args {
let value = engine.evaluate_node(arg, context)?;
if let Value::Number(n) = &value {
if let Some(f) = n.as_f64()
&& f < min_num
{
min_num = f;
min_value = Some(value);
}
} else {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
}
Ok(min_value.unwrap_or(Value::Null))
}