formualizer-eval 0.14.0

High-performance Arrow-backed Excel formula engine with dependency graph and incremental recalculation
Documentation
use super::*;
use crate::engine::named_range::{NameScope, NamedDefinition};

fn make_tiny(family: bool, names: bool, tail: bool) -> Engine<TestWorkbook> {
    let mut e = engine(family);
    f(&mut e, 1, 10, "=1");
    f(&mut e, 2, 10, "=J1+1");
    f(&mut e, 3, 10, "=J2+1");
    f(&mut e, 1, 2, "=IF(J3>0,SEQUENCE(5),0)");
    if names {
        e.define_name(
            "SpillCell",
            NamedDefinition::Cell(e.graph.make_cell_ref("Sheet1", 5, 2)),
            NameScope::Workbook,
        )
        .unwrap();
        e.define_name(
            "SpillValues",
            NamedDefinition::Formula {
                ast: parse("=B1#").unwrap(),
                dependencies: vec![],
                range_deps: vec![],
            },
            NameScope::Workbook,
        )
        .unwrap();
    }
    for col in 20..40 {
        f(&mut e, 1, col, &format!("={col}"));
    }
    f(&mut e, 1, 4, "=INDIRECT(\"B5\")*2");
    if tail {
        f(&mut e, 2, 4, "=D1+1");
    }
    e
}
fn tiny_cases(names: bool) {
    let mut failures = Vec::new();
    for family in [false, true] {
        for job in [false, true] {
            for tail in [false, true] {
                let mut e = make_tiny(family, names, tail);
                let mut fresh = make_tiny(family, names, tail);
                fresh.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 mut installed_empty = false;
                let mut covered_at_install = false;
                let mut dirty_at_publish = false;
                let mut complete = false;
                for _ in 0..100 {
                    e.resumable_stop_after_units_for_test = Some(1);
                    let before_spill = v(&e, 1, 2);
                    let result = test_recalc_slice(&mut e, EvaluationStop::none(), job).unwrap();
                    if !installed_empty
                        && v(&e, 1, 2).is_none()
                        && v(&e, 1, 4) == Some(LiteralValue::Number(0.))
                        && !e.graph.is_dirty(reader)
                    {
                        installed_empty = true;
                        let observed = e
                            .graph
                            .authority_host()
                            .observed(reader)
                            .map(|r| r.to_vec());
                        println!(
                            "OBS names={names} family={family} job={job} dynamic={} observed={observed:?}",
                            e.graph.is_dynamic(reader)
                        );
                        assert_eq!(observed.as_deref(), Some([(sheet, 4, 1, 4, 1)].as_slice()));
                        e.graph.authority_sync();
                        covered_at_install = e
                            .graph
                            .authority_closure_of_rects(&[(
                                sheet,
                                crate::engine::authority::geom::Rect::new(4, 1, 4, 1),
                            )])
                            .contains(&reader);
                    }
                    if before_spill.is_none() && v(&e, 1, 2).is_some() {
                        dirty_at_publish = e.graph.is_dirty(reader);
                    }
                    if matches!(result, EvaluationProgress::Complete(_)) {
                        complete = true;
                        break;
                    }
                }
                assert!(complete && installed_empty);
                let actual = v(&e, 1, 4);
                let control = v(&fresh, 1, 4);
                println!(
                    "EMPTY_SPILL names={names} family={family} job={job} tail={tail} actual={actual:?} ordinary={control:?} covered={covered_at_install} dirty_at_publish={dirty_at_publish}"
                );
                if actual != Some(LiteralValue::Number(10.))
                    || control != actual
                    || !covered_at_install
                    || !dirty_at_publish
                    || (tail
                        && (v(&e, 2, 4) != Some(LiteralValue::Number(11.))
                            || v(&fresh, 2, 4) != v(&e, 2, 4)))
                {
                    failures.push((family, job, tail));
                }
            }
        }
    }
    assert!(failures.is_empty(), "{failures:?}");
}
#[test]
fn named_future_spill_indirect_matches_ordinary() {
    tiny_cases(true);
}
#[test]
fn unnamed_future_spill_indirect_matches_ordinary() {
    tiny_cases(false);
}

#[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);
    f(&mut e, 1, 10, "=1");
    assert_eq!(v(&e, 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());
}

fn make_sweep(family: bool, mask: usize, depth: u32) -> Engine<TestWorkbook> {
    let mut e = engine(family);
    f(&mut e, 1, 10, "=1");
    for row in 2..=depth {
        f(&mut e, row, 10, &format!("=J{}+1", row - 1));
    }
    f(&mut e, 1, 2, &format!("=IF(J{depth}>0,SEQUENCE(5),0)"));
    e.define_name(
        "SpillCell",
        NamedDefinition::Cell(e.graph.make_cell_ref("Sheet1", 5, 2)),
        NameScope::Workbook,
    )
    .unwrap();
    e.define_name(
        "SpillValues",
        NamedDefinition::Formula {
            ast: parse("=B1#").unwrap(),
            dependencies: vec![],
            range_deps: vec![],
        },
        NameScope::Workbook,
    )
    .unwrap();
    let readers = [
        "=B5*2",
        "=INDIRECT(\"B5\")*2",
        "=OFFSET(B1,4,0)*2",
        "=SUM(B1#)",
        "=SpillCell*2",
        "=SUM(SpillValues)",
        "=SUM(INDIRECT(\"B5:B8\"))",
    ];
    for (i, s) in readers.iter().enumerate() {
        if mask & (1 << i) != 0 {
            f(&mut e, 1, 3 + i as u32, s);
            f(
                &mut e,
                2,
                3 + i as u32,
                &format!("={}1+1", (b'C' + i as u8) as char),
            );
        }
    }
    e
}
#[test]
fn future_spill_dynamic_reader_96_configuration_sweep() {
    let mut failures = Vec::new();
    for family in [false, true] {
        for job in [false, true] {
            for mask in [1, 2, 4, 8, 16, 32, 3, 6, 10, 18, 34, 127] {
                for depth in [3, 40] {
                    let mut e = make_sweep(family, mask, depth);
                    let mut fresh = make_sweep(family, mask, depth);
                    let mut complete = false;
                    for _ in 0..300 {
                        e.resumable_stop_after_units_for_test = Some(1);
                        if matches!(
                            test_recalc_slice(&mut e, EvaluationStop::none(), job).unwrap(),
                            EvaluationProgress::Complete(_)
                        ) {
                            complete = true;
                            break;
                        }
                    }
                    assert!(complete);
                    fresh.evaluate_all().unwrap();
                    let mut bad = Vec::new();
                    for col in 3..10 {
                        for row in 1..=2 {
                            if v(&e, row, col) != v(&fresh, row, col) {
                                bad.push((row, col, v(&e, row, col), v(&fresh, row, col)));
                            }
                        }
                    }
                    if !bad.is_empty() {
                        println!(
                            "SWEEP family={family} job={job} mask={mask} depth={depth} bad={bad:?}"
                        );
                        failures.push((family, job, mask, depth));
                    }
                }
            }
        }
    }
    println!("SWEEP failures={}/96", failures.len());
    assert!(failures.is_empty(), "{failures:?}");
}