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 GitHubfn 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.