formualizer-eval 0.14.0

High-performance Arrow-backed Excel formula engine with dependency graph and incremental recalculation
Documentation
use crate::engine::{Engine, EvalConfig, EvaluationTarget};
use crate::test_workbook::TestWorkbook;
use formualizer_common::LiteralValue;
use formualizer_parse::parser::parse;

fn engine(family: bool) -> Engine<TestWorkbook> {
    crate::builtins::load_builtins();
    Engine::new(
        TestWorkbook::new(),
        EvalConfig {
            family_execution: family,
            enable_parallel: false,
            ..Default::default()
        },
    )
}
fn formula(e: &mut Engine<TestWorkbook>, row: u32, col: u32, src: &str) {
    e.set_cell_formula("Sheet1", row, col, parse(src).unwrap())
        .unwrap();
}
fn future_spill(family: bool) -> Engine<TestWorkbook> {
    let mut e = engine(family);
    formula(&mut e, 1, 10, "=1");
    formula(&mut e, 2, 10, "=J1+1");
    formula(&mut e, 3, 10, "=J2+1");
    formula(&mut e, 1, 2, "=IF(J3>0,SEQUENCE(5),0)");
    formula(&mut e, 1, 4, "=INDIRECT(\"B5\")*2");
    formula(&mut e, 2, 4, "=D1+1");
    e
}

#[test]
fn future_spill_dirties_indirect_and_static_descendant() {
    for family in [false, true] {
        for api in 0..4 {
            let mut e = future_spill(family);
            match api {
                0 => {
                    e.evaluate_all().unwrap();
                }
                1 => {
                    e.evaluate_cell("Sheet1", 2, 4).unwrap();
                }
                2 => {
                    e.evaluate_targets(&[EvaluationTarget::Cell {
                        sheet: "Sheet1".into(),
                        row: 2,
                        col: 4,
                    }])
                    .unwrap();
                }
                _ => {
                    e.evaluate_all_for_snapshot(None).unwrap();
                }
            }
            assert_eq!(
                e.get_cell_value("Sheet1", 1, 4),
                Some(LiteralValue::Number(10.)),
                "family={family} api={api}"
            );
            assert_eq!(
                e.get_cell_value("Sheet1", 2, 4),
                Some(LiteralValue::Number(11.)),
                "family={family} api={api}"
            );
        }
    }
}

#[test]
fn retarget_and_formula_replacement_remove_observed_routes() {
    for family in [false, true] {
        let mut e = engine(family);
        e.set_cell_value("Sheet1", 1, 1, LiteralValue::Number(11.))
            .unwrap();
        e.set_cell_value("Sheet1", 2, 1, LiteralValue::Number(22.))
            .unwrap();
        e.set_cell_value("Sheet1", 1, 8, LiteralValue::Text("A1".into()))
            .unwrap();
        formula(&mut e, 1, 4, "=INDIRECT(H1)");
        e.evaluate_all().unwrap();
        let reader = e
            .graph
            .get_vertex_id_for_address(&e.graph.make_cell_ref("Sheet1", 1, 4))
            .unwrap();
        let sheet = e.graph.sheet_id("Sheet1").unwrap();
        let routed = |e: &Engine<TestWorkbook>, row| {
            e.graph
                .authority_closure_of_rects(&[(
                    sheet,
                    crate::engine::authority::geom::Rect::cell(row, 0),
                )])
                .contains(&reader)
        };
        assert!(routed(&e, 0));
        e.set_cell_value("Sheet1", 1, 8, LiteralValue::Text("A2".into()))
            .unwrap();
        e.evaluate_all().unwrap();
        assert!(!routed(&e, 0));
        assert!(routed(&e, 1));
        assert_eq!(
            e.get_cell_value("Sheet1", 1, 4),
            Some(LiteralValue::Number(22.))
        );
        formula(&mut e, 1, 4, "=7");
        e.evaluate_all().unwrap();
        assert!(!routed(&e, 1));
    }
}

#[test]
fn empty_grid_read_receipts_keep_future_coordinates() {
    use crate::engine::live_edges::{ReadLog, RecordingContext};
    use crate::traits::EvaluationContext;
    let mut e = engine(false);
    formula(&mut e, 1, 10, "=1");
    assert_eq!(e.get_cell_value("Sheet1", 5, 2), None);
    let sheet = e.graph.sheet_id("Sheet1").unwrap();
    for (reference, expected) in [("B5", (sheet, 4, 1, 4, 1)), ("B5:B8", (sheet, 4, 1, 7, 1))] {
        let log = ReadLog::default();
        let ctx = RecordingContext::new(&e, &log);
        let reference = formualizer_parse::parser::ReferenceType::from_string(reference).unwrap();
        ctx.resolve_range_view(&reference, "Sheet1").unwrap();
        assert_eq!(log.take(), vec![expected]);
    }
}

#[test]
fn observed_routes_retarget_invalidate_and_clear() {
    use crate::engine::authority::{geom::Rect, host::AuthorityHost};
    let mut host = AuthorityHost::default();
    let reader = crate::engine::VertexId(123);
    let old = [(0, 4, 1, 4, 1)];
    let new = [(1, 6, 2, 8, 2)];
    let query = |host: &AuthorityHost, sheet, rect| {
        let mut out = Vec::new();
        host.observed_readers(sheet, &rect, &mut out);
        out
    };
    host.set_observed(reader, old.to_vec());
    assert_eq!(query(&host, 0, Rect::cell(4, 1)), vec![reader]);
    let revision = host.rev_dyn();
    host.set_observed_with_value(reader, old.to_vec(), false);
    assert_eq!(host.rev_dyn(), revision);
    assert_eq!(
        query(&host, 0, Rect::cell(4, 1)),
        vec![reader],
        "routing survives value invalidation"
    );
    host.set_observed(reader, new.to_vec());
    assert!(query(&host, 0, Rect::cell(4, 1)).is_empty());
    assert_eq!(query(&host, 1, Rect::cell(7, 2)), vec![reader]);
    host.forget_observed(reader);
    assert!(!host.has_observed_routes());
    assert!(query(&host, 1, Rect::cell(7, 2)).is_empty());
    host.set_observed(reader, old.to_vec());
    host.clear_observed();
    assert!(!host.has_observed_routes());
}