formualizer-eval 0.6.0

High-performance Arrow-backed Excel formula engine with dependency graph and incremental recalculation
Documentation
use std::sync::Arc;

use crate::engine::graph::editor::undo_engine::UndoEngine;
use crate::engine::{
    EditorError, Engine, EvalConfig, FormulaIngestBatch, FormulaIngestRecord, FormulaPlaneMode,
};
use crate::test_workbook::TestWorkbook;
use formualizer_common::LiteralValue;
use formualizer_parse::parser::parse;

fn authoritative_engine() -> Engine<TestWorkbook> {
    let cfg =
        EvalConfig::default().with_formula_plane_mode(FormulaPlaneMode::AuthoritativeExperimental);
    Engine::new(TestWorkbook::default(), cfg)
}

fn formula_record(
    engine: &mut Engine<TestWorkbook>,
    row: u32,
    col: u32,
    formula: &str,
) -> FormulaIngestRecord {
    let ast = parse(formula).unwrap();
    let ast_id = engine.intern_formula_ast(&ast);
    FormulaIngestRecord::new(row, col, ast_id, Some(Arc::<str>::from(formula)))
}

fn build_single_span_engine(rows: u32) -> Engine<TestWorkbook> {
    let mut engine = authoritative_engine();
    let mut formulas = Vec::new();
    for row in 1..=rows {
        engine
            .set_cell_value("Sheet1", row, 1, LiteralValue::Number(row as f64))
            .unwrap();
        formulas.push(formula_record(&mut engine, row, 2, &format!("=A{row}*2")));
    }
    engine
        .ingest_formula_batches(vec![FormulaIngestBatch::new("Sheet1", formulas)])
        .unwrap();
    assert_eq!(engine.baseline_stats().formula_plane_active_span_count, 1);
    engine.evaluate_all().unwrap();
    engine
}

fn build_two_span_engine(rows: u32) -> Engine<TestWorkbook> {
    let mut engine = authoritative_engine();
    let mut formulas = Vec::new();
    for row in 1..=rows {
        engine
            .set_cell_value("Sheet1", row, 1, LiteralValue::Number(row as f64))
            .unwrap();
        formulas.push(formula_record(&mut engine, row, 2, &format!("=A{row}*2")));
        formulas.push(formula_record(&mut engine, row, 3, &format!("=B{row}+1")));
    }
    engine
        .ingest_formula_batches(vec![FormulaIngestBatch::new("Sheet1", formulas)])
        .unwrap();
    assert_eq!(engine.baseline_stats().formula_plane_active_span_count, 2);
    engine.evaluate_all().unwrap();
    engine
}

fn assert_number_cell(engine: &Engine<TestWorkbook>, row: u32, col: u32, expected: f64) {
    match engine.get_cell_value("Sheet1", row, col) {
        Some(LiteralValue::Number(actual)) => assert_eq!(actual, expected),
        Some(LiteralValue::Int(actual)) => assert_eq!(actual as f64, expected),
        other => panic!("expected Sheet1!R{row}C{col} to be {expected}, got {other:?}"),
    }
}

fn edit_first_fifty_values(
    tx: &mut crate::engine::EngineAction<'_, TestWorkbook>,
) -> Result<(), EditorError> {
    for row in 1..=50 {
        tx.set_cell_value("Sheet1", row, 1, LiteralValue::Number((row * 10) as f64))?;
    }
    Ok(())
}

#[test]
fn action_atomic_value_edits_use_dirty_closure_not_whole_all() {
    let mut engine = build_single_span_engine(1_000);
    let epoch = engine.formula_plane_indexes_epoch();

    engine
        .action_atomic_journal("bulk values".to_string(), edit_first_fifty_values)
        .unwrap();
    assert_eq!(engine.formula_plane_indexes_epoch(), epoch);

    engine.evaluate_all().unwrap();
    assert_eq!(engine.formula_plane_indexes_epoch(), epoch);
    let report = engine.last_formula_plane_span_eval_report().unwrap();
    assert_eq!(report.span_eval_placement_count, 50, "{report:?}");
}

#[test]
fn undo_redo_of_value_bulk_uses_dirty_closure_not_whole_all() {
    let mut engine = build_single_span_engine(1_000);
    let mut undo = UndoEngine::new();

    let (_ret, journal) = engine
        .action_atomic_journal("bulk values".to_string(), edit_first_fifty_values)
        .unwrap();
    undo.push_action(journal);
    engine.evaluate_all().unwrap();
    assert_eq!(
        engine
            .last_formula_plane_span_eval_report()
            .unwrap()
            .span_eval_placement_count,
        50
    );

    engine.undo_action(&mut undo).unwrap();
    engine.evaluate_all().unwrap();
    assert_eq!(
        engine
            .last_formula_plane_span_eval_report()
            .unwrap()
            .span_eval_placement_count,
        50
    );

    engine.redo_action(&mut undo).unwrap();
    engine.evaluate_all().unwrap();
    assert_eq!(
        engine
            .last_formula_plane_span_eval_report()
            .unwrap()
            .span_eval_placement_count,
        50
    );
}

#[test]
fn per_cell_formula_write_demotion_dirties_only_true_closure() {
    let mut engine = build_two_span_engine(200);

    engine
        .action_atomic_journal("demote precise".to_string(), |tx| {
            tx.set_cell_value("Sheet1", 1, 1, LiteralValue::Number(1000.0))?;
            tx.set_cell_formula("Sheet1", 100, 2, parse("=A100*5").unwrap())?;
            tx.set_cell_value("Sheet1", 200, 1, LiteralValue::Number(2000.0))?;
            Ok(())
        })
        .unwrap();

    let result = engine.evaluate_all().unwrap();
    assert_eq!(engine.baseline_stats().formula_plane_active_span_count, 0);
    assert_eq!(result.computed_vertices, 6);
    assert_number_cell(&engine, 1, 2, 2000.0);
    assert_number_cell(&engine, 1, 3, 2001.0);
    assert_number_cell(&engine, 100, 2, 500.0);
    assert_number_cell(&engine, 100, 3, 501.0);
    assert_number_cell(&engine, 200, 2, 4000.0);
    assert_number_cell(&engine, 200, 3, 4001.0);
}

#[test]
fn per_cell_formula_write_demotion_correct_after_undo() {
    let mut engine = build_two_span_engine(200);
    let mut undo = UndoEngine::new();

    let (_ret, journal) = engine
        .action_atomic_journal("demote precise".to_string(), |tx| {
            tx.set_cell_value("Sheet1", 1, 1, LiteralValue::Number(1000.0))?;
            tx.set_cell_formula("Sheet1", 100, 2, parse("=A100*5").unwrap())?;
            tx.set_cell_value("Sheet1", 200, 1, LiteralValue::Number(2000.0))?;
            Ok(())
        })
        .unwrap();
    undo.push_action(journal);
    engine.evaluate_all().unwrap();

    engine.undo_action(&mut undo).unwrap();
    let result = engine.evaluate_all().unwrap();
    assert!(
        result.computed_vertices <= 6,
        "computed_vertices={}",
        result.computed_vertices
    );
    assert_number_cell(&engine, 1, 1, 1.0);
    assert_number_cell(&engine, 1, 2, 2.0);
    assert_number_cell(&engine, 1, 3, 3.0);
    assert_number_cell(&engine, 100, 2, 200.0);
    assert_number_cell(&engine, 100, 3, 201.0);
    assert_number_cell(&engine, 200, 1, 200.0);
    assert_number_cell(&engine, 200, 2, 400.0);
    assert_number_cell(&engine, 200, 3, 401.0);
}