use crate::arena::{ContextStack, DataValue, coerce_to_number_cfg, try_coerce_to_integer_cfg};
use crate::{CompiledNode, Engine, Result};
use bumpalo::Bump;
use datavalue::NumberValue;
use super::helpers::{
ArithOp, FoldState, FoldStepOutcome, NanAction, VariadicFoldSpec, alloc_number,
coerce_pair_f64, coerce_pair_int, handle_nan, try_int_op, variadic_fold,
};
#[inline]
pub(crate) fn evaluate_add<'a>(
args: &'a [CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
if args.is_empty() {
return Ok(alloc_number(arena, NumberValue::from_i64(0)));
}
if args.len() == 1 {
return one_arg_arith(&args[0], ctx, engine, arena, ArithOp::Add);
}
if args.len() == 2 {
return add_two_arg(&args[0], &args[1], ctx, engine, arena);
}
variadic_fold(
args,
ctx,
engine,
arena,
VariadicFoldSpec {
int_init: 0,
float_init: 0.0,
i_combine: i64::checked_add,
f_combine: |a, b| a + b,
},
)
}
#[inline]
fn add_two_arg<'a>(
a: &'a CompiledNode,
b: &'a CompiledNode,
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
let a_av = engine.dispatch_node(a, ctx, arena)?;
let b_av = engine.dispatch_node(b, ctx, arena)?;
if let (Some(ia), Some(ib)) = (a_av.as_i64(), b_av.as_i64()) {
return Ok(alloc_number(
arena,
try_int_op(ia, ib, i64::checked_add, |x, y| x + y),
));
}
if let Some((i1, i2)) = coerce_pair_int(a_av, b_av, engine) {
return Ok(alloc_number(
arena,
try_int_op(i1, i2, i64::checked_add, |x, y| x + y),
));
}
if let Some((f1, f2)) = coerce_pair_f64(a_av, b_av, engine) {
return Ok(alloc_number(arena, NumberValue::from_f64(f1 + f2)));
}
#[cfg(feature = "datetime")]
{
if let Some(av) = crate::operators::datetime::arith::datetime_add(a_av, b_av, arena) {
return Ok(av);
}
}
let mut sum = 0.0f64;
for av in [a_av, b_av] {
if let Some(f) = coerce_to_number_cfg(av, engine) {
sum += f;
} else {
match handle_nan(engine)? {
NanAction::Skip => {}
NanAction::ReturnNull => return Ok(crate::arena::singletons::singleton_null()),
}
}
}
Ok(alloc_number(arena, NumberValue::from_f64(sum)))
}
#[inline]
pub(crate) fn evaluate_multiply<'a>(
args: &'a [CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
if args.is_empty() {
return Ok(alloc_number(arena, NumberValue::from_i64(1)));
}
if args.len() == 1 {
return one_arg_arith(&args[0], ctx, engine, arena, ArithOp::Multiply);
}
if args.len() == 2 {
return multiply_two_arg(&args[0], &args[1], ctx, engine, arena);
}
variadic_fold(
args,
ctx,
engine,
arena,
VariadicFoldSpec {
int_init: 1,
float_init: 1.0,
i_combine: i64::checked_mul,
f_combine: |a, b| a * b,
},
)
}
#[inline]
fn multiply_two_arg<'a>(
a: &'a CompiledNode,
b: &'a CompiledNode,
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
let a_av = engine.dispatch_node(a, ctx, arena)?;
let b_av = engine.dispatch_node(b, ctx, arena)?;
if let (Some(ia), Some(ib)) = (a_av.as_i64(), b_av.as_i64()) {
return Ok(alloc_number(
arena,
try_int_op(ia, ib, i64::checked_mul, |x, y| x * y),
));
}
#[cfg(feature = "datetime")]
{
if let Some(av) = crate::operators::datetime::arith::datetime_multiply(a_av, b_av, arena) {
return Ok(av);
}
}
if let Some((i1, i2)) = coerce_pair_int(a_av, b_av, engine) {
return Ok(alloc_number(
arena,
try_int_op(i1, i2, i64::checked_mul, |x, y| x * y),
));
}
if let Some((f1, f2)) = coerce_pair_f64(a_av, b_av, engine) {
return Ok(alloc_number(arena, NumberValue::from_f64(f1 * f2)));
}
let mut product = 1.0f64;
for av in [a_av, b_av] {
if let Some(f) = coerce_to_number_cfg(av, engine) {
product *= f;
} else {
match handle_nan(engine)? {
NanAction::Skip => {}
NanAction::ReturnNull => return Ok(crate::arena::singletons::singleton_null()),
}
}
}
Ok(alloc_number(arena, NumberValue::from_f64(product)))
}
#[inline]
pub(crate) fn evaluate_subtract<'a>(
args: &'a [CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
if args.is_empty() {
return Err(crate::Error::invalid_args());
}
if args.len() == 1 {
return subtract_one_arg(&args[0], ctx, engine, arena);
}
if args.len() == 2 {
return subtract_two_arg(&args[0], &args[1], ctx, engine, arena);
}
subtract_variadic(args, ctx, engine, arena)
}
#[inline]
fn subtract_one_arg<'a>(
arg: &'a CompiledNode,
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
let av = engine.dispatch_node(arg, ctx, arena)?;
if let DataValue::Array(items) = av {
if items.is_empty() {
return Err(crate::Error::invalid_args());
}
let mut result = coerce_to_number_cfg(&items[0], engine).ok_or_else(crate::Error::nan)?;
for elem in &items[1..] {
let n = coerce_to_number_cfg(elem, engine).ok_or_else(crate::Error::nan)?;
result -= n;
}
return Ok(alloc_number(arena, NumberValue::from_f64(result)));
}
if let Some(i) = av.as_i64() {
return Ok(alloc_number(
arena,
i.checked_neg()
.map(NumberValue::from_i64)
.unwrap_or_else(|| NumberValue::from_f64(-(i as f64))),
));
}
if let Some(f) = coerce_to_number_cfg(av, engine) {
return Ok(alloc_number(arena, NumberValue::from_f64(-f)));
}
Err(crate::Error::nan())
}
#[inline]
fn subtract_two_arg<'a>(
a: &'a CompiledNode,
b: &'a CompiledNode,
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
let a_av = engine.dispatch_node(a, ctx, arena)?;
let b_av = engine.dispatch_node(b, ctx, arena)?;
if let (Some(ia), Some(ib)) = (a_av.as_i64(), b_av.as_i64()) {
return Ok(alloc_number(
arena,
try_int_op(ia, ib, i64::checked_sub, |x, y| x - y),
));
}
if let Some((i1, i2)) = coerce_pair_int(a_av, b_av, engine) {
return Ok(alloc_number(
arena,
try_int_op(i1, i2, i64::checked_sub, |x, y| x - y),
));
}
if let Some((f1, f2)) = coerce_pair_f64(a_av, b_av, engine) {
return Ok(alloc_number(arena, NumberValue::from_f64(f1 - f2)));
}
#[cfg(feature = "datetime")]
{
if let Some(av) = crate::operators::datetime::arith::datetime_subtract(a_av, b_av, arena) {
return Ok(av);
}
}
Err(crate::Error::nan())
}
#[inline]
fn subtract_variadic<'a>(
args: &'a [CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
let first_av = engine.dispatch_node(&args[0], ctx, arena)?;
let int_init = first_av
.as_i64()
.or_else(|| try_coerce_to_integer_cfg(first_av, engine));
let float_init = match coerce_to_number_cfg(first_av, engine) {
Some(f) => f,
None => return Err(crate::Error::nan()),
};
let mut state = FoldState::new(int_init.unwrap_or_default(), float_init);
state.all_int = int_init.is_some();
for arg in args.iter().skip(1) {
let av = engine.dispatch_node(arg, ctx, arena)?;
let int_opt = av
.as_i64()
.or_else(|| try_coerce_to_integer_cfg(av, engine));
let float_opt = if int_opt.is_some() {
None
} else {
coerce_to_number_cfg(av, engine)
};
if let FoldStepOutcome::ReturnNull = state.step(
int_opt,
float_opt,
i64::checked_sub,
std::ops::Sub::sub,
engine,
)? {
return Ok(crate::arena::singletons::singleton_null());
}
}
Ok(state.finalize(arena))
}
fn one_arg_arith<'a>(
arg: &'a CompiledNode,
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
op: ArithOp,
) -> Result<&'a DataValue<'a>> {
let is_literal_array = matches!(arg, CompiledNode::Array { .. })
|| matches!(
arg,
CompiledNode::Value {
value: datavalue::OwnedDataValue::Array(_),
..
}
);
if is_literal_array {
return match handle_nan(engine)? {
NanAction::Skip => Ok(alloc_number(
arena,
NumberValue::from_i64(op.identity_int()),
)),
NanAction::ReturnNull => Ok(crate::arena::singletons::singleton_null()),
};
}
let av = engine.dispatch_node(arg, ctx, arena)?;
if let DataValue::Array(items) = av {
return one_arg_array_fold(items, engine, arena, op);
}
if let Some(i) = try_coerce_to_integer_cfg(av, engine) {
return match op.combine_int(op.identity_int(), i) {
Some(r) => Ok(alloc_number(arena, NumberValue::from_i64(r))),
None => Ok(alloc_number(
arena,
NumberValue::from_f64(op.combine_f(op.identity_int() as f64, i as f64)),
)),
};
}
if let Some(f) = coerce_to_number_cfg(av, engine) {
return Ok(alloc_number(
arena,
NumberValue::from_f64(op.combine_f(op.identity_int() as f64, f)),
));
}
match handle_nan(engine)? {
NanAction::Skip => Ok(alloc_number(
arena,
NumberValue::from_i64(op.identity_int()),
)),
NanAction::ReturnNull => Ok(crate::arena::singletons::singleton_null()),
}
}
#[inline]
fn one_arg_array_fold<'a>(
items: &[DataValue<'a>],
engine: &Engine,
arena: &'a Bump,
op: ArithOp,
) -> Result<&'a DataValue<'a>> {
if items.is_empty() {
return Ok(alloc_number(
arena,
NumberValue::from_i64(op.identity_int()),
));
}
let init = op.identity_int();
let mut state = FoldState::new(init, init as f64);
for item in items.iter() {
let int_opt = try_coerce_to_integer_cfg(item, engine);
let float_opt = if int_opt.is_some() {
None
} else {
coerce_to_number_cfg(item, engine)
};
if let FoldStepOutcome::ReturnNull = state.step(
int_opt,
float_opt,
|a, b| op.combine_int(a, b),
|a, b| op.combine_f(a, b),
engine,
)? {
return Ok(crate::arena::singletons::singleton_null());
}
}
Ok(state.finalize(arena))
}