use crate::Engine;
use crate::Result;
use crate::arena::{DataValue, coerce_to_number_cfg, try_coerce_to_integer_cfg};
use crate::config::NanHandling;
use bumpalo::Bump;
use datavalue::NumberValue;
pub(super) enum NanAction {
Skip,
ReturnNull,
}
#[inline]
pub(super) fn handle_nan(engine: &Engine) -> Result<NanAction> {
match engine.config().arithmetic_nan_handling {
NanHandling::ThrowError => Err(crate::Error::nan()),
NanHandling::IgnoreValue | NanHandling::CoerceToZero => Ok(NanAction::Skip),
NanHandling::ReturnNull => Ok(NanAction::ReturnNull),
}
}
#[inline]
pub(super) fn alloc_number<'a>(arena: &'a Bump, n: NumberValue) -> &'a DataValue<'a> {
arena.alloc(DataValue::Number(n))
}
#[inline]
pub(super) fn try_int_op(
a: i64,
b: i64,
int_op: fn(i64, i64) -> Option<i64>,
float_op: fn(f64, f64) -> f64,
) -> NumberValue {
match int_op(a, b) {
Some(r) => NumberValue::from_i64(r),
None => NumberValue::from_f64(float_op(a as f64, b as f64)),
}
}
#[inline]
pub(super) fn coerce_pair_int(
a: &DataValue<'_>,
b: &DataValue<'_>,
engine: &Engine,
) -> Option<(i64, i64)> {
Some((
try_coerce_to_integer_cfg(a, engine)?,
try_coerce_to_integer_cfg(b, engine)?,
))
}
#[inline]
pub(super) fn coerce_pair_f64(
a: &DataValue<'_>,
b: &DataValue<'_>,
engine: &Engine,
) -> Option<(f64, f64)> {
Some((
coerce_to_number_cfg(a, engine)?,
coerce_to_number_cfg(b, engine)?,
))
}
#[derive(Clone, Copy)]
pub(super) enum ArithOp {
Add,
Multiply,
}
impl ArithOp {
#[inline]
pub(super) fn identity_int(self) -> i64 {
match self {
ArithOp::Add => 0,
ArithOp::Multiply => 1,
}
}
#[inline]
pub(super) fn combine_int(self, a: i64, b: i64) -> Option<i64> {
match self {
ArithOp::Add => a.checked_add(b),
ArithOp::Multiply => a.checked_mul(b),
}
}
#[inline]
pub(super) fn combine_f(self, a: f64, b: f64) -> f64 {
match self {
ArithOp::Add => a + b,
ArithOp::Multiply => a * b,
}
}
}
pub(super) struct VariadicFoldSpec {
pub(super) int_init: i64,
pub(super) float_init: f64,
pub(super) i_combine: fn(i64, i64) -> Option<i64>,
pub(super) f_combine: fn(f64, f64) -> f64,
}
pub(super) struct FoldState {
pub(super) int_acc: i64,
pub(super) float_acc: f64,
pub(super) all_int: bool,
}
pub(super) enum FoldStepOutcome {
Continue,
ReturnNull,
}
impl FoldState {
#[inline]
pub(super) fn new(int_init: i64, float_init: f64) -> Self {
Self {
int_acc: int_init,
float_acc: float_init,
all_int: true,
}
}
#[inline]
pub(super) fn step<I, F>(
&mut self,
int_opt: Option<i64>,
float_opt: Option<f64>,
i_combine: I,
f_combine: F,
engine: &Engine,
) -> Result<FoldStepOutcome>
where
I: Fn(i64, i64) -> Option<i64>,
F: Fn(f64, f64) -> f64,
{
if self.all_int {
if let Some(i) = int_opt {
match i_combine(self.int_acc, i) {
Some(r) => self.int_acc = r,
None => {
self.all_int = false;
self.float_acc = f_combine(self.int_acc as f64, i as f64);
}
}
return Ok(FoldStepOutcome::Continue);
}
}
let float_arg = float_opt.or_else(|| int_opt.map(|i| i as f64));
if let Some(f) = float_arg {
if self.all_int {
self.all_int = false;
self.float_acc = f_combine(self.int_acc as f64, f);
} else {
self.float_acc = f_combine(self.float_acc, f);
}
return Ok(FoldStepOutcome::Continue);
}
match handle_nan(engine)? {
NanAction::Skip => Ok(FoldStepOutcome::Continue),
NanAction::ReturnNull => Ok(FoldStepOutcome::ReturnNull),
}
}
#[inline]
pub(super) fn finalize<'a>(self, arena: &'a Bump) -> &'a DataValue<'a> {
if self.all_int {
alloc_number(arena, NumberValue::from_i64(self.int_acc))
} else {
alloc_number(arena, NumberValue::from_f64(self.float_acc))
}
}
}
#[inline]
pub(super) fn variadic_fold<'a>(
args: &'a [crate::CompiledNode],
ctx: &mut crate::arena::ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
spec: VariadicFoldSpec,
) -> Result<&'a DataValue<'a>> {
let mut state = FoldState::new(spec.int_init, spec.float_init);
for arg in args {
let av = engine.dispatch_node(arg, ctx, arena)?;
let int_opt = av.as_i64();
let float_opt = if int_opt.is_some() {
None
} else {
av.as_f64().or_else(|| coerce_to_number_cfg(av, engine))
};
if let FoldStepOutcome::ReturnNull =
state.step(int_opt, float_opt, spec.i_combine, spec.f_combine, engine)?
{
return Ok(crate::arena::singletons::singleton_null());
}
}
Ok(state.finalize(arena))
}