cellrune 0.1.18

Bounded XLSX/XLSM reading, deterministic calculation, editing, and writing for Rust
Documentation
use super::super::ast::Expr;
use super::super::eval::{Engine, EvalContext};
use super::super::runtime::Array;
use super::super::value::{ErrorKind, Value};
use super::kernel::ArrayFunction;
use super::util::required_number;

pub(super) fn call_array(
    engine: &Engine<'_>,
    context: EvalContext<'_>,
    function: ArrayFunction,
    args: &[Expr],
) -> Result<Array, ErrorKind> {
    match function {
        ArrayFunction::ChooseCols => super::modern_array::choose_cols(engine, context, args),
        ArrayFunction::ChooseRows => super::modern_array::choose_rows(engine, context, args),
        ArrayFunction::Drop => super::modern_array::drop(engine, context, args),
        ArrayFunction::Expand => super::array_reshape::expand(engine, context, args),
        ArrayFunction::Filter => super::modern_array::filter_array(engine, context, args),
        ArrayFunction::HStack => super::modern_array::hstack(engine, context, args),
        ArrayFunction::MInverse => minverse(engine, context, args),
        ArrayFunction::Sort => super::modern_array::sort_array(engine, context, args),
        ArrayFunction::SortBy => super::array_sort::sort_by(engine, context, args),
        ArrayFunction::Take => super::modern_array::take(engine, context, args),
        ArrayFunction::ToCol => super::array_reshape::to_col(engine, context, args),
        ArrayFunction::ToRow => super::array_reshape::to_row(engine, context, args),
        ArrayFunction::Unique => super::modern_array::unique_array(engine, context, args),
        ArrayFunction::VStack => super::modern_array::vstack(engine, context, args),
        ArrayFunction::TrimRange => super::array_reshape::trim_range(engine, context, args),
        ArrayFunction::WrapCols => super::array_reshape::wrap_cols(engine, context, args),
        ArrayFunction::WrapRows => super::array_reshape::wrap_rows(engine, context, args),
        ArrayFunction::MMult => mmult(engine, context, args),
        ArrayFunction::MUnit => munit(engine, context, args),
        ArrayFunction::Sequence => sequence(engine, context, args),
        ArrayFunction::Transpose => transpose(engine, context, args),
    }
}

fn minverse(
    engine: &Engine<'_>,
    context: EvalContext<'_>,
    args: &[Expr],
) -> Result<Array, ErrorKind> {
    let [argument] = args else {
        return Err(ErrorKind::Value);
    };
    let source = engine.eval_array(context, argument)?;
    if source.rows != source.cols {
        return Err(ErrorKind::Value);
    }
    let dimension = u64::from(source.rows);
    let cells = dimension.checked_mul(dimension).ok_or(ErrorKind::Num)?;
    let workspace = cells
        .checked_mul(3)
        .and_then(|value| value.checked_add(dimension.checked_mul(2)?))
        .ok_or(ErrorKind::Num)?;
    engine.ensure_array_cells(workspace)?;
    let numbers = strict_numbers(source.data)?;
    let matrix = super::linear_algebra::DenseMatrix::new(
        source.rows as usize,
        source.cols as usize,
        numbers,
    )?;
    let inverse = super::linear_algebra::invert(matrix, |work| {
        engine.charge_function_iterations(context, work)
    })?;
    Ok(Array {
        rows: source.rows,
        cols: source.cols,
        data: inverse.into_data().into_iter().map(Value::Number).collect(),
    })
}

fn munit(engine: &Engine<'_>, context: EvalContext<'_>, args: &[Expr]) -> Result<Array, ErrorKind> {
    let [argument] = args else {
        return Err(ErrorKind::Value);
    };
    let dimension = required_number(engine, context, argument)?.trunc();
    if !dimension.is_finite() || dimension < 1.0 || dimension > f64::from(u32::MAX) {
        return Err(ErrorKind::Value);
    }
    let dimension = dimension as u32;
    let cells = u64::from(dimension)
        .checked_mul(u64::from(dimension))
        .ok_or(ErrorKind::Num)?;
    engine.ensure_array_cells(cells)?;
    engine.charge_function_iterations(context, cells)?;
    let capacity = usize::try_from(cells).map_err(|_| ErrorKind::Num)?;
    let mut data = Vec::with_capacity(capacity);
    for row in 0..dimension {
        for col in 0..dimension {
            data.push(Value::Number(if row == col { 1.0 } else { 0.0 }));
        }
    }
    Ok(Array {
        rows: dimension,
        cols: dimension,
        data,
    })
}

pub(super) fn call_scalar(
    engine: &Engine<'_>,
    context: EvalContext<'_>,
    function: ArrayFunction,
    args: &[Expr],
) -> Value {
    match call_array(engine, context, function, args) {
        Ok(array) => array
            .data
            .into_iter()
            .next()
            .unwrap_or(Value::Error(ErrorKind::Value)),
        Err(kind) => Value::Error(kind),
    }
}

fn mmult(engine: &Engine<'_>, context: EvalContext<'_>, args: &[Expr]) -> Result<Array, ErrorKind> {
    if args.len() != 2 {
        return Err(ErrorKind::Value);
    }
    let left = engine.eval_array(context, &args[0])?;
    let right = engine.eval_array(context, &args[1])?;
    if left.cols != right.rows {
        return Err(ErrorKind::Value);
    }
    let output_cells = u64::from(left.rows) * u64::from(right.cols);
    let operations = output_cells
        .checked_mul(u64::from(left.cols))
        .ok_or(ErrorKind::Num)?;
    engine.ensure_array_cells(output_cells)?;
    engine.charge_function_iterations(context, operations)?;

    let left_numbers = strict_numbers(left.data)?;
    let right_numbers = strict_numbers(right.data)?;
    let mut data = Vec::with_capacity(output_cells as usize);
    for row in 0..left.rows {
        for column in 0..right.cols {
            let mut result = 0.0;
            for inner in 0..left.cols {
                let left_index = (row * left.cols + inner) as usize;
                let right_index = (inner * right.cols + column) as usize;
                result += left_numbers[left_index] * right_numbers[right_index];
            }
            if !result.is_finite() {
                return Err(ErrorKind::Num);
            }
            data.push(Value::Number(result));
        }
    }
    Ok(Array {
        rows: left.rows,
        cols: right.cols,
        data,
    })
}

fn strict_numbers(values: Vec<Value>) -> Result<Vec<f64>, ErrorKind> {
    values
        .into_iter()
        .map(|value| match value {
            Value::Number(number) => Ok(number),
            Value::Error(kind) => Err(kind),
            Value::Blank | Value::Text(_) | Value::Logical(_) => Err(ErrorKind::Value),
        })
        .collect()
}

fn transpose(
    engine: &Engine<'_>,
    context: EvalContext<'_>,
    args: &[Expr],
) -> Result<Array, ErrorKind> {
    if args.len() != 1 {
        return Err(ErrorKind::Value);
    }
    let source = engine.eval_array(context, &args[0])?;
    let cells = u64::from(source.rows) * u64::from(source.cols);
    engine.ensure_array_cells(cells)?;
    let mut data = Vec::with_capacity(cells as usize);
    for row in 0..source.cols {
        for column in 0..source.rows {
            data.push(source.at(column, row).clone());
        }
    }
    Ok(Array {
        rows: source.cols,
        cols: source.rows,
        data,
    })
}

fn sequence(
    engine: &Engine<'_>,
    context: EvalContext<'_>,
    args: &[Expr],
) -> Result<Array, ErrorKind> {
    if args.is_empty() || args.len() > 4 {
        return Err(ErrorKind::Value);
    }
    let rows = dimension(engine, context, args.first())?;
    let columns = dimension(engine, context, args.get(1))?;
    let start = optional_number(engine, context, args.get(2), 1.0)?;
    let step = optional_number(engine, context, args.get(3), 1.0)?;
    let cells = u64::from(rows) * u64::from(columns);
    engine.ensure_array_cells(cells)?;
    engine.charge_function_iterations(context, cells)?;

    let mut data = Vec::with_capacity(cells as usize);
    for index in 0..cells {
        let value = start + step * index as f64;
        if !value.is_finite() {
            return Err(ErrorKind::Num);
        }
        data.push(Value::Number(value));
    }
    Ok(Array {
        rows,
        cols: columns,
        data,
    })
}

fn dimension(
    engine: &Engine<'_>,
    context: EvalContext<'_>,
    expr: Option<&Expr>,
) -> Result<u32, ErrorKind> {
    let value = match expr {
        Some(Expr::Missing) | None => 1.0,
        Some(expr) => required_number(engine, context, expr)?,
    };
    let value = value.trunc();
    if value < 1.0 || value > f64::from(u32::MAX) {
        return Err(ErrorKind::Num);
    }
    Ok(value as u32)
}

fn optional_number(
    engine: &Engine<'_>,
    context: EvalContext<'_>,
    expr: Option<&Expr>,
    default: f64,
) -> Result<f64, ErrorKind> {
    match expr {
        Some(Expr::Missing) | None => Ok(default),
        Some(expr) => required_number(engine, context, expr),
    }
}