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 GitHub
fn 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 GitHub
fn 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 GitHub
fn 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.