Shared Rust kernels
catalogue/aggregate
All modules · quoted at commit 9e1884369a67 of DnaCalc/DnaCalc. Comments are the source's own.
aggregate · 17 lines · calc-func/src/catalogue/aggregate.rs on GitHub/// An eager named call that takes 1 to 255 arguments, the last parameter repeating.
const fn aggregate(
id: FunctionId,
spelling: &'static str,
parameters: &'static [Parameter],
kernel: EagerKernel,
) -> CatalogueEntry {
CatalogueEntry {
id,
spelling,
notation: Notation::Call,
arity: CallableArityShape::range(1, 255),
parameters,
demand: ArgumentDemand::Eager,
kernel: Kernel::Eager(kernel),
}
}
Used by 12 functions.
direct_first · 14 lines · calc-func/src/catalogue/aggregate.rs on GitHub/// Reads the direct arguments through worksheet number coercion, in order, and stops at the first
/// that is an error or that coercion stops on. Their numbers are not used: the fold reads them
/// again in the reading order.
pub(super) fn direct_first(site: &Site<'_>, arguments: &[CalcValue]) -> Step<()> {
for (index, argument) in arguments.iter().enumerate() {
if !matches!(
argument.core_ref(),
CoreRef::Reference(_) | CoreRef::Array(_)
) {
site.number_argument(index, argument)?;
}
}
Ok(())
}
Used by 2 functions.
product · 23 lines · calc-func/src/catalogue/aggregate.rs on GitHub/// PRODUCT: the numbers multiplied from 1, or 0 when there is none. An omitted argument is passed
/// over (dc-bd5: PRODUCT(2,) is 2 in Excel 16.0.20430, where SUM and the others read it as 0).
fn product(
arguments: &[CalcValue],
services: &mut EvalServices<'_>,
) -> Result<CalcValue, FunctionFault> {
let site = Site::new(PRODUCT, services);
if holds_held_arrays(arguments) {
return Site::finish(Err(Exit::Error(WorksheetErrorCode::NA)));
}
let given: Vec<CalcValue> = arguments
.iter()
.filter(|argument| !argument.is_missing())
.cloned()
.collect();
let arguments = given.as_slice();
let mut running: Option<f64> = None;
let read = each_number(&site, arguments, services, Held::Numbers, &mut |n| {
running = Some(times(&site, running.unwrap_or(1.0), n)?);
Ok(())
});
Site::finish(read.and_then(|()| site.number(running.unwrap_or(0.0))))
}
Used by 3 functions.
mean · 28 lines · calc-func/src/catalogue/aggregate.rs on GitHubfn mean(
id: FunctionId,
held: Held,
arguments: &[CalcValue],
services: &mut EvalServices<'_>,
) -> Result<CalcValue, FunctionFault> {
let site = Site::new(id, services);
if holds_held_arrays(arguments) {
return Site::finish(Err(Exit::Error(WorksheetErrorCode::NA)));
}
let held_references = if id == AVERAGE {
match read_held_references(&site, services, arguments, HeldReference::FirstCell) {
Ok(held) => held,
Err(exit) => return Site::finish(Err(exit)),
}
} else {
None
};
let arguments: &[CalcValue] = held_references.as_deref().unwrap_or(arguments);
let (mut total, mut counted) = (0.0, 0u64);
let read = each_number(&site, arguments, services, held, &mut |n| {
total = site.add(total, n)?;
counted += 1;
Ok(())
});
// A count is below 2^53, so it converts to binary64 exactly.
Site::finish(read.and_then(|()| site.number(site.divide(total, counted as f64)?)))
}
Used by 2 functions.
min · 7 lines · calc-func/src/catalogue/aggregate.rs on GitHub/// MIN: the smallest number, or 0 when there is none. Choosing one number needs no rounding.
fn min(
arguments: &[CalcValue],
services: &mut EvalServices<'_>,
) -> Result<CalcValue, FunctionFault> {
extreme(MIN, Held::Numbers, |n, best| n < best, arguments, services)
}
Used by 2 functions.
extreme · 50 lines · calc-func/src/catalogue/aggregate.rs on GitHubfn extreme(
id: FunctionId,
held: Held,
replaces: fn(f64, f64) -> bool,
arguments: &[CalcValue],
services: &mut EvalServices<'_>,
) -> Result<CalcValue, FunctionFault> {
let site = Site::new(id, services);
if holds_held_arrays(arguments) {
return Site::finish(Err(Exit::Error(WorksheetErrorCode::NA)));
}
let held_references = if id == MAX {
match read_held_references(&site, services, arguments, HeldReference::FirstCell) {
Ok(held) => held,
Err(exit) => return Site::finish(Err(exit)),
}
} else {
None
};
let arguments: &[CalcValue] = held_references.as_deref().unwrap_or(arguments);
let mut best: Option<CalcValue> = None;
let read = each_value(&site, arguments, services, &mut |index, origin, value| {
let n = if value.is_excel_overflow() {
value.as_number()
} else {
number_of(&site, held, index, origin, value)?
};
let Some(n) = n else { return Ok(()); };
let replace = if let Some(best) = &best
&& (best.is_excel_overflow() || value.is_excel_overflow())
{
let order = super::comparison::overflow_numbers(&site, n, best.as_number().expect("selected number"))?;
if id == MIN || id == MINA { order.is_lt() } else { order.is_gt() }
} else {
best.as_ref().is_none_or(|best| {
let prior = best.as_number().expect("selected number");
replaces(n, prior)
})
};
if replace {
best = Some(if value.is_excel_overflow() { value.clone() } else { CalcValue::number(n) });
}
Ok(())
});
Site::finish(read.and_then(|()| match best {
Some(value) if value.is_excel_overflow() => Ok(value),
Some(value) => site.number(value.as_number().expect("selected number")),
None => site.number(0.0),
}))
}
Used by 5 functions.
finite · 8 lines · calc-func/src/catalogue/aggregate.rs on GitHub/// A number a count reads. A NaN or an infinity is no worksheet value and is refused.
fn finite(site: &Site<'_>, index: usize, n: f64) -> Step<()> {
if n.is_finite() {
Ok(())
} else {
Err(site.not_modelled(Some(index)))
}
}
Used by 2 functions.
times · 10 lines · calc-func/src/catalogue/aggregate.rs on GitHub/// `a * b` at an arithmetic site; a product below binary64's normal range is 0, as Excel flushes
/// it (dc-bd5).
fn times(site: &Site<'_>, a: f64, b: f64) -> Step<f64> {
let product = site.multiply(a, b)?;
if product.is_subnormal() {
Ok(0.0)
} else {
Ok(product)
}
}
Used by 3 functions.
holds_held_arrays · 7 lines · calc-func/src/catalogue/aggregate.rs on GitHub/// Whether an array argument holds an array in a cell (dc-h9u).
fn holds_held_arrays(arguments: &[CalcValue]) -> bool {
arguments.iter().any(|argument| {
argument.callable_value().is_none()
&& argument.as_array().is_some_and(|array| array.holds_arrays())
})
}
Used by 12 functions.
read_through_held_arrays · 25 lines · calc-func/src/catalogue/aggregate.rs on GitHub/// The arguments with each array argument that holds arrays in its cells read through: the held
/// arrays' elements in the place of the cell, in row-major order, as one row (dc-h9u: SUM, COUNT
/// and COUNTA read a held array's elements as the outer array's); `None`, allocating nothing,
/// when no argument holds one.
fn read_through_held_arrays(arguments: &[CalcValue]) -> Option<Vec<CalcValue>> {
if !holds_held_arrays(arguments) {
return None;
}
let read = arguments
.iter()
.map(|argument| match argument.as_array() {
Some(array) if argument.callable_value().is_none() && array.holds_arrays() => {
let mut flat = Vec::new();
flatten(array, &mut flat);
let shape = crate::value::ArrayShape {
rows: 1,
cols: flat.len(),
};
CalcValue::array(CalcArray::new(shape, flat).expect("at least one element"))
}
_ => argument.clone(),
})
.collect();
Some(read)
}
Used by 3 functions.
flatten · 8 lines · calc-func/src/catalogue/aggregate.rs on GitHubfn flatten(array: &CalcArray, out: &mut Vec<CalcValue>) {
for element in array.iter_row_major() {
match element.as_array() {
Some(held) if element.callable_value().is_none() => flatten(held, out),
_ => out.push(element.clone()),
}
}
}
Used by 3 functions.
read_held_references · 56 lines · calc-func/src/catalogue/aggregate.rs on GitHub/// The arguments with each reference an array argument holds read by `how`; `None`, allocating
/// nothing, when no array argument holds one.
fn read_held_references(
site: &Site<'_>,
services: &mut EvalServices<'_>,
arguments: &[CalcValue],
how: HeldReference,
) -> Step<Option<Vec<CalcValue>>> {
let holds = |argument: &CalcValue| {
argument.callable_value().is_none()
&& argument.as_array().is_some_and(|array| {
array
.iter_row_major()
.any(|element| matches!(element.core_ref(), CoreRef::Reference(_)))
})
};
if !arguments.iter().any(holds) {
return Ok(None);
}
let mut read = Vec::with_capacity(arguments.len());
for (index, argument) in arguments.iter().enumerate() {
let Some(array) = argument.as_array().filter(|_| holds(argument)) else {
read.push(argument.clone());
continue;
};
let mut cells = Vec::with_capacity(array.cell_count());
for element in array.iter_row_major() {
let CoreRef::Reference(reference) = element.core_ref() else {
cells.push(element.clone());
continue;
};
cells.push(match how {
HeldReference::NoNumber => CalcValue::text(crate::value::ExcelText::from_utf16_code_units(Vec::new())),
HeldReference::Counted => CalcValue::logical(true),
HeldReference::FirstCell => {
let first = reference.areas()[0].first();
let mut value = CalcValue::empty();
let cells_reader = reader(site, services, index)?;
cells_reader
.read_area(GridArea::cell(first), &mut |_, held| {
value = held.clone();
ControlFlow::Break(())
})
.map_err(|Unreadable| {
Exit::Fault(FunctionFault::ReferenceUnreadable { id: site.id(), index })
})?;
value
}
});
}
read.push(CalcValue::array(
CalcArray::new(array.shape(), cells).expect("the array's own shape"),
));
}
Ok(Some(read))
}
Used by 10 functions.
number_of · 38 lines · calc-func/src/catalogue/aggregate.rs on GitHub/// The number that `value`, of argument `index`, gives an aggregate that reads held values by
/// `held`, or `None` when the value is skipped. A direct argument goes through worksheet number
/// coercion, which converts text that reads as a number at an arithmetic site.
pub(super) fn number_of(
site: &Site<'_>,
held: Held,
index: usize,
origin: Origin,
value: &CalcValue,
) -> Step<Option<f64>> {
if matches!(held, Held::ModeNumbers)
&& matches!(value.core_ref(), CoreRef::Text(_) | CoreRef::Logical(_))
{
return Ok(None);
}
if origin == Origin::Direct {
if matches!(held, Held::ModeNumbers)
&& matches!(value.core_ref(), CoreRef::Empty | CoreRef::Missing)
{
return Err(Exit::Error(WorksheetErrorCode::Value));
}
return site.number_argument(index, value).map(Some);
}
match (held, value.core_ref()) {
(_, CoreRef::Number(_)) if value.is_excel_overflow() => Err(Exit::Error(WorksheetErrorCode::Num)),
(_, CoreRef::Number(n)) => site.argument(index, coerce::operand(n)).map(Some),
(_, CoreRef::Error(code)) => Err(Exit::Error(code)),
(Held::AverageValues, CoreRef::Text(_)) => Ok(Some(0.0)),
(Held::Values, CoreRef::Text(_)) if origin == Origin::Held => Ok(Some(0.0)),
(Held::Values | Held::AverageValues, CoreRef::Logical(b)) if origin == Origin::Held => {
Ok(Some(if b { 1.0 } else { 0.0 }))
}
(Held::Values | Held::AverageValues, CoreRef::Text(_) | CoreRef::Logical(_)) => Ok(None),
(Held::Numbers | Held::ModeNumbers, CoreRef::Text(_) | CoreRef::Logical(_))
| (_, CoreRef::Empty | CoreRef::Missing) => Ok(None),
(_, CoreRef::Array(_) | CoreRef::Reference(_)) => Err(site.not_modelled(Some(index))),
}
}
Used by 11 functions.
each_number · 19 lines · calc-func/src/catalogue/aggregate.rs on GitHub/// Calls `take` with every number the arguments give, in the reading order.
pub(super) fn each_number(
site: &Site<'_>,
arguments: &[CalcValue],
services: &mut EvalServices<'_>,
held: Held,
take: &mut dyn FnMut(f64) -> Step<()>,
) -> Step<()> {
read_values(
site,
arguments,
services,
matches!(held, Held::ModeNumbers),
&mut |index, origin, value| match number_of(site, held, index, origin, value)? {
Some(n) => take(n),
None => Ok(()),
},
)
}
Used by 7 functions.
each_value · 11 lines · calc-func/src/catalogue/aggregate.rs on GitHub/// Calls `visit` with every value of the arguments in the reading order: a direct argument as
/// itself, and each value a reference or an array holds. Stops at the first step that is not
/// `Ok`.
pub(super) fn each_value(
site: &Site<'_>,
arguments: &[CalcValue],
services: &mut EvalServices<'_>,
visit: &mut dyn FnMut(usize, Origin, &CalcValue) -> Step<()>,
) -> Step<()> {
read_values(site, arguments, services, false, visit)
}
Used by 7 functions.
read_values · 38 lines · calc-func/src/catalogue/aggregate.rs on GitHub/// MODE's scalar-cell rule uses the same reader and argument order. A sparse
/// reader need not visit a blank cell, so retain Empty until a value is visited.
fn read_values(
site: &Site<'_>,
arguments: &[CalcValue],
services: &mut EvalServices<'_>,
scalar_references: bool,
visit: &mut dyn FnMut(usize, Origin, &CalcValue) -> Step<()>,
) -> Step<()> {
for (index, argument) in arguments.iter().enumerate() {
match argument.core_ref() {
CoreRef::Reference(reference) => {
let cells = reader(site, services, index)?;
if scalar_references && matches!(reference.areas(), [area]
if area.row_count() == 1 && area.column_count() == 1)
{
let mut value = CalcValue::empty();
read_reference(site, cells, reference, index, &mut |_, _, held| {
value = held.clone();
Ok(())
})?;
visit(index, Origin::Direct, &value)?;
} else {
read_reference(site, cells, reference, index, &mut |_, _, value| {
visit(index, Origin::Held, value)
})?;
}
}
CoreRef::Array(array) => {
for element in array.iter_row_major() {
visit(index, Origin::Element, element)?;
}
}
_ => visit(index, Origin::Direct, argument)?,
}
}
Ok(())
}
Used by 13 functions.
reader · 13 lines · calc-func/src/catalogue/aggregate.rs on GitHub/// The services' cell reader, which a kernel needs to read the reference at argument `index`.
pub(super) fn reader<'a, 's>(
site: &Site<'_>,
services: &'a mut EvalServices<'s>,
index: usize,
) -> Step<&'a mut (dyn CellReader + 's)> {
services
.cells()
.ok_or(Exit::Fault(FunctionFault::ReferenceUnreadable {
id: site.id(),
index,
}))
}
Used by 54 functions.
read_reference · 37 lines · calc-func/src/catalogue/aggregate.rs on GitHub/// Reads the held cells of every area of `reference`, the argument at `index`, in its order:
/// `visit` receives each cell's area, key and value. A reader that refuses an area, or visits a
/// cell outside it, cannot supply the area, and the call ends in ReferenceUnreadable.
pub(super) fn read_reference(
site: &Site<'_>,
cells: &mut dyn CellReader,
reference: &ReferenceLike,
index: usize,
visit: &mut dyn FnMut(GridArea, CellKey, &CalcValue) -> Step<()>,
) -> Step<()> {
let unreadable = || {
Exit::Fault(FunctionFault::ReferenceUnreadable {
id: site.id(),
index,
})
};
for &area in reference.areas() {
let mut stopped = Ok(());
let read = cells.read_area(area, &mut |key, value| {
let step = if area.contains(key) {
visit(area, key, value)
} else {
Err(unreadable())
};
match step {
Ok(()) => ControlFlow::Continue(()),
Err(exit) => {
stopped = Err(exit);
ControlFlow::Break(())
}
}
});
read.map_err(|Unreadable| unreadable())?;
stopped?;
}
Ok(())
}
Used by 54 functions.