use super::array_value::ArrayStackValue::Double;
use super::array_value::{ArrayCell, ArrayStackValue, zip_map};
use super::cast::{c_int, c_long, d2ui, imod, my_nint, nint};
use super::error::CalcError;
use super::opcodes::{ArrayOp, ControlOp, CoreOp, Opcode};
use super::random::local_random;
use super::{ArrayInputs, CompiledExpr};
use crate::calc::math::{derivative, fitting, stats};
const MY_MAXFLOAT: f64 = 1e35f32 as f64;
static ACALC_LOOP_MAX: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(1000);
pub fn acalc_loop_max() -> i32 {
ACALC_LOOP_MAX.load(std::sync::atomic::Ordering::Relaxed)
}
pub fn set_acalc_loop_max(n: i32) {
ACALC_LOOP_MAX.store(n, std::sync::atomic::Ordering::Relaxed);
}
#[derive(Debug, Clone)]
struct Cell {
v: ArrayStackValue,
src: Option<usize>,
}
impl From<ArrayStackValue> for Cell {
fn from(v: ArrayStackValue) -> Self {
Cell { v, src: None }
}
}
pub fn eval(expr: &CompiledExpr, inputs: &mut ArrayInputs) -> Result<ArrayStackValue, CalcError> {
if expr.is_empty() {
return Err(CalcError::EmptyProgram);
}
expr.check_until_ceiling()?;
inputs.amask = 0;
let mut stack: Vec<Cell> = Vec::with_capacity(20);
let code = &expr.code;
let mut pc = 0;
let mut status = Status::default();
let mut until_marks: Vec<(usize, usize)> = Vec::new();
let mut loops_done: i32 = 0;
while pc < code.len() {
let op = &code[pc];
pc += 1;
match op {
Opcode::Core(core) => match core {
CoreOp::End => break,
CoreOp::PushConst(v) => push(&mut stack, Double(*v)),
CoreOp::PushVar(idx) => {
let i = *idx as usize;
match inputs.num_arg(i) {
Some(v) => push_src(&mut stack, Double(v), Some(i)),
None => push(&mut stack, Double(0.0)),
}
}
CoreOp::PushDoubleVar(idx) => {
let arr = inputs.array_arg(*idx as usize).cloned().unwrap_or_default();
push(
&mut stack,
ArrayStackValue::Array(ArrayCell::new(arr, inputs.array_size)),
);
}
CoreOp::Pi => push(&mut stack, Double(std::f64::consts::PI)),
CoreOp::D2R => push(&mut stack, Double(std::f64::consts::PI / 180.0)),
CoreOp::R2D => push(&mut stack, Double(180.0 / std::f64::consts::PI)),
CoreOp::S2R => push(&mut stack, Double(std::f64::consts::PI / (180.0 * 3600.0))),
CoreOp::R2S => push(&mut stack, Double((180.0 * 3600.0) / std::f64::consts::PI)),
CoreOp::Random => push(&mut stack, Double(local_random())),
CoreOp::NormalRandom => {
let u1 = local_random();
let u2 = local_random();
let n = (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos();
push(&mut stack, Double(n));
}
CoreOp::FetchVal => {
push(&mut stack, Double(inputs.prev_val));
}
CoreOp::FetchSval => {
return Err(CalcError::Internal);
}
CoreOp::Add => binary(&mut stack, |a, b| zip_map(a, b, |x, y| x + y))?,
CoreOp::Sub => binary(&mut stack, |a, b| zip_map(a, b, |x, y| x - y))?,
CoreOp::Mul => binary(&mut stack, |a, b| zip_map(a, b, |x, y| x * y))?,
CoreOp::Div => {
binary(&mut stack, |a, b| {
zip_map(a, b, |x, y| if y == 0.0 { MY_MAXFLOAT } else { x / y })
})?
}
CoreOp::Mod => {
binary(&mut stack, |a, b| {
zip_map(a, b, |x, y| {
imod(x, y, |v| i64::from(c_int(v))).unwrap_or(MY_MAXFLOAT)
})
})?
}
CoreOp::Neg => unary_op(&mut stack, |a| a.map(|x| -x))?,
CoreOp::Power => {
let exp = pop(&mut stack)?.v.as_f64()?;
unary_op(&mut stack, |a| a.map(|x| x.powf(exp)))?
}
CoreOp::Eq => binary(&mut stack, |a, b| {
zip_map(a, b, |x, y| f64::from(u8::from(x == y)))
})?,
CoreOp::Ne => binary(&mut stack, |a, b| {
zip_map(a, b, |x, y| f64::from(u8::from(x != y)))
})?,
CoreOp::Lt => binary(&mut stack, |a, b| {
zip_map(a, b, |x, y| f64::from(u8::from(x < y)))
})?,
CoreOp::Le => binary(&mut stack, |a, b| {
zip_map(a, b, |x, y| f64::from(u8::from(x <= y)))
})?,
CoreOp::Gt => binary(&mut stack, |a, b| {
zip_map(a, b, |x, y| f64::from(u8::from(x > y)))
})?,
CoreOp::Ge => binary(&mut stack, |a, b| {
zip_map(a, b, |x, y| f64::from(u8::from(x >= y)))
})?,
CoreOp::And => binary(&mut stack, |a, b| {
zip_map(a, b, |x, y| if x != 0.0 && y != 0.0 { 1.0 } else { 0.0 })
})?,
CoreOp::Or => binary(&mut stack, |a, b| {
zip_map(a, b, |x, y| if x != 0.0 || y != 0.0 { 1.0 } else { 0.0 })
})?,
CoreOp::Not => {
unary_op(&mut stack, |a| a.map(|x| if x == 0.0 { 1.0 } else { 0.0 }))?
}
CoreOp::BitAnd => binary(&mut stack, |a, b| {
zip_map(a, b, |x, y| (c_int(x) & c_int(y)) as f64)
})?,
CoreOp::BitOr => binary(&mut stack, |a, b| {
zip_map(a, b, |x, y| (c_int(x) | c_int(y)) as f64)
})?,
CoreOp::BitXor => binary(&mut stack, |a, b| {
zip_map(a, b, |x, y| (c_int(x) ^ c_int(y)) as f64)
})?,
CoreOp::BitNot => unary_op(&mut stack, |a| a.map(|x| !c_int(x) as f64))?,
CoreOp::Shl | CoreOp::Shr => {
let left_shift = matches!(core, CoreOp::Shl);
let b = pop(&mut stack)?;
let count = b.v.as_f64()?;
unary_op(&mut stack, |a| match a {
Double(x) => {
let n = c_int(count) & 31;
let v = if left_shift {
c_int(x) << n
} else {
c_int(x) >> n
};
Double(v as f64)
}
ArrayStackValue::Array(mut cell) => {
shift_elements(cell.buf_mut(), if left_shift { -count } else { count });
ArrayStackValue::Array(cell)
}
})?
}
CoreOp::ShrLogical => binary(&mut stack, |a, b| {
zip_map(a, b, |x, y| (d2ui(x) >> (d2ui(y) & 31)) as f64)
})?,
CoreOp::CondIf => {
let cond = pop_f64(&mut stack)?;
if cond == 0.0 {
pc = cond_search(code, pc, true)?;
}
}
CoreOp::CondElse => {
pc = cond_search(code, pc, false)?;
}
CoreOp::CondEnd => {}
CoreOp::Abs => unary_op(&mut stack, |a| a.map(super::abs_val))?,
CoreOp::Sqrt => {
unary_op(&mut stack, |a| domain_guarded(a, f64::sqrt, &mut status))?
}
CoreOp::Exp => unary_op(&mut stack, |a| a.map(f64::exp))?,
CoreOp::Log10 => {
unary_op(&mut stack, |a| domain_guarded(a, f64::log10, &mut status))?
}
CoreOp::LogE => unary_op(&mut stack, |a| domain_guarded(a, f64::ln, &mut status))?,
CoreOp::Sin => unary_op(&mut stack, |a| a.map(f64::sin))?,
CoreOp::Cos => unary_op(&mut stack, |a| a.map(f64::cos))?,
CoreOp::Tan => unary_op(&mut stack, |a| a.map(f64::tan))?,
CoreOp::Asin => unary_op(&mut stack, |a| a.map(f64::asin))?,
CoreOp::Acos => unary_op(&mut stack, |a| a.map(f64::acos))?,
CoreOp::Atan => unary_op(&mut stack, |a| a.map(f64::atan))?,
CoreOp::Sinh => unary_op(&mut stack, |a| a.map(f64::sinh))?,
CoreOp::Cosh => unary_op(&mut stack, |a| a.map(f64::cosh))?,
CoreOp::Tanh => unary_op(&mut stack, |a| a.map(f64::tanh))?,
CoreOp::Ceil => unary_op(&mut stack, |a| a.map(f64::ceil))?,
CoreOp::Floor => unary_op(&mut stack, |a| a.map(f64::floor))?,
CoreOp::Nint => {
unary_op(&mut stack, |a| a.map(|x| nint(x, c_long)))?
}
CoreOp::IsNan(nargs) => {
let args = popn(&mut stack, *nargs as usize)?;
let any_nan = args
.iter()
.flat_map(ArrayStackValue::elements)
.any(f64::is_nan);
push(&mut stack, Double(f64::from(u8::from(any_nan))));
}
CoreOp::Finite(nargs) => {
let args = popn(&mut stack, *nargs as usize)?;
let all_finite = args
.iter()
.flat_map(ArrayStackValue::elements)
.all(f64::is_finite);
push(&mut stack, Double(f64::from(u8::from(all_finite))));
}
CoreOp::IsInf => unary_op(&mut stack, |a| a.map(super::isinf))?,
CoreOp::Atan2 => binary(&mut stack, |a, b| zip_map(a, b, |x, y| y.atan2(x)))?,
CoreOp::Fmod => binary(&mut stack, |a, b| zip_map(a, b, |x, y| x % y))?,
CoreOp::Max(nargs) => vararg_extremum(
&mut stack,
*nargs as usize,
inputs.array_size,
Extremum::Max,
)?,
CoreOp::Min(nargs) => vararg_extremum(
&mut stack,
*nargs as usize,
inputs.array_size,
Extremum::Min,
)?,
CoreOp::MaxVal => binary(&mut stack, |a, b| zip_map(a, b, max_val))?,
CoreOp::MinVal => binary(&mut stack, |a, b| zip_map(a, b, min_val))?,
CoreOp::StoreVar(idx) => {
let v = pop_f64(&mut stack)?;
if let Some(slot) = inputs.num_arg_mut(*idx as usize) {
*slot = v;
}
}
CoreOp::StoreDoubleVar(idx) => {
let i = *idx as usize;
let v = pop(&mut stack)?.v;
let buf = match v {
ArrayStackValue::Array(cell) => cell.into_buf(),
Double(d) => vec![d; inputs.array_size],
};
inputs.store_array(i, buf);
}
},
Opcode::Array(aop) => match aop {
ArrayOp::ConstIndex => {
let arr: Vec<f64> = (0..inputs.array_size).map(|i| i as f64).collect();
push(&mut stack, ArrayStackValue::array(arr));
}
ArrayOp::ToArray => {
let array_size = inputs.array_size;
unary_op(&mut stack, |v| {
ArrayStackValue::Array(v.into_cell(array_size))
})?
}
ArrayOp::ToDouble => {
let v = pop(&mut stack)?.v;
push(&mut stack, Double(v.as_f64()?));
}
ArrayOp::Average => {
unary_op(&mut stack, |v| {
unary(
v,
|c| Double(window_sum(&c) / c.span() as f64),
PASS_THROUGH,
)
})?
}
ArrayOp::StdDev => {
unary_op(&mut stack, |v| {
unary(
v,
|c| {
let n = c.span();
let mean = window_sum(&c) / n as f64;
let e: f64 = c.window().iter().map(|x| (x - mean).powi(2)).sum();
Double(if n > 1 {
(e / (n - 1) as f64).sqrt()
} else {
e.sqrt()
})
},
ZERO,
)
})?
}
ArrayOp::Fwhm => unary_op(&mut stack, |v| {
unary(v, |c| Double(stats::fwhm(c.buf(), c.last_el())), ZERO)
})?,
ArrayOp::ArraySum => {
unary_op(&mut stack, |v| {
unary(v, |c| Double(c.window().iter().sum()), PASS_THROUGH)
})?
}
ArrayOp::ArrayMax => unary_op(&mut stack, |v| {
unary(
v,
|c| Double(extremum(&c, |x, best| x > best).0),
PASS_THROUGH,
)
})?,
ArrayOp::ArrayMin => unary_op(&mut stack, |v| {
unary(
v,
|c| Double(extremum(&c, |x, best| x < best).0),
PASS_THROUGH,
)
})?,
ArrayOp::IndexMax => unary_op(&mut stack, |v| {
unary(
v,
|c| Double(extremum(&c, |x, best| x > best).1 as f64),
ZERO,
)
})?,
ArrayOp::IndexMin => unary_op(&mut stack, |v| {
unary(
v,
|c| Double(extremum(&c, |x, best| x < best).1 as f64),
ZERO,
)
})?,
ArrayOp::IndexZero => {
unary_op(&mut stack, |v| {
unary(
v,
|c| Double(index_zero_crossing(c.window())),
|d| if d.abs() < SMALL { 0.0 } else { -1.0 },
)
})?
}
ArrayOp::IndexNonZero => {
unary_op(&mut stack, |v| {
unary(
v,
|c| {
Double(
c.window()
.iter()
.position(|&x| x.abs() > SMALL)
.map_or(-1.0, |i| i as f64),
)
},
|d| if d.abs() > SMALL { 0.0 } else { -1.0 },
)
})?
}
ArrayOp::Smooth => {
unary_op(&mut stack, |v| {
unary(
v,
|mut c| {
let smoothed = stats::smooth(c.window());
c.window_mut().copy_from_slice(&smoothed);
ArrayStackValue::Array(c)
},
PASS_THROUGH,
)
})?
}
ArrayOp::NSmooth => {
let n = c_int(pop_f64(&mut stack)?).max(0) as usize;
try_unary_op(&mut stack, |v| {
let mut cell = v.as_cell()?.clone();
let smoothed = stats::nsmooth(cell.buf(), n);
cell.buf_mut().copy_from_slice(&smoothed);
Ok(ArrayStackValue::Array(cell))
})?
}
ArrayOp::Deriv => {
unary_op(&mut stack, |v| {
unary(
v,
|mut c| {
derivative_into_window(&mut c, 2, &mut status);
ArrayStackValue::Array(c)
},
ZERO,
)
})?
}
ArrayOp::NDeriv => {
let npts = i64::from(c_int(pop_f64(&mut stack)?));
let array_size = inputs.array_size;
unary_op(&mut stack, |v| {
let mut cell = v.into_cell(array_size);
derivative_into_window(&mut cell, npts, &mut status);
ArrayStackValue::Array(cell)
})?
}
ArrayOp::Cum => {
unary_op(&mut stack, |v| {
unary(
v,
|mut c| {
let buf = c.buf_mut();
for i in 1..buf.len() {
buf[i] += buf[i - 1];
}
ArrayStackValue::Array(c)
},
PASS_THROUGH,
)
})?
}
ArrayOp::Cat => {
let array_size = inputs.array_size;
binary(&mut stack, |a, b| match (a, b) {
(ArrayStackValue::Array(mut cell), Double(d)) => {
let at = cell.span();
if at >= 0 && at < cell.buf().len() as i64 {
cell.buf_mut()[at as usize] = d;
cell.set_num_el(at + 1);
}
ArrayStackValue::Array(cell)
}
(a, ArrayStackValue::Array(right)) => {
let mut cell = a.into_cell(array_size);
let mut i = cell.span().max(0) as usize;
for &v in right.window() {
if i >= cell.buf().len() {
break;
}
cell.buf_mut()[i] = v;
i += 1;
}
cell.set_num_el(i as i64);
ArrayStackValue::Array(cell)
}
(Double(x), Double(_)) => Double(x),
})?
}
ArrayOp::ArrayRandom => {
let arr: Vec<f64> = (0..inputs.array_size).map(|_| local_random()).collect();
push(&mut stack, ArrayStackValue::array(arr));
}
ArrayOp::ArraySubrange | ArrayOp::ArraySubrangeInPlace => {
let n = inputs.array_size as i64;
let array_size = inputs.array_size;
let (i, j) = pop_subrange_bounds(&mut stack, n)?;
let in_place = matches!(aop, ArrayOp::ArraySubrange);
unary_op(&mut stack, |v| {
let mut cell = v.into_cell(array_size);
if in_place {
let src = cell.buf().to_vec();
let buf = cell.buf_mut();
let mut k = 0usize;
for s in i..=j {
match (buf.get_mut(k), src.get(s as usize)) {
(Some(dst), Some(&v)) => *dst = v,
_ => break,
}
k += 1;
}
buf[k..].fill(0.0);
cell.set_num_el(1 + j - i);
} else {
for (k, v) in cell.buf_mut().iter_mut().enumerate() {
let k = k as i64;
if k < i || k > j {
*v = 0.0;
}
}
cell.set_num_el(j + 1);
}
ArrayStackValue::Array(cell)
})?
}
ArrayOp::ANeg => {
unary_op(&mut stack, |v| v.map(|x| if x < 0.0 { 0.0 } else { x }))?
}
ArrayOp::APos => {
unary_op(&mut stack, |v| v.map(|x| if x > 0.0 { 0.0 } else { x }))?
}
ArrayOp::FetchAval => {
push(
&mut stack,
ArrayStackValue::Array(ArrayCell::new(
inputs.prev_aval.clone(),
inputs.array_size,
)),
);
}
ArrayOp::DynFetch => {
let idx = my_nint(pop_f64(&mut stack)?);
let found = usize::try_from(idx)
.ok()
.and_then(|i| inputs.num_arg(i).map(|v| (i, v)));
match found {
Some((i, v)) => push_src(&mut stack, Double(v), Some(i)),
None => push(&mut stack, Double(0.0)),
}
}
ArrayOp::DynAFetch => {
let idx = my_nint(pop_f64(&mut stack)?);
let arr = usize::try_from(idx)
.ok()
.and_then(|i| inputs.array_arg(i))
.cloned()
.unwrap_or_default();
push(
&mut stack,
ArrayStackValue::Array(ArrayCell::new(arr, inputs.array_size)),
);
}
ArrayOp::DynStore => {
let value = pop_f64(&mut stack)?;
let idx = my_nint(pop_f64(&mut stack)?);
if let Some(slot) = usize::try_from(idx)
.ok()
.and_then(|i| inputs.num_arg_mut(i))
{
*slot = value;
}
}
ArrayOp::DynAStore => {
let value = pop(&mut stack)?.v;
let idx = my_nint(pop_f64(&mut stack)?);
if let Ok(i) = usize::try_from(idx) {
let buf = match value {
ArrayStackValue::Array(cell) => cell.into_buf(),
Double(d) => vec![d; inputs.array_size],
};
inputs.store_array(i, buf);
}
}
ArrayOp::LenNoop => {
}
ArrayOp::FitPoly => {
unary_op(&mut stack, |v| {
unary(
v,
|mut c| {
fit_into_window(&mut c, None, &mut status);
ArrayStackValue::Array(c)
},
ZERO,
)
})?
}
ArrayOp::FitMPoly => {
let array_size = inputs.array_size;
let mask = pop(&mut stack)?.v.as_cell()?.clone();
try_unary_op(&mut stack, |v| {
let mut cell = v.into_cell(array_size);
fit_into_window(&mut cell, Some(&mask), &mut status);
Ok(ArrayStackValue::Array(cell))
})?
}
ArrayOp::FitQ(nargs) => {
let array_size = inputs.array_size;
let targets = pop_fit_targets(&mut stack, *nargs as usize, 4)?;
let mut cell = pop(&mut stack)?.v.into_cell(array_size);
let coeffs = fit_into_window(&mut cell, None, &mut status);
store_fit_coefficients(inputs, targets, coeffs);
push(&mut stack, ArrayStackValue::Array(cell));
}
ArrayOp::FitMQ(nargs) => {
let array_size = inputs.array_size;
if *nargs < 2 {
return Err(CalcError::Underflow);
}
let targets = pop_fit_targets(&mut stack, *nargs as usize, 5)?;
let mask = pop(&mut stack)?.v.into_cell(array_size);
let mut cell = pop(&mut stack)?.v.into_cell(array_size);
let coeffs = fit_into_window(&mut cell, Some(&mask), &mut status);
store_fit_coefficients(inputs, targets, coeffs);
push(&mut stack, ArrayStackValue::Array(cell));
}
},
Opcode::Control(ctrl) => match ctrl {
ControlOp::Until(_end_pc) => {
let until_pc = pc - 1;
match until_marks.iter_mut().find(|(k, _)| *k == until_pc) {
Some((_, depth)) => *depth = stack.len(),
None => until_marks.push((until_pc, stack.len())),
}
}
ControlOp::UntilEnd(start_pc) => {
loops_done += 1;
if loops_done > acalc_loop_max() {
continue;
}
let cond = match stack.last() {
Some(c) => c.v.as_f64()?,
None => return Err(CalcError::Underflow),
};
if cond == 0.0 {
let Some((_, depth)) = until_marks.iter().find(|(k, _)| k == start_pc)
else {
return Err(CalcError::Internal);
};
stack.truncate(*depth);
pc = *start_pc + 1;
}
}
},
#[allow(unreachable_patterns)]
_ => return Err(CalcError::Internal),
}
}
status.into_result()?;
match <[Cell; 1]>::try_from(stack) {
Ok([result]) => Ok(result.v),
Err(_) => Err(CalcError::StackLeak),
}
}
#[derive(Default)]
struct Status(Option<CalcError>);
impl Status {
fn set(&mut self, outcome: Option<CalcError>) {
self.0 = outcome;
}
fn into_result(self) -> Result<(), CalcError> {
self.0.map_or(Ok(()), Err)
}
}
fn fit_into_window(
cell: &mut ArrayCell,
mask: Option<&ArrayCell>,
status: &mut Status,
) -> (f64, f64, f64) {
let window = mask.map_or_else(|| cell.window().len(), |m| m.window().len());
let window = window.min(cell.buf().len());
let x: Vec<f64> = (0..window).map(|i| i as f64).collect();
let y = cell.buf()[..window].to_vec();
let fit = fitting::fitpoly(&x, &y, mask.map(|m| &m.buf()[..window]));
status.set(fit.is_none().then_some(CalcError::FitFailed));
let coeffs = fit.unwrap_or((0.0, 0.0, 0.0));
let (c, b, a) = coeffs;
for (i, xi) in x.iter().enumerate() {
cell.buf_mut()[i] = c + b * xi + a * xi * xi;
}
cell.buf_mut()[window..].fill(0.0);
coeffs
}
fn derivative_into_window(cell: &mut ArrayCell, npts: i64, status: &mut Status) {
let d = derivative::nderiv(cell.window(), npts);
status.set(d.is_none().then_some(CalcError::FitFailed));
let d = d.unwrap_or_else(|| vec![0.0; cell.window().len()]);
cell.window_mut().copy_from_slice(&d);
cell.clear_outside_window();
}
type FitTargets = (Option<usize>, Option<usize>, Option<usize>);
fn pop_fit_targets(
stack: &mut Vec<Cell>,
nargs: usize,
max: usize,
) -> Result<FitTargets, CalcError> {
let mut nargs = nargs;
while nargs > max {
pop(stack)?;
nargs -= 1;
}
let fixed = max - 3;
let coeff_args = nargs.saturating_sub(fixed);
let mut t = [None, None, None]; for slot in (0..coeff_args.min(3)).rev() {
t[slot] = pop(stack)?.src;
}
Ok((t[0], t[1], t[2]))
}
fn store_fit_coefficients(inputs: &mut ArrayInputs, targets: FitTargets, coeffs: (f64, f64, f64)) {
let (tc, tb, ta) = targets;
let (c, b, a) = coeffs;
for (target, value) in [(tc, c), (tb, b), (ta, a)] {
if let Some(i) = target
&& let Some(slot) = inputs.num_arg_mut(i)
{
*slot = value;
}
}
}
fn domain_guarded(v: ArrayStackValue, f: fn(f64) -> f64, status: &mut Status) -> ArrayStackValue {
match v {
Double(d) => Double(if d < 0.0 { 0.0 } else { f(d) }),
ArrayStackValue::Array(mut cell) => {
let mut outcome = None;
for x in cell.buf_mut() {
if *x < 0.0 {
outcome = Some(CalcError::DomainError);
*x = 0.0;
} else {
*x = f(*x);
}
}
status.set(outcome);
ArrayStackValue::Array(cell)
}
}
}
fn max_val(x: f64, y: f64) -> f64 {
if x < y { y } else { x }
}
fn min_val(x: f64, y: f64) -> f64 {
if x > y { y } else { x }
}
#[derive(Clone, Copy)]
enum Extremum {
Max,
Min,
}
impl Extremum {
fn fold_element(self, acc: f64, other: f64) -> f64 {
match self {
Extremum::Max => max_val(acc, other),
Extremum::Min => min_val(acc, other),
}
}
fn keeps(self, arg: f64, acc: f64) -> bool {
match self {
Extremum::Max => arg < acc,
Extremum::Min => arg > acc,
}
}
}
fn vararg_extremum(
stack: &mut Vec<Cell>,
nargs: usize,
array_size: usize,
op: Extremum,
) -> Result<(), CalcError> {
let args = popn(stack, nargs)?;
if args.is_empty() {
return Err(CalcError::Underflow);
}
let result = if args.iter().any(ArrayStackValue::is_array) {
let mut it = args.into_iter();
let mut acc = it.next().ok_or(CalcError::Underflow)?.into_cell(array_size);
for arg in it {
match arg {
ArrayStackValue::Array(other) => {
for (a, &o) in acc.buf_mut().iter_mut().zip(other.buf()) {
*a = op.fold_element(*a, o);
}
}
Double(d) => {
for a in acc.buf_mut() {
*a = op.fold_element(*a, d);
}
}
}
}
ArrayStackValue::Array(acc)
} else {
let vals: Vec<f64> = args
.iter()
.map(ArrayStackValue::as_f64)
.collect::<Result<_, _>>()?;
let mut acc = *vals.last().ok_or(CalcError::Underflow)?;
for &arg in vals.iter().rev().skip(1) {
if !op.keeps(arg, acc) && !acc.is_nan() {
acc = arg;
}
}
Double(acc)
};
push(stack, result);
Ok(())
}
fn unary(
v: ArrayStackValue,
on_array: impl FnOnce(ArrayCell) -> ArrayStackValue,
on_scalar: impl FnOnce(f64) -> f64,
) -> ArrayStackValue {
match v {
ArrayStackValue::Array(c) => on_array(c),
Double(d) => Double(on_scalar(d)),
}
}
fn window_sum(cell: &ArrayCell) -> f64 {
let seed = cell.buf().first().copied().unwrap_or(0.0);
seed + cell.window().iter().skip(1).sum::<f64>()
}
const PASS_THROUGH: fn(f64) -> f64 = |d| d;
const ZERO: fn(f64) -> f64 = |_| 0.0;
fn extremum(cell: &ArrayCell, better: impl Fn(f64, f64) -> bool) -> (f64, usize) {
let mut best = (cell.buf().first().copied().unwrap_or(0.0), 0usize);
for (i, &x) in cell.window().iter().enumerate().skip(1) {
if better(x, best.0) {
best = (x, i);
}
}
best
}
fn index_zero_crossing(arr: &[f64]) -> f64 {
let Some(&first) = arr.first() else {
return -1.0;
};
for i in 1..arr.len() {
if (arr[i] > 0.0) != (first > 0.0) {
let j = i - 1;
let d = arr[j].abs() / (arr[j] - arr[i]).abs();
return j as f64 + d;
}
}
-1.0
}
const SMALL: f64 = 1e-9;
fn shift_elements(a: &mut [f64], e: f64) {
let n = a.len();
if n == 0 {
return;
}
let j = my_nint(e);
if j > 0 {
let j = j as usize;
if j >= n {
a.fill(0.0);
} else {
for i in (j..n).rev() {
a[i] = a[i - j];
}
a[..j].fill(0.0);
}
} else if j < 0 {
let k = j.unsigned_abs() as usize;
if k >= n {
a.fill(0.0);
} else {
for i in 0..(n - k) {
a[i] = a[i + k];
}
a[(n - k)..].fill(0.0);
}
}
let d = (e - f64::from(j)).abs();
if d <= SMALL {
return;
}
if n < 2 {
return;
}
if e < f64::from(j) {
for i in 0..n - 1 {
a[i] += d * (a[i + 1] - a[i]);
}
a[n - 1] += d * (a[n - 1] - a[n - 2]);
} else {
for i in (1..n).rev() {
a[i] += d * (a[i - 1] - a[i]);
}
a[0] += d * (a[0] - a[1]);
}
}
fn pop_subrange_bounds(stack: &mut Vec<Cell>, array_size: i64) -> Result<(i64, i64), CalcError> {
let j = i64::from(c_int(pop_f64(stack)?));
let i = i64::from(c_int(pop_f64(stack)?));
Ok(super::subrange_bounds(i, j, array_size))
}
fn push(stack: &mut Vec<Cell>, v: ArrayStackValue) {
stack.push(Cell { v, src: None });
}
fn push_src(stack: &mut Vec<Cell>, v: ArrayStackValue, src: Option<usize>) {
stack.push(Cell { v, src });
}
fn pop(stack: &mut Vec<Cell>) -> Result<Cell, CalcError> {
stack.pop().ok_or(CalcError::Underflow)
}
fn unary_op(
stack: &mut [Cell],
f: impl FnOnce(ArrayStackValue) -> ArrayStackValue,
) -> Result<(), CalcError> {
let cell = stack.last_mut().ok_or(CalcError::Underflow)?;
let v = std::mem::replace(&mut cell.v, Double(0.0));
cell.v = f(v);
Ok(())
}
fn try_unary_op(
stack: &mut [Cell],
f: impl FnOnce(ArrayStackValue) -> Result<ArrayStackValue, CalcError>,
) -> Result<(), CalcError> {
let cell = stack.last_mut().ok_or(CalcError::Underflow)?;
let v = std::mem::replace(&mut cell.v, Double(0.0));
cell.v = f(v)?;
Ok(())
}
fn binary(
stack: &mut Vec<Cell>,
f: impl FnOnce(ArrayStackValue, ArrayStackValue) -> ArrayStackValue,
) -> Result<(), CalcError> {
let b = pop(stack)?;
unary_op(stack, |a| f(a, b.v))
}
fn popn(stack: &mut Vec<Cell>, n: usize) -> Result<Vec<ArrayStackValue>, CalcError> {
if stack.len() < n {
return Err(CalcError::Underflow);
}
Ok(stack
.split_off(stack.len() - n)
.into_iter()
.map(|c| c.v)
.collect())
}
fn pop_f64(stack: &mut Vec<Cell>) -> Result<f64, CalcError> {
pop(stack)?.v.as_f64()
}
fn cond_search(code: &[Opcode], start: usize, find_else: bool) -> Result<usize, CalcError> {
let mut count: i32 = 1;
let mut pc = start;
while pc < code.len() {
let op = &code[pc];
if matches!(op, Opcode::Core(CoreOp::End)) {
break;
}
let is_match = match op {
Opcode::Core(CoreOp::CondElse) => find_else,
Opcode::Core(CoreOp::CondEnd) => !find_else,
_ => false,
};
if is_match {
count -= 1;
if count == 0 {
return Ok(pc + 1);
}
}
if matches!(op, Opcode::Core(CoreOp::CondIf)) {
count += 1;
}
pc += 1;
}
Err(CalcError::Conditional)
}
#[cfg(test)]
mod stack_depth_invariant {
use super::*;
use crate::calc::engine::ExprKind;
fn run(code: Vec<Opcode>) -> Result<ArrayStackValue, CalcError> {
let expr = CompiledExpr {
code,
..CompiledExpr::empty(ExprKind::Array)
};
eval(&expr, &mut ArrayInputs::new(4))
}
#[test]
fn a_leaked_operand_is_an_error_not_the_top_of_stack() {
let leaked = vec![
Opcode::Core(CoreOp::PushConst(1.0)),
Opcode::Core(CoreOp::PushConst(2.0)),
Opcode::Core(CoreOp::End),
];
assert_eq!(run(leaked), Err(CalcError::StackLeak));
}
#[test]
fn an_empty_stack_is_an_error_not_a_zero() {
let consumed = vec![
Opcode::Core(CoreOp::PushConst(1.0)),
Opcode::Core(CoreOp::StoreVar(0)),
Opcode::Core(CoreOp::End),
];
assert_eq!(run(consumed), Err(CalcError::StackLeak));
}
#[test]
fn exactly_one_value_is_the_result() {
let balanced = vec![
Opcode::Core(CoreOp::PushConst(1.0)),
Opcode::Core(CoreOp::PushConst(2.0)),
Opcode::Core(CoreOp::Add),
Opcode::Core(CoreOp::End),
];
assert_eq!(run(balanced), Ok(Double(3.0)));
}
}