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