formualizer-eval 0.7.0

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

use crate::engine::{
    ChangeLog, Engine, EvalConfig, FormulaIngestBatch, FormulaIngestRecord, FormulaPlaneMode,
};
use crate::test_workbook::TestWorkbook;
use formualizer_common::LiteralValue;
use formualizer_parse::parser::parse;

const TARGET_ROW: u32 = 100;
const TARGET_COL: u32 = 2;
const EDITED_INPUT: f64 = 500.0;
const EXPECTED_TARGET: f64 = EDITED_INPUT * 2.0;

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)))
}

pub(super) fn build_engine_with_active_spans() -> Engine<TestWorkbook> {
    let cfg =
        EvalConfig::default().with_formula_plane_mode(FormulaPlaneMode::AuthoritativeExperimental);
    let mut engine = Engine::new(TestWorkbook::default(), cfg);
    let mut formulas = Vec::new();
    for row in 1..=200 {
        engine
            .set_cell_value("Sheet1", row, 1, LiteralValue::Number(row as f64))
            .unwrap();
        formulas.push(formula_record(
            &mut engine,
            row,
            TARGET_COL,
            &format!("=A{row}*2"),
        ));
    }
    engine
        .ingest_formula_batches(vec![FormulaIngestBatch::new("Sheet1", formulas)])
        .unwrap();
    engine.evaluate_all().unwrap();
    engine
        .set_cell_value("Sheet1", TARGET_ROW, 1, LiteralValue::Number(EDITED_INPUT))
        .unwrap();
    engine
}

fn assert_active_spans(engine: &Engine<TestWorkbook>) {
    assert!(engine.graph.formula_authority().active_span_count() > 0);
}

fn assert_target_fresh(engine: &Engine<TestWorkbook>) {
    assert_eq!(
        engine.get_cell_value("Sheet1", TARGET_ROW, TARGET_COL),
        Some(LiteralValue::Number(EXPECTED_TARGET))
    );
}

#[test]
fn evaluate_all_flushes_active_spans() {
    let mut engine = build_engine_with_active_spans();
    assert_active_spans(&engine);

    engine.evaluate_all().unwrap();

    assert_target_fresh(&engine);
}

#[test]
fn evaluate_all_with_delta_flushes_active_spans() {
    let mut engine = build_engine_with_active_spans();
    assert_active_spans(&engine);

    engine.evaluate_all_with_delta().unwrap();

    assert_target_fresh(&engine);
}

#[test]
fn evaluate_all_cancellable_flushes_active_spans() {
    let mut engine = build_engine_with_active_spans();
    assert_active_spans(&engine);

    engine
        .evaluate_all_cancellable(Arc::new(AtomicBool::new(false)))
        .unwrap();

    assert_target_fresh(&engine);
}

#[test]
fn evaluate_all_logged_flushes_active_spans() {
    let mut engine = build_engine_with_active_spans();
    let mut log = ChangeLog::new();
    assert_active_spans(&engine);

    engine.evaluate_all_logged(&mut log).unwrap();

    assert_target_fresh(&engine);
}

#[test]
fn evaluate_cell_flushes_active_spans() {
    let mut engine = build_engine_with_active_spans();
    assert_active_spans(&engine);

    let value = engine
        .evaluate_cell("Sheet1", TARGET_ROW, TARGET_COL)
        .unwrap();

    assert_eq!(value, Some(LiteralValue::Number(EXPECTED_TARGET)));
    assert_target_fresh(&engine);
}

#[test]
fn evaluate_cells_flushes_active_spans() {
    let mut engine = build_engine_with_active_spans();
    assert_active_spans(&engine);

    let values = engine
        .evaluate_cells(&[("Sheet1", TARGET_ROW, TARGET_COL)])
        .unwrap();

    assert_eq!(values, vec![Some(LiteralValue::Number(EXPECTED_TARGET))]);
    assert_target_fresh(&engine);
}

#[test]
fn evaluate_cells_cancellable_flushes_active_spans() {
    let mut engine = build_engine_with_active_spans();
    assert_active_spans(&engine);

    let values = engine
        .evaluate_cells_cancellable(
            &[("Sheet1", TARGET_ROW, TARGET_COL)],
            Arc::new(AtomicBool::new(false)),
        )
        .unwrap();

    assert_eq!(values, vec![Some(LiteralValue::Number(EXPECTED_TARGET))]);
    assert_target_fresh(&engine);
}

#[test]
fn evaluate_cells_with_delta_flushes_active_spans() {
    let mut engine = build_engine_with_active_spans();
    assert_active_spans(&engine);

    let (values, _) = engine
        .evaluate_cells_with_delta(&[("Sheet1", TARGET_ROW, TARGET_COL)])
        .unwrap();

    assert_eq!(values, vec![Some(LiteralValue::Number(EXPECTED_TARGET))]);
    assert_target_fresh(&engine);
}

#[test]
fn evaluate_until_flushes_active_spans() {
    let mut engine = build_engine_with_active_spans();
    assert_active_spans(&engine);

    engine
        .evaluate_until(&[("Sheet1", TARGET_ROW, TARGET_COL)])
        .unwrap();

    assert_target_fresh(&engine);
}

#[test]
fn evaluate_until_cancellable_flushes_active_spans() {
    let mut engine = build_engine_with_active_spans();
    assert_active_spans(&engine);

    engine
        .evaluate_until_cancellable(&["Sheet1!B100"], Arc::new(AtomicBool::new(false)))
        .unwrap();

    assert_target_fresh(&engine);
}

#[test]
fn evaluate_recalc_plan_flushes_active_spans() {
    let mut engine = build_engine_with_active_spans();
    let plan = engine.build_recalc_plan().unwrap();
    assert_active_spans(&engine);

    engine.evaluate_recalc_plan(&plan).unwrap();

    assert_target_fresh(&engine);
}

#[test]
fn evaluate_vertex_flushes_active_spans() {
    let mut engine = build_engine_with_active_spans();
    let input_vertex = *engine
        .graph
        .get_vertex_id_for_address(&engine.graph.make_cell_ref("Sheet1", TARGET_ROW, 1))
        .expect("input vertex");
    assert_active_spans(&engine);

    let value = engine.evaluate_vertex(input_vertex).unwrap();

    assert_eq!(value, LiteralValue::Number(EDITED_INPUT));
    assert_target_fresh(&engine);
}