use crate::arena::{ContextStack, DataValue, coerce_to_number_cfg, try_coerce_to_integer_cfg};
use crate::operators::meta::ArithOp;
use crate::operators::{NanForm, nan_error};
use crate::{CompiledNode, Engine, Result};
use bumpalo::Bump;
use datavalue::NumberValue;
#[cfg(feature = "datetime")]
use super::helpers::fold_values;
use super::helpers::{
FoldState, FoldStepOutcome, NanAction, VariadicFoldSpec, alloc_number, coerce_pair_f64,
coerce_pair_int, handle_nan, is_literal_array, 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);
}
#[cfg(feature = "datetime")]
if let Some(sum) = add_temporal_variadic(args, ctx, engine, arena)? {
return Ok(sum);
}
variadic_fold(args, ctx, engine, arena, ADD_FOLD)
}
const ADD_FOLD: VariadicFoldSpec = VariadicFoldSpec {
int_init: 0,
float_init: 0.0,
i_combine: i64::checked_add,
f_combine: |a, b| a + b,
};
#[cfg(feature = "datetime")]
fn add_temporal_variadic<'a>(
args: &'a [CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<Option<&'a DataValue<'a>>> {
use crate::operators::datetime::arith::Temporal;
if let CompiledNode::Value { value, .. } = &args[0]
&& value.as_number().is_some()
{
return Ok(None);
}
let first = engine.dispatch_node(&args[0], ctx, arena)?;
let numeric = first.as_i64().is_some() || coerce_to_number_cfg(first, engine).is_some();
let mut acc = match (numeric, Temporal::of(first)) {
(false, Some(t)) => t,
_ => return numeric_fold_from(first, &args[1..], ctx, engine, arena, ADD_FOLD).map(Some),
};
for (i, arg) in args.iter().enumerate().skip(1) {
let av = engine.dispatch_node(arg, ctx, arena)?;
match Temporal::of(av).and_then(|t| acc.add(t)) {
Some(sum) => acc = sum,
None => {
let mut state = FoldState::new(ADD_FOLD.int_init, ADD_FOLD.float_init);
for _ in 0..i {
if let FoldStepOutcome::ReturnNull = state.step(
None,
None,
ADD_FOLD.i_combine,
ADD_FOLD.f_combine,
ctx,
engine,
)? {
return Ok(Some(crate::arena::singletons::singleton_null()));
}
}
return fold_values(state, av, &args[i + 1..], ctx, engine, arena, ADD_FOLD)
.map(Some);
}
}
}
Ok(Some(acc.into_value(arena)))
}
#[cfg(feature = "datetime")]
fn numeric_fold_from<'a>(
first: &'a DataValue<'a>,
rest: &'a [CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
spec: VariadicFoldSpec,
) -> Result<&'a DataValue<'a>> {
let state = FoldState::new(spec.int_init, spec.float_init);
fold_values(state, first, rest, ctx, engine, arena, spec)
}
#[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(ctx, engine)? {
NanAction::Skip | NanAction::Zero => {}
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::Mul);
}
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, engine, 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(ctx, engine)? {
NanAction::Skip => {}
NanAction::Zero => product = 0.0,
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.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 {
ctx.charge(items.len() as u64)?;
if items.is_empty() {
return Err(crate::Error::invalid_args());
}
let first_av = &items[0];
let int_init = first_av
.as_i64()
.or_else(|| try_coerce_to_integer_cfg(first_av, engine));
let float_init = coerce_to_number_cfg(first_av, engine)
.ok_or_else(|| nan_error(NanForm::Thrown, ctx))?;
let mut state = FoldState::new(int_init.unwrap_or_default(), float_init);
state.all_int = int_init.is_some();
for elem in &items[1..] {
let int_opt = elem
.as_i64()
.or_else(|| try_coerce_to_integer_cfg(elem, engine));
let float_opt = if int_opt.is_some() {
None
} else {
coerce_to_number_cfg(elem, engine)
};
if let FoldStepOutcome::ReturnNull = state.step(
int_opt,
float_opt,
i64::checked_sub,
std::ops::Sub::sub,
ctx,
engine,
)? {
return Ok(crate::arena::singletons::singleton_null());
}
}
return Ok(state.finalize(arena));
}
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(nan_error(NanForm::Thrown, ctx))
}
#[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(nan_error(NanForm::Thrown, ctx))
}
#[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,
#[cfg(feature = "datetime")]
None if let Some(first) = crate::operators::datetime::arith::Temporal::of(first_av) => {
return subtract_temporal_variadic(first, &args[1..], ctx, engine, arena);
}
None => return Err(nan_error(NanForm::Thrown, ctx)),
};
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,
ctx,
engine,
)? {
return Ok(crate::arena::singletons::singleton_null());
}
}
Ok(state.finalize(arena))
}
#[cfg(feature = "datetime")]
fn subtract_temporal_variadic<'a>(
first: crate::operators::datetime::arith::Temporal,
rest: &'a [CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
use crate::operators::datetime::arith::Temporal;
let mut acc = first;
for arg in rest {
let av = engine.dispatch_node(arg, ctx, arena)?;
acc = Temporal::of(av)
.and_then(|t| acc.sub(t))
.ok_or_else(|| nan_error(NanForm::Thrown, ctx))?;
}
Ok(acc.into_value(arena))
}
fn one_arg_arith<'a>(
arg: &'a CompiledNode,
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
op: ArithOp,
) -> Result<&'a DataValue<'a>> {
if is_literal_array(arg) {
return nan_operand(op, ctx, engine, arena);
}
let av = engine.dispatch_node(arg, ctx, arena)?;
if let DataValue::Array(items) = av {
return one_arg_array_fold(items, ctx, engine, arena, op);
}
if let Some(i) = try_coerce_to_integer_cfg(av, engine) {
return match op.checked_i64(op.right_identity(), i) {
Some(r) => Ok(alloc_number(arena, NumberValue::from_i64(r))),
None => Ok(alloc_number(
arena,
NumberValue::from_f64(op.apply_f64(op.right_identity() as f64, i as f64)),
)),
};
}
if let Some(f) = coerce_to_number_cfg(av, engine) {
return Ok(alloc_number(
arena,
NumberValue::from_f64(op.apply_f64(op.right_identity() as f64, f)),
));
}
nan_operand(op, ctx, engine, arena)
}
fn nan_operand<'a>(
op: ArithOp,
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
let value = match handle_nan(ctx, engine)? {
NanAction::Skip => op.right_identity(),
NanAction::Zero => op.checked_i64(op.right_identity(), 0).unwrap_or(0),
NanAction::ReturnNull => return Ok(crate::arena::singletons::singleton_null()),
};
Ok(alloc_number(arena, NumberValue::from_i64(value)))
}
#[inline]
fn one_arg_array_fold<'a>(
items: &[DataValue<'a>],
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
op: ArithOp,
) -> Result<&'a DataValue<'a>> {
ctx.charge(items.len() as u64)?;
if items.is_empty() {
return Ok(alloc_number(
arena,
NumberValue::from_i64(op.right_identity()),
));
}
let init = op.right_identity();
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.checked_i64(a, b),
|a, b| op.apply_f64(a, b),
ctx,
engine,
)? {
return Ok(crate::arena::singletons::singleton_null());
}
}
Ok(state.finalize(arena))
}