Shared Rust kernels
catalogue/lambda_helpers
All modules · quoted at commit 9e1884369a67 of DnaCalc/DnaCalc. Comments are the source's own.
prepared · 25 lines · calc-func/src/catalogue/lambda_helpers.rs on GitHub/// Argument `index` as the helper reads it: a reference to one cell as the cell's value, a larger
/// reference as the array of its values, anything else as it is.
fn prepared(
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)?;
if g.rows == 1 && g.cols == 1 {
return Ok(g.cells.into_iter().next().unwrap_or_else(CalcValue::empty));
}
CalcArray::new(
ArrayShape {
rows: g.rows,
cols: g.cols,
},
g.cells,
)
.map(CalcValue::array)
.ok_or_else(|| site.not_modelled(Some(index)))
}
Used by 5 functions.
lambda · 9 lines · calc-func/src/catalogue/lambda_helpers.rs on GitHub/// The LAMBDA at argument `index`: any other value, an error included, is #VALUE!, and so is a
/// LAMBDA that does not take `count` arguments.
fn lambda<'v>(a: &'v [CalcValue], index: usize, count: usize) -> Step<&'v CalcValue> {
let value = &a[index];
match value.callable_value() {
Some(callable) if callable.arity().accepts(count) => Ok(value),
_ => fail(W::Value),
}
}
Used by 6 functions.
item · 11 lines · calc-func/src/catalogue/lambda_helpers.rs on GitHub/// An item of an array as the LAMBDA receives it.
fn item(cell: &CalcValue) -> CalcValue {
if cell.callable_value().is_some() {
return cell.clone();
}
match cell.core_ref() {
CoreRef::Empty | CoreRef::Missing => CalcValue::empty(),
CoreRef::Array(_) | CoreRef::Reference(_) => CalcValue::error(W::Value),
_ => cell.clone(),
}
}
Used by 5 functions.
cell · 16 lines · calc-func/src/catalogue/lambda_helpers.rs on GitHub/// A call's value as one cell of the helper's result, or `None` when it is an array.
fn cell(value: CalcValue, singleton: bool) -> Option<CalcValue> {
if value.callable_value().is_some() {
return Some(value);
}
match value.core_ref() {
CoreRef::Array(array)
if singleton && array.shape() == (ArrayShape { rows: 1, cols: 1 }) =>
{
cell(array.get(0, 0).expect("one cell").clone(), false)
}
CoreRef::Array(_) | CoreRef::Reference(_) => None,
CoreRef::Missing => Some(CalcValue::empty()),
_ => Some(value),
}
}
Used by 6 functions.
items · 9 lines · calc-func/src/catalogue/lambda_helpers.rs on GitHub/// The items of a prepared value, row-major: an array's cells, or the value itself.
fn items(value: &CalcValue) -> Vec<CalcValue> {
match value.as_array() {
Some(array) if value.callable_value().is_none() => {
array.iter_row_major().map(item).collect()
}
_ => vec![value.clone()],
}
}
Used by 3 functions.
array · 3 lines · calc-func/src/catalogue/lambda_helpers.rs on GitHubfn array(shape: ArrayShape, cells: Vec<CalcValue>) -> CalcValue {
CalcValue::array(CalcArray::new(shape, cells).expect("one cell per item of the shape"))
}
Used by 6 functions.
reduce_task · 31 lines · calc-func/src/catalogue/lambda_helpers.rs on GitHub/// The value a REDUCE or SCAN accumulator starts with: argument 0, a reference read as its value.
fn reduce_task(
site: &Site<'_>,
a: &[CalcValue],
sv: &mut EvalServices<'_>,
scan: bool,
) -> Step<Box<dyn KernelTask>> {
let accumulator = prepared(site, a, 0, sv)?;
let source = prepared(site, a, 1, sv)?;
let f = lambda(a, 2, 2)?;
let list = items(&source);
let shape = match source.as_array() {
Some(array) if source.callable_value().is_none() => array.shape(),
_ => ArrayShape {
rows: 1,
cols: list.len().max(1),
},
};
let count = list.len();
Ok(helper(
site,
f,
HelperInput::Reduce {
list,
accumulator,
scan,
},
shape,
count,
))
}
Used by 2 functions.
by_task · 39 lines · calc-func/src/catalogue/lambda_helpers.rs on GitHub/// BYROW (`columns` false) and BYCOL: the LAMBDA applied to each row (a one-row array) or column.
fn by_task(
site: &Site<'_>,
a: &[CalcValue],
sv: &mut EvalServices<'_>,
columns: bool,
) -> Step<Box<dyn KernelTask>> {
// unswept-00087/00093: direct scalar errors precede an invalid final argument. A reference
// held error still follows the existing preparation and LAMBDA validation order below.
scalar_error(&a[0])?;
let source = prepared(site, a, 0, sv)?;
let f = lambda(a, 1, 1)?;
// Direct errors were handled above. Fn5 helper018/019 keep held errors
// inside the row/column argument so the LAMBDA can handle them.
let source = match source.as_array() {
Some(array) if source.callable_value().is_none() => array.clone(),
_ => CalcArray::from_scalar(item(&source)),
};
let ArrayShape { rows, cols } = source.shape();
let count = if columns { cols } else { rows };
let shape = if columns {
ArrayShape {
rows: 1,
cols: count,
}
} else {
ArrayShape {
rows: count,
cols: 1,
}
};
Ok(helper(
site,
f,
HelperInput::By { source, columns },
shape,
count,
))
}
Used by 2 functions.
helper · 18 lines · calc-func/src/catalogue/lambda_helpers.rs on GitHubfn helper(
site: &Site<'_>,
f: &CalcValue,
input: HelperInput,
shape: ArrayShape,
count: usize,
) -> Box<dyn KernelTask> {
Box::new(HelperTask {
id: site.id(),
callee: f.clone(),
input,
shape,
count,
at: 0,
arguments: None,
cells: Vec::new(),
})
}
Used by 6 functions.
HelperTask::advance · 98 lines · calc-func/src/catalogue/lambda_helpers.rs on GitHubfn advance(
&mut self,
elements: usize,
sv: &mut EvalServices<'_>,
) -> Result<Option<CalcValue>, FunctionFault> {
let refused = || FunctionFault::NotImplemented {
id: self.id,
argument: None,
};
for _ in 0..elements {
if self.at == self.count {
break;
}
let args = self.arguments.get_or_insert_with(|| match &self.input {
HelperInput::Map(lists) => lists
.iter()
.map(|list| {
list.get(self.at)
.cloned()
.unwrap_or_else(|| CalcValue::error(W::NA))
})
.collect(),
HelperInput::Reduce {
list, accumulator, ..
} => vec![accumulator.clone(), list[self.at].clone()],
HelperInput::By { source, columns } => {
let ArrayShape { rows, cols } = source.shape();
let (shape, line) = if *columns {
(
ArrayShape { rows, cols: 1 },
(0..rows)
.map(|r| {
source
.get(r, self.at)
.cloned()
.unwrap_or_else(CalcValue::empty)
})
.collect(),
)
} else {
(
ArrayShape { rows: 1, cols },
source.row_slice(self.at).unwrap_or_default().to_vec(),
)
};
vec![array(shape, line)]
}
HelperInput::MakeArray => vec![
CalcValue::number((self.at / self.shape.cols + 1) as f64),
CalcValue::number((self.at % self.shape.cols + 1) as f64),
],
});
let value = match sv
.callables()
.ok_or_else(refused)?
.call_slice(&self.callee, args)
{
Ok(Some(value)) => value,
Ok(None) => return Ok(None),
Err(CallAbandoned) => return Err(refused()),
};
let (keep, error) = match &mut self.input {
HelperInput::Reduce {
accumulator, scan, ..
} => {
*accumulator = value.clone();
// Preserve the accumulator itself; publication below admits only
// a one-cell result array (fn5 helper011), not larger arrays.
(*scan, W::Calc)
}
HelperInput::MakeArray => (true, W::Value),
_ => (true, W::Calc),
};
if keep {
let singleton = matches!(
&self.input,
HelperInput::By { .. } | HelperInput::Reduce { scan: true, .. }
);
let Some(value) = cell(value, singleton) else {
return Ok(Some(CalcValue::error(error)));
};
self.cells.push(value);
}
self.arguments = None;
self.at += 1;
}
if self.at != self.count {
return Ok(None);
}
Ok(Some(match &self.input {
HelperInput::Reduce {
accumulator,
scan: false,
..
} => accumulator.clone(),
_ => array(self.shape, std::mem::take(&mut self.cells)),
}))
}
Used by 6 functions.
run · 10 lines · calc-func/src/catalogue/lambda_helpers.rs on GitHubfn run(
site: &Site<'_>,
task: Step<Box<dyn KernelTask>>,
sv: &mut EvalServices<'_>,
) -> Step<CalcValue> {
task?
.advance(usize::MAX, sv)
.map_err(crate::kernel::Exit::Fault)?
.ok_or_else(|| site.not_modelled(None))
}
Used by 6 functions.