use crate::Engine;
use crate::Result;
use crate::arena::{ContextStack, DataValue, coerce_to_number_cfg, try_coerce_to_integer_cfg};
use crate::config::NanHandling;
use crate::operators::{NanForm, nan_error};
use bumpalo::Bump;
use datavalue::NumberValue;
pub(super) enum NanAction {
Skip,
Zero,
ReturnNull,
}
#[inline]
pub(super) fn handle_nan(ctx: &mut ContextStack<'_>, engine: &Engine) -> Result<NanAction> {
match engine.config().arithmetic_nan_handling {
NanHandling::ThrowError => Err(nan_error(NanForm::Thrown, ctx)),
NanHandling::IgnoreValue => Ok(NanAction::Skip),
NanHandling::CoerceToZero => Ok(NanAction::Zero),
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(always)]
pub(crate) fn try_int_op(
a: i64,
b: i64,
int_op: impl Fn(i64, i64) -> Option<i64>,
float_op: impl 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) 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,
ctx: &mut ContextStack<'_>,
engine: &Engine,
) -> Result<FoldStepOutcome>
where
I: Fn(i64, i64) -> Option<i64>,
F: Fn(f64, f64) -> f64,
{
if self.all_int
&& 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(ctx, engine)? {
NanAction::Skip => Ok(FoldStepOutcome::Continue),
NanAction::Zero => self.step(Some(0), None, i_combine, f_combine, ctx, engine),
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 is_literal_array(node: &crate::CompiledNode) -> bool {
matches!(node, crate::CompiledNode::Array { .. })
|| matches!(
node,
crate::CompiledNode::Value {
value: datavalue::OwnedDataValue::Array(_),
..
}
)
}
#[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)?;
if let FoldStepOutcome::ReturnNull = step_value(&mut state, av, ctx, engine, spec)? {
return Ok(crate::arena::singletons::singleton_null());
}
}
Ok(state.finalize(arena))
}
#[inline(always)]
fn step_value(
state: &mut FoldState,
av: &DataValue<'_>,
ctx: &mut ContextStack<'_>,
engine: &Engine,
spec: VariadicFoldSpec,
) -> Result<FoldStepOutcome> {
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)
};
state.step(
int_opt,
float_opt,
spec.i_combine,
spec.f_combine,
ctx,
engine,
)
}
#[cfg(feature = "datetime")]
pub(super) fn fold_values<'a>(
mut state: FoldState,
first: &'a DataValue<'a>,
rest: &'a [crate::CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
spec: VariadicFoldSpec,
) -> Result<&'a DataValue<'a>> {
if let FoldStepOutcome::ReturnNull = step_value(&mut state, first, ctx, engine, spec)? {
return Ok(crate::arena::singletons::singleton_null());
}
for arg in rest {
let av = engine.dispatch_node(arg, ctx, arena)?;
if let FoldStepOutcome::ReturnNull = step_value(&mut state, av, ctx, engine, spec)? {
return Ok(crate::arena::singletons::singleton_null());
}
}
Ok(state.finalize(arena))
}