Shared Rust kernels

catalogue/arrays

All modules · quoted at commit 9e1884369a67 of DnaCalc/DnaCalc. Comments are the source's own.

source_error · 5 lines · calc-func/src/catalogue/arrays.rs on GitHub
/// W111 typed array-function controls distinguish a direct scalar source error from an error
/// held in an actual array or reference.
fn source_error(arguments: &[CalcValue]) -> Step<()> {
    scalar_error(&arguments[0])
}

Used by 8 functions.

prepared · 8 lines · calc-func/src/catalogue/arrays.rs on GitHub
/// The arguments as OxFunc's kernels take them: each reference read as the array of its values.
fn prepared(site: &Site<'_>, a: &[CalcValue], sv: &mut EvalServices<'_>) -> Step<Vec<CalcValue>> {
    let mut out = Vec::with_capacity(a.len());
    for index in 0..a.len() {
        out.push(prepared_one(site, a, index, sv)?);
    }
    Ok(out)
}

Used by 14 functions.

prepared_one · 18 lines · calc-func/src/catalogue/arrays.rs on GitHub
fn prepared_one(
    site: &Site<'_>,
    a: &[CalcValue],
    index: usize,
    sv: &mut EvalServices<'_>,
) -> Step<CalcValue> {
    if !matches!(a[index].core_ref(), CoreRef::Reference(_)) {
        return Ok(a[index].clone());
    }
    let g = grid(site, a, index, sv)?;
    let shape = ArrayShape {
        rows: g.rows,
        cols: g.cols,
    };
    CalcArray::new(shape, g.cells)
        .map(CalcValue::array)
        .ok_or_else(|| site.not_modelled(Some(index)))
}

Used by 15 functions.

by_area · 60 lines · calc-func/src/catalogue/arrays.rs on GitHub
/// TOCOL (`column`) or TOROW of a reference of several areas (a 3-D reference): each area flattened
/// by the call's own ignore and scan arguments, in the reference's order (sheet by sheet), the
/// results joined into one column or row; `None` for any other first argument. Excel 16.0.20430
/// (2026-10-25, dc-6au), sheets Ja, Fe and Ma holding 1, 10 / 100 at A1, B1 / A2 (times 1, 2, 3):
/// `=TOCOL(Ja:Ma!A1:B1)` and `=TOROW(..)` give 1,10,2,20,3,30, and `=TOCOL(Ja:Ma!A1:B2,1)`, blanks
/// ignored, 1,10,100,2,20,200,3,30,300 (9 rows). An area whose cells are all left out adds
/// nothing; when every area does, the result is the kernel's own for an empty one.
fn by_area(
    site: &Site<'_>,
    a: &[CalcValue],
    sv: &mut EvalServices<'_>,
    kernel: fn(&[CalcValue]) -> Result<CalcValue, E>,
    column: bool,
) -> Step<Option<CalcValue>> {
    let Some(reference) = a[0].as_reference() else {
        return Ok(None);
    };
    if reference.areas().len() < 2 {
        return Ok(None);
    }
    let mut cells = Vec::new();
    let mut empty = None;
    for &area in reference.areas() {
        let mut args = a.to_vec();
        args[0] = CalcValue::reference(ReferenceLike::area(area));
        args[0] = prepared_one(site, &args, 0, sv)?;
        match result(site, kernel(&args)) {
            Ok(value) => match value.as_array() {
                Some(array) => cells.extend(array.iter_row_major().cloned()),
                None => cells.push(value),
            },
            // An area whose every cell is left out (#CALC!, an empty array).
            Err(Exit::Error(WorksheetErrorCode::Calc)) => {
                empty.get_or_insert(WorksheetErrorCode::Calc);
            }
            Err(other) => return Err(other),
        }
    }
    if cells.is_empty() {
        return match empty {
            Some(code) => fail(code),
            None => Ok(None),
        };
    }
    let count = cells.len();
    let shape = if column {
        ArrayShape {
            rows: count,
            cols: 1,
        }
    } else {
        ArrayShape {
            rows: 1,
            cols: count,
        }
    };
    Ok(Some(CalcValue::array(
        CalcArray::new(shape, cells).expect("one cell per item of the shape"),
    )))
}

Used by 2 functions.

result · 9 lines · calc-func/src/catalogue/arrays.rs on GitHub
/// A kernel's result: its array, or its failure as a worksheet error (a coercion calc-func does
/// not model is refused).
fn result<T>(site: &Site<'_>, r: Result<T, E>) -> Step<T> {
    match r {
        Ok(value) => Ok(value),
        Err(E::Preparation(CoercionError::NotModelled)) => Err(site.not_modelled(None)),
        Err(error) => fail(k::map_dynamic_array_reshape_error_to_ws(&error)),
    }
}

Used by 13 functions.

run · 10 lines · calc-func/src/catalogue/arrays.rs on GitHub
/// Runs `kernel` on the prepared arguments.
fn run(
    site: &Site<'_>,
    a: &[CalcValue],
    sv: &mut EvalServices<'_>,
    kernel: fn(&[CalcValue]) -> Result<CalcValue, E>,
) -> Step<CalcValue> {
    let args = prepared(site, a, sv)?;
    result(site, kernel(&args))
}

Used by 9 functions.

sub_area · 57 lines · calc-func/src/catalogue/arrays.rs on GitHub
/// TAKE (`take`) or DROP of a reference to one area: the reference to the rows and columns kept,
/// or `None` for an array argument. Excel returns a reference here: `=_xlfn.DROP(Data!A1:A3,1)`
/// entered as a legacy formula outside rows 1 to 3 is #VALUE!, the implicit intersection of the
/// reference A2:A3 (the catalogue oracle's capture), not 5, the first of the values.
fn sub_area(site: &Site<'_>, a: &[CalcValue], take: bool) -> Step<Option<CalcValue>> {
    let Some(reference) = a[0].as_reference() else {
        return Ok(None);
    };
    let [area] = reference.areas() else {
        return Ok(None);
    };
    let (rows, cols) = (area.row_count() as usize, area.column_count() as usize);
    let count = |index: usize, full: isize| -> Step<isize> {
        match a.get(index) {
            None => Ok(full),
            Some(value) if value.is_missing() && index == 1 && a.len() > 2 => Ok(full),
            Some(value) => match k::parse_integer(value) {
                Ok(n) => Ok(n),
                Err(error) => result::<CalcValue>(site, Err(error)).map(|_| 0),
            },
        }
    };
    let (row_full, col_full) = if take {
        (rows as isize, cols as isize)
    } else {
        (0, 0)
    };
    let (row_count, col_count) = (count(1, row_full)?, count(2, col_full)?);
    let span = |len: usize, n: isize| {
        if take {
            k::take_span(len, n)
        } else {
            k::drop_span(len, n)
        }
    };
    let ((r0, r1), (c0, c1)) = match (span(rows, row_count), span(cols, col_count)) {
        (Ok(r), Ok(c)) => (r, c),
        (Err(error), _) | (_, Err(error)) => {
            return result::<CalcValue>(site, Err(error)).map(|_| None);
        }
    };
    let first = area.first();
    let corner = |row: usize, col: usize| {
        CellKey::new(
            area.sheet(),
            first.row() + row as u32,
            first.column() + col as u32,
        )
    };
    match (corner(r0, c0), corner(r1 - 1, c1 - 1)) {
        (Ok(top_left), Ok(bottom_right)) => match GridArea::new(top_left, bottom_right) {
            Ok(kept) => Ok(Some(CalcValue::reference(ReferenceLike::area(kept)))),
            Err(_) => Err(site.not_modelled(Some(0))),
        },
        _ => Err(site.not_modelled(Some(0))),
    }
}

Used by 2 functions.

choose · 19 lines · calc-func/src/catalogue/arrays.rs on GitHub
/// CHOOSEROWS and CHOOSECOLS: the selectors after the first argument, each array flattened in
/// row-major order, as OxFunc's surface expands them.
fn choose(
    site: &Site<'_>,
    a: &[CalcValue],
    sv: &mut EvalServices<'_>,
    kernel: fn(&[CalcValue]) -> Result<CalcValue, E>,
) -> Step<CalcValue> {
    source_error(a)?;
    let args = prepared(site, a, sv)?;
    let mut flat = vec![args[0].clone()];
    for arg in &args[1..] {
        match arg.as_array() {
            Some(array) => flat.extend(array.iter_row_major().cloned()),
            None => flat.push(arg.clone()),
        }
    }
    result(site, kernel(&flat))
}

Used by 2 functions.

as_array · 10 lines · calc-func/src/catalogue/arrays.rs on GitHub
/// A prepared argument as an array (a single value as one by one).
fn as_array(value: &CalcValue) -> CalcArray {
    match value.as_array() {
        Some(array) => array.clone(),
        None => CalcArray::from_scalar(match value.core_ref() {
            CoreRef::Missing => CalcValue::empty(),
            _ => value.clone(),
        }),
    }
}

Used by 8 functions.

integer · 16 lines · calc-func/src/catalogue/arrays.rs on GitHub
/// An integer argument of SORT and SORTBY: a blank or omitted one is `None`.
fn integer(site: &Site<'_>, value: Option<&CalcValue>) -> Step<Option<isize>> {
    let Some(value) = value else {
        return Ok(None);
    };
    if matches!(value.core_ref(), CoreRef::Missing | CoreRef::Empty) {
        return Ok(None);
    }
    match k::coerce_prepared_to_number(value) {
        Ok(n) if n.is_finite() => Ok(Some(n.trunc() as isize)),
        Ok(_) => fail(WorksheetErrorCode::Value),
        Err(CoercionError::WorksheetError(code)) => fail(code),
        Err(CoercionError::NotModelled) => Err(site.not_modelled(None)),
        Err(CoercionError::UnsupportedValueKind(_)) => fail(WorksheetErrorCode::Value),
    }
}

Used by 3 functions.

descending · 8 lines · calc-func/src/catalogue/arrays.rs on GitHub
/// A sort order: 1 (or omitted) ascending, -1 descending, anything else #VALUE!.
fn descending(site: &Site<'_>, value: Option<&CalcValue>) -> Step<bool> {
    match integer(site, value)? {
        None | Some(1) => Ok(false),
        Some(-1) => Ok(true),
        Some(_) => fail(WorksheetErrorCode::Value),
    }
}

Used by 2 functions.

flag · 13 lines · calc-func/src/catalogue/arrays.rs on GitHub
/// A logical flag argument (by_col, exactly_once): omitted or blank is FALSE.
fn flag(site: &Site<'_>, value: Option<&CalcValue>) -> Step<bool> {
    match value.map(CalcValue::core_ref) {
        None | Some(CoreRef::Missing | CoreRef::Empty) => Ok(false),
        Some(CoreRef::Logical(b)) => Ok(b),
        Some(CoreRef::Number(n)) => Ok(n != 0.0),
        Some(CoreRef::Error(code)) => fail(code),
        Some(_) => match integer(site, value)? {
            Some(n) => Ok(n != 0),
            None => Ok(false),
        },
    }
}

Used by 2 functions.

kind · 11 lines · calc-func/src/catalogue/arrays.rs on GitHub
/// The rank of a value's kind in the sort order.
fn kind(value: &CalcValue) -> u8 {
    match value.core_ref() {
        CoreRef::Empty | CoreRef::Missing => 0,
        CoreRef::Number(_) => 1,
        CoreRef::Text(_) => 2,
        CoreRef::Logical(_) => 3,
        CoreRef::Error(_) => 4,
        CoreRef::Array(_) | CoreRef::Reference(_) => 5,
    }
}

Used by 3 functions.

order · 15 lines · calc-func/src/catalogue/arrays.rs on GitHub
/// The order of two values for SORT and SORTBY.
fn order(site: &Site<'_>, x: &CalcValue, y: &CalcValue) -> Step<Ordering> {
    match (x.core_ref(), y.core_ref()) {
        (CoreRef::Number(a), CoreRef::Number(b)) => comparison::numbers(site, a, b),
        (CoreRef::Text(a), CoreRef::Text(b)) => {
            comparison::texts(a.utf16_code_units(), b.utf16_code_units(), Want::Order)
                .map_err(|_| site.not_modelled(None))
        }
        (CoreRef::Logical(a), CoreRef::Logical(b)) => Ok(a.cmp(&b)),
        (CoreRef::Error(a), CoreRef::Error(b)) => Ok((a as u8).cmp(&(b as u8))),
        (CoreRef::Array(_) | CoreRef::Reference(_), _)
        | (_, CoreRef::Array(_) | CoreRef::Reference(_)) => Err(site.not_modelled(None)),
        _ => Ok(kind(x).cmp(&kind(y))),
    }
}

Used by 3 functions.

sorted_indices · 30 lines · calc-func/src/catalogue/arrays.rs on GitHub
/// The indices `0..len` stably sorted by `keys` (each a key per index and its direction); the
/// first refused comparison refuses the call.
fn sorted_indices(
    site: &Site<'_>,
    len: usize,
    keys: &[(Vec<CalcValue>, bool)],
) -> Step<Vec<usize>> {
    let refused: RefCell<Option<Exit>> = RefCell::new(None);
    let mut indices: Vec<usize> = (0..len).collect();
    indices.sort_by(|&i, &j| {
        if refused.borrow().is_some() {
            return Ordering::Equal;
        }
        for (key, descending) in keys {
            match order(site, &key[i], &key[j]) {
                Ok(Ordering::Equal) => continue,
                Ok(o) => return if *descending { o.reverse() } else { o },
                Err(exit) => {
                    *refused.borrow_mut() = Some(exit);
                    return Ordering::Equal;
                }
            }
        }
        Ordering::Equal
    });
    match refused.into_inner() {
        Some(exit) => Err(exit),
        None => Ok(indices),
    }
}

Used by 2 functions.

reordered · 18 lines · calc-func/src/catalogue/arrays.rs on GitHub
/// `array`'s rows (or, `by_col`, its columns) in the order of `indices`.
fn reordered(array: &CalcArray, indices: &[usize], by_col: bool) -> Step<CalcValue> {
    let ArrayShape { rows, cols } = array.shape();
    let mut cells = Vec::with_capacity(rows * cols);
    for row in 0..rows {
        for col in 0..cols {
            let (r, c) = if by_col {
                (row, indices[col])
            } else {
                (indices[row], col)
            };
            cells.push(array.get(r, c).cloned().unwrap_or_else(CalcValue::empty));
        }
    }
    Ok(CalcValue::array(
        CalcArray::new(ArrayShape { rows, cols }, cells).expect("the array's own shape"),
    ))
}

Used by 2 functions.