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, Value},
    connector::Connector,
};
use logisheets_parser::ast;

fn calc<C, F>(args: Vec<CalcVertex>, fetcher: &mut C, func: F) -> CalcVertex
where
    C: Connector,
    F: 'static + Fn(f64) -> f64,
{
    if args.len() != 1 {
        return CalcVertex::from_error(ast::Error::Unspecified);
    }
    let arg = args.into_iter().next().unwrap();
    let value = fetcher.get_calc_value(arg);
    let v = match value {
        CalcValue::Scalar(s) => CalcValue::Scalar(call(&s, &func)),
        CalcValue::Range(r) => {
            let vec2d = r.map(move |v| call(v, &func));
            CalcValue::Range(vec2d)
        }
        CalcValue::Cube(_) => CalcValue::Scalar(Value::Error(ast::Error::Ref)),
        CalcValue::Union(u) => {
            if u.len() == 1 {
                let arg = *u.into_iter().next().unwrap();
                let v = vec![CalcVertex::Value(arg)];
                if let CalcVertex::Value(value) = calc(v, fetcher, func) {
                    value
                } else {
                    CalcValue::Scalar(Value::Error(ast::Error::Value))
                }
            } else {
                CalcValue::Scalar(Value::Error(ast::Error::Value))
            }
        }
    };
    CalcVertex::Value(v)
}

fn call<F>(value: &Value, func: &F) -> Value
where
    F: Fn(f64) -> f64,
{
    match value {
        Value::Blank => Value::Number(func(0_f64)),
        Value::Number(n) => Value::Number(func(n.clone())),
        Value::Text(_) => Value::Error(ast::Error::Value),
        Value::Boolean(b) => {
            if *b {
                Value::Number(func(1_f64))
            } else {
                Value::Number(func(0_f64))
            }
        }
        Value::Error(e) => Value::Error(e.clone()),
    }
}

/// Like [`calc`], but the per-element function may fail (e.g. SQRT of a
/// negative), producing a cell error instead of a NaN number.
fn calc_checked<C, F>(args: Vec<CalcVertex>, fetcher: &mut C, func: F) -> CalcVertex
where
    C: Connector,
    F: 'static + Fn(f64) -> Result<f64, ast::Error>,
{
    if args.len() != 1 {
        return CalcVertex::from_error(ast::Error::Unspecified);
    }
    let arg = args.into_iter().next().unwrap();
    let value = fetcher.get_calc_value(arg);
    let v = match value {
        CalcValue::Scalar(s) => CalcValue::Scalar(call_checked(&s, &func)),
        CalcValue::Range(r) => CalcValue::Range(r.map(move |v| call_checked(v, &func))),
        CalcValue::Cube(_) => CalcValue::Scalar(Value::Error(ast::Error::Ref)),
        CalcValue::Union(u) => {
            if u.len() == 1 {
                let arg = *u.into_iter().next().unwrap();
                let v = vec![CalcVertex::Value(arg)];
                if let CalcVertex::Value(value) = calc_checked(v, fetcher, func) {
                    value
                } else {
                    CalcValue::Scalar(Value::Error(ast::Error::Value))
                }
            } else {
                CalcValue::Scalar(Value::Error(ast::Error::Value))
            }
        }
    };
    CalcVertex::Value(v)
}

fn call_checked<F>(value: &Value, func: &F) -> Value
where
    F: Fn(f64) -> Result<f64, ast::Error>,
{
    let apply = |x: f64| match func(x) {
        Ok(n) => Value::Number(n),
        Err(e) => Value::Error(e),
    };
    match value {
        Value::Blank => apply(0_f64),
        Value::Number(n) => apply(n.clone()),
        Value::Text(_) => Value::Error(ast::Error::Value),
        Value::Boolean(b) => apply(if *b { 1_f64 } else { 0_f64 }),
        Value::Error(e) => Value::Error(e.clone()),
    }
}

pub fn calc_abs<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    calc(args, fetcher, |a| a.abs())
}

pub fn calc_sin<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    calc(args, fetcher, |a| a.sin())
}

pub fn calc_csc<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    calc(args, fetcher, |a| 1. / a.sin())
}

pub fn calc_asin<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    calc(args, fetcher, |a| a.asin())
}

pub fn calc_asinh<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    calc(args, fetcher, |a| a.asinh())
}

pub fn calc_cos<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    calc(args, fetcher, |a| a.cos())
}

pub fn calc_tan<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    calc(args, fetcher, |a| a.tan())
}

pub fn calc_cot<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    calc(args, fetcher, |a| 1. / a.tan())
}

pub fn calc_tanh<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    calc(args, fetcher, |a| a.tanh())
}

pub fn calc_coth<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    calc(args, fetcher, |a| 1. / a.tanh())
}

pub fn calc_sinh<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    calc(args, fetcher, |a| a.sinh())
}

pub fn calc_cosh<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    calc(args, fetcher, |a| a.cosh())
}

pub fn calc_sqrt<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    calc_checked(args, fetcher, |a| {
        if a < 0. {
            Err(ast::Error::Num)
        } else {
            Ok(a.sqrt())
        }
    })
}

pub fn calc_sqrtpi<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    calc_checked(args, fetcher, |a| {
        if a < 0. {
            Err(ast::Error::Num)
        } else {
            Ok((std::f64::consts::PI * a).sqrt())
        }
    })
}

pub fn calc_acos<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    calc(args, fetcher, |a| a.acos())
}

pub fn calc_atan<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    calc(args, fetcher, |a| a.atan())
}

pub fn calc_atanh<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    calc(args, fetcher, |a| a.atanh())
}

pub fn calc_acosh<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    calc(args, fetcher, |a| a.acosh())
}

pub fn calc_ln<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    calc(args, fetcher, |a| a.ln())
}

/// INT(number) — round DOWN to the nearest integer (toward -infinity), so
/// INT(-2.5) is -3.
pub fn calc_int<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    calc(args, fetcher, |a| a.floor())
}

/// LOG(number, [base]) — logarithm of `number` in the given base (default 10).
/// `number` must be > 0 and `base` a valid logarithm base (> 0 and != 1),
/// otherwise #NUM!.
pub fn calc_log<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    assert_or_return!(args.len() == 1 || args.len() == 2, ast::Error::Unspecified);
    let mut args_iter = args.into_iter();
    let first = fetcher.get_calc_value(args_iter.next().unwrap());
    assert_f64_from_calc_value!(number, first);
    let base = if let Some(arg) = args_iter.next() {
        let v = fetcher.get_calc_value(arg);
        assert_f64_from_calc_value!(b, v);
        b
    } else {
        10.
    };
    assert_or_return!(number > 0., ast::Error::Num);
    assert_or_return!(base > 0. && base != 1., ast::Error::Num);
    CalcVertex::from_number(number.ln() / base.ln())
}

pub fn calc_log10<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    calc(args, fetcher, |a| a.log10())
}

pub fn calc_exp<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    calc(args, fetcher, |a| a.exp())
}

pub fn calc_factdouble<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    use crate::calc_engine::calculator::math::fact::factdouble;
    calc(args, fetcher, |a| factdouble(a.floor() as u64) as f64)
}

pub fn calc_odd<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    let func = |n: f64| -> f64 {
        let pos = n >= 0.;
        let c = n.abs().ceil() as u32;
        let u_c = if c % 2 == 0 { c + 1 } else { c };
        if pos { u_c as f64 } else { -(u_c as f64) }
    };
    calc(args, fetcher, func)
}

pub fn calc_even<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    let func = |n: f64| -> f64 {
        let pos = n >= 0.;
        let c = n.abs().ceil() as u32;
        let u_c = if c % 2 == 0 { c } else { c + 1 };
        if pos { u_c as f64 } else { -(u_c as f64) }
    };
    calc(args, fetcher, func)
}

pub fn calc_sign<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    let func = |n: f64| -> f64 {
        if n == 0. {
            0.
        } else if n > 0. {
            1.
        } else {
            -1.
        }
    };
    calc(args, fetcher, func)
}

pub fn calc_radians<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    let func = |n: f64| -> f64 { n * std::f64::consts::PI / 180. };
    calc(args, fetcher, func)
}

pub fn calc_degrees<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    let func = |n: f64| -> f64 { n * 180. / std::f64::consts::PI };
    calc(args, fetcher, func)
}

pub fn calc_gamma<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    let func = |n: f64| -> f64 { statrs::function::gamma::gamma(n) };
    calc(args, fetcher, func)
}

pub fn calc_normsdist<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    use statrs::distribution::{ContinuousCDF, Normal};
    let func = |x: f64| -> f64 {
        let n = Normal::new(0.0, 1.0).unwrap();
        n.cdf(x)
    };
    calc(args, fetcher, func)
}

pub fn calc_gammaln<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    let func = |n: f64| -> f64 {
        let g = statrs::function::gamma::gamma(n);
        g.ln()
    };
    calc(args, fetcher, func)
}