logisheets_controller 1.16.1

Core spreadsheet engine for LogiSheets: formula evaluation, dependency tracking, and undo/redo.
Documentation
use crate::calc_engine::{
    calculator::calc_vertex::{CalcValue, CalcVertex, Reference, Value},
    connector::Connector,
};
use logisheets_parser::ast;

/// `ISFORMULA(reference)` — TRUE if the top-left cell of `reference` carries a
/// formula, FALSE otherwise. A non-reference argument is `#VALUE!` (Excel).
pub fn calc_isformula<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    assert_or_return!(args.len() == 1, ast::Error::Unspecified);
    let reference = match args.into_iter().next().unwrap() {
        CalcVertex::Reference(r) => r,
        _ => return CalcVertex::from_error(ast::Error::Value),
    };
    let (row, col) = match reference.reference {
        Reference::Addr(a) => (a.row, a.col),
        Reference::Range(s, _) => (s.row, s.col),
        Reference::ColumnRange(cr) => (0, cr.start),
        Reference::RowRange(rr) => (rr.start, 0),
    };
    let sheet = reference.sheet;
    match fetcher.get_cell_id(sheet, row, col) {
        Ok(cell_id) => CalcVertex::from_bool(fetcher.has_formula(sheet, &cell_id)),
        Err(_) => CalcVertex::from_error(ast::Error::Value),
    }
}

pub fn calc_isblank<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    let f = |v: &CalcValue| -> Result<bool, ast::Error> {
        match v {
            CalcValue::Scalar(s) => match s {
                Value::Blank => Ok(true),
                _ => Ok(false),
            },
            _ => Ok(false),
        }
    };
    calc(args, fetcher, &f)
}

pub fn calc_isnumber<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    let f = |v: &CalcValue| -> Result<bool, ast::Error> {
        match v {
            CalcValue::Scalar(s) => match s {
                Value::Number(_) => Ok(true),
                _ => Ok(false),
            },
            _ => Ok(false),
        }
    };
    calc(args, fetcher, &f)
}

pub fn calc_islogical<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    let f = |v: &CalcValue| -> Result<bool, ast::Error> {
        match v {
            CalcValue::Scalar(s) => match s {
                Value::Boolean(_) => Ok(true),
                _ => Ok(false),
            },
            _ => Ok(false),
        }
    };
    calc(args, fetcher, &f)
}

pub fn calc_istext<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    let f = |v: &CalcValue| -> Result<bool, ast::Error> {
        match v {
            CalcValue::Scalar(s) => match s {
                Value::Text(_) => Ok(true),
                _ => Ok(false),
            },
            _ => Ok(false),
        }
    };
    calc(args, fetcher, &f)
}

pub fn calc_isnontext<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    let f = |v: &CalcValue| -> Result<bool, ast::Error> {
        match v {
            CalcValue::Scalar(s) => match s {
                Value::Text(_) => Ok(false),
                _ => Ok(true),
            },
            _ => Ok(true),
        }
    };
    calc(args, fetcher, &f)
}

pub fn calc_iseven<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    calc(args, fetcher, &|v| parity(v))
}

pub fn calc_isodd<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    calc(args, fetcher, &|v| parity(v).map(|even| !even))
}

/// Whether a value's (truncated) integer part is even. Numbers/blank/bools
/// coerce; text is #VALUE! and errors propagate — matching ISEVEN/ISODD.
fn parity(v: &CalcValue) -> Result<bool, ast::Error> {
    let n = match v {
        CalcValue::Scalar(s) => match s {
            Value::Number(n) => *n,
            Value::Blank => 0.,
            Value::Boolean(b) => {
                if *b {
                    1.
                } else {
                    0.
                }
            }
            Value::Text(_) => return Err(ast::Error::Value),
            Value::Error(e) => return Err(e.clone()),
        },
        _ => return Err(ast::Error::Value),
    };
    Ok((n.trunc() as i64) % 2 == 0)
}

fn calc<C, F>(args: Vec<CalcVertex>, fetcher: &mut C, f: &F) -> CalcVertex
where
    C: Connector,
    F: Fn(&CalcValue) -> Result<bool, ast::Error>,
{
    assert_or_return!(args.len() == 1, ast::Error::Unspecified);
    let first = fetcher.get_calc_value(args.into_iter().next().unwrap());
    match f(&first) {
        Ok(b) => CalcVertex::from_bool(b),
        Err(e) => CalcVertex::from_error(e),
    }
}