use crate::engine::named_range::{NameScope, NamedDefinition};
use crate::engine::{Engine, EvalConfig};
use crate::reference::{CellRef, Coord, RangeRef};
use crate::traits::{
EvaluationContext, FunctionProvider, NamedRangeResolver, Range, RangeResolver,
ReferenceResolver, Resolver, SourceResolver, Table, TableResolver,
};
use formualizer_common::LiteralValue;
use formualizer_common::error::{ExcelError, ExcelErrorKind};
use formualizer_parse::parser::TableReference;
use std::collections::HashMap;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, RwLock};
#[derive(Debug, Clone)]
struct MemTable {
headers: Vec<String>,
data: Vec<Vec<LiteralValue>>,
}
impl Table for MemTable {
fn get_cell(&self, row: usize, column: &str) -> Result<LiteralValue, ExcelError> {
let col_idx = self
.headers
.iter()
.position(|h| h.eq_ignore_ascii_case(column))
.ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
self.data
.get(row)
.and_then(|r| r.get(col_idx))
.cloned()
.ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
}
fn get_column(&self, column: &str) -> Result<Box<dyn Range>, ExcelError> {
let col_idx = self
.headers
.iter()
.position(|h| h.eq_ignore_ascii_case(column))
.ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
let mut col: Vec<Vec<LiteralValue>> = Vec::with_capacity(self.data.len());
for r in &self.data {
col.push(vec![r.get(col_idx).cloned().unwrap_or(LiteralValue::Empty)]);
}
Ok(Box::new(crate::traits::InMemoryRange::new(col)))
}
fn columns(&self) -> Vec<String> {
self.headers.clone()
}
fn data_height(&self) -> usize {
self.data.len()
}
fn has_headers(&self) -> bool {
true
}
fn has_totals(&self) -> bool {
false
}
fn headers_row(&self) -> Option<Box<dyn Range>> {
Some(Box::new(crate::traits::InMemoryRange::new(vec![
self.headers
.iter()
.cloned()
.map(LiteralValue::Text)
.collect(),
])))
}
fn data_body(&self) -> Option<Box<dyn Range>> {
Some(Box::new(crate::traits::InMemoryRange::new(
self.data.clone(),
)))
}
fn clone_box(&self) -> Box<dyn Table> {
Box::new(self.clone())
}
}
#[derive(Clone, Default)]
struct SourceCtx {
scalars: Arc<RwLock<HashMap<String, LiteralValue>>>,
tables: Arc<RwLock<HashMap<String, Arc<dyn Table>>>>,
scalar_calls: Arc<AtomicUsize>,
table_calls: Arc<AtomicUsize>,
}
impl SourceCtx {
fn set_scalar(&self, name: &str, v: LiteralValue) {
self.scalars.write().unwrap().insert(name.to_string(), v);
}
fn set_table(&self, name: &str, t: Arc<dyn Table>) {
self.tables.write().unwrap().insert(name.to_string(), t);
}
fn scalar_calls(&self) -> usize {
self.scalar_calls.load(Ordering::Relaxed)
}
fn table_calls(&self) -> usize {
self.table_calls.load(Ordering::Relaxed)
}
}
impl ReferenceResolver for SourceCtx {
fn resolve_cell_reference(
&self,
_sheet: Option<&str>,
_row: u32,
_col: u32,
) -> Result<LiteralValue, ExcelError> {
Err(ExcelError::new(ExcelErrorKind::NImpl))
}
}
impl RangeResolver for SourceCtx {
fn resolve_range_reference(
&self,
_sheet: Option<&str>,
_sr: Option<u32>,
_sc: Option<u32>,
_er: Option<u32>,
_ec: Option<u32>,
) -> Result<Box<dyn Range>, ExcelError> {
Err(ExcelError::new(ExcelErrorKind::NImpl))
}
}
impl NamedRangeResolver for SourceCtx {
fn resolve_named_range_reference(
&self,
_name: &str,
) -> Result<Vec<Vec<LiteralValue>>, ExcelError> {
Err(ExcelError::new(ExcelErrorKind::Name))
}
}
impl TableResolver for SourceCtx {
fn resolve_table_reference(
&self,
_tref: &TableReference,
) -> Result<Box<dyn Table>, ExcelError> {
Err(ExcelError::new(ExcelErrorKind::NImpl))
}
}
impl SourceResolver for SourceCtx {
fn resolve_source_scalar(&self, name: &str) -> Result<LiteralValue, ExcelError> {
self.scalar_calls.fetch_add(1, Ordering::Relaxed);
self.scalars
.read()
.unwrap()
.get(name)
.cloned()
.ok_or_else(|| ExcelError::new(ExcelErrorKind::Name))
}
fn resolve_source_table(&self, name: &str) -> Result<Box<dyn Table>, ExcelError> {
self.table_calls.fetch_add(1, Ordering::Relaxed);
let t = self
.tables
.read()
.unwrap()
.get(name)
.cloned()
.ok_or_else(|| ExcelError::new(ExcelErrorKind::Name))?;
Ok(t.clone_box())
}
}
impl FunctionProvider for SourceCtx {
fn get_function(
&self,
ns: &str,
name: &str,
) -> Option<std::sync::Arc<dyn crate::function::Function>> {
crate::function_registry::get(ns, name)
}
}
impl Resolver for SourceCtx {}
impl EvaluationContext for SourceCtx {}
#[test]
fn undeclared_source_symbol_is_stored_and_evaluates_to_name_error() {
let ctx = SourceCtx::default();
let mut engine: Engine<_> = Engine::new(ctx, EvalConfig::default());
engine.add_sheet("Sheet1").unwrap();
let ast = formualizer_parse::parser::parse("=Foo").unwrap();
engine.set_cell_formula("Sheet1", 1, 1, ast).unwrap();
engine.evaluate_all().unwrap();
match engine.get_cell_value("Sheet1", 1, 1) {
Some(LiteralValue::Error(e)) => assert_eq!(e.kind, ExcelErrorKind::Name),
other => panic!("expected #NAME? for unresolved source-like symbol, got {other:?}"),
}
}
#[test]
fn defining_source_scalar_heals_pending_formula_symbol() {
let ctx = SourceCtx::default();
ctx.set_scalar("Foo", LiteralValue::Number(9.0));
let mut engine: Engine<_> = Engine::new(ctx.clone(), EvalConfig::default());
engine.add_sheet("Sheet1").unwrap();
engine
.set_cell_formula(
"Sheet1",
1,
1,
formualizer_parse::parser::parse("=Foo+1").unwrap(),
)
.unwrap();
engine.evaluate_all().unwrap();
match engine.get_cell_value("Sheet1", 1, 1) {
Some(LiteralValue::Error(e)) => assert_eq!(e.kind, ExcelErrorKind::Name),
other => panic!("expected pending symbol to start unresolved, got {other:?}"),
}
engine.define_source_scalar("Foo", Some(1)).unwrap();
engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 1, 1),
Some(LiteralValue::Number(10.0))
);
ctx.set_scalar("Foo", LiteralValue::Number(12.0));
engine.invalidate_source("Foo").unwrap();
engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 1, 1),
Some(LiteralValue::Number(13.0))
);
}
#[test]
fn staged_build_undeclared_reference_evaluates_to_name_error() {
let ctx = SourceCtx::default();
let mut engine: Engine<_> = Engine::new(ctx, EvalConfig::default());
engine.stage_formula_text("Sheet1", 1, 1, "Foo".to_string());
engine.build_graph_all().unwrap();
engine.evaluate_all().unwrap();
match engine.get_cell_value("Sheet1", 1, 1) {
Some(LiteralValue::Error(e)) => assert_eq!(e.kind, ExcelErrorKind::Name),
other => panic!("expected #NAME?, got {other:?}"),
}
}
#[test]
fn staged_build_declared_unresolved_source_yields_ref() {
let ctx = SourceCtx::default();
let mut engine: Engine<_> = Engine::new(ctx, EvalConfig::default());
engine.define_source_scalar("Foo", Some(1)).unwrap();
engine.stage_formula_text("Sheet1", 1, 1, "Foo".to_string());
engine.build_graph_all().unwrap();
engine.evaluate_all().unwrap();
match engine.get_cell_value("Sheet1", 1, 1) {
Some(LiteralValue::Error(e)) => assert_eq!(e.kind, ExcelErrorKind::Ref),
other => panic!("expected #REF!, got {other:?}"),
}
}
#[test]
fn declared_source_missing_is_ref_error() {
let ctx = SourceCtx::default();
let mut engine: Engine<_> = Engine::new(ctx, EvalConfig::default());
engine.add_sheet("Sheet1").unwrap();
engine.define_source_scalar("Foo", Some(1)).unwrap();
let ast = formualizer_parse::parser::parse("=Foo").unwrap();
engine.set_cell_formula("Sheet1", 1, 1, ast).unwrap();
match engine.evaluate_cell("Sheet1", 1, 1).unwrap() {
Some(LiteralValue::Error(e)) => assert_eq!(e.kind, ExcelErrorKind::Ref),
other => panic!("expected #REF!, got {other:?}"),
}
}
#[test]
fn declared_table_source_missing_is_ref_error() {
let ctx = SourceCtx::default();
let mut engine: Engine<_> = Engine::new(ctx, EvalConfig::default());
engine.add_sheet("Sheet1").unwrap();
engine.define_source_table("Sales", Some(1)).unwrap();
let ast = formualizer_parse::parser::parse("=SUM(Sales[Amount])").unwrap();
engine.set_cell_formula("Sheet1", 1, 1, ast).unwrap();
match engine.evaluate_cell("Sheet1", 1, 1).unwrap() {
Some(LiteralValue::Error(e)) => assert_eq!(e.kind, ExcelErrorKind::Ref),
other => panic!("expected #REF!, got {other:?}"),
}
}
#[test]
fn scalar_source_invalidates_dependents() {
let ctx = SourceCtx::default();
ctx.set_scalar("Foo", LiteralValue::Int(1));
let mut engine: Engine<_> = Engine::new(ctx.clone(), EvalConfig::default());
engine.add_sheet("Sheet1").unwrap();
engine.define_source_scalar("Foo", Some(1)).unwrap();
let ast = formualizer_parse::parser::parse("=Foo").unwrap();
engine.set_cell_formula("Sheet1", 1, 1, ast).unwrap();
let v1 = engine
.evaluate_cell("Sheet1", 1, 1)
.unwrap()
.expect("computed value");
assert_eq!(v1, LiteralValue::Number(1.0));
ctx.set_scalar("Foo", LiteralValue::Int(2));
engine.invalidate_source("Foo").unwrap();
let v2 = engine
.evaluate_cell("Sheet1", 1, 1)
.unwrap()
.expect("computed value");
assert_eq!(v2, LiteralValue::Number(2.0));
}
#[test]
fn scalar_source_invalidate_marks_multiple_dependents_dirty() {
let ctx = SourceCtx::default();
ctx.set_scalar("Foo", LiteralValue::Int(1));
let cfg = EvalConfig::default();
let mut engine: Engine<_> = Engine::new(ctx.clone(), cfg);
engine.add_sheet("Sheet1").unwrap();
engine.define_source_scalar("Foo", Some(1)).unwrap();
engine
.set_cell_formula(
"Sheet1",
1,
1,
formualizer_parse::parser::parse("=Foo").unwrap(),
)
.unwrap();
engine
.set_cell_formula(
"Sheet1",
2,
1,
formualizer_parse::parser::parse("=Foo+1").unwrap(),
)
.unwrap();
engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 1, 1),
Some(LiteralValue::Number(1.0))
);
assert_eq!(
engine.get_cell_value("Sheet1", 2, 1),
Some(LiteralValue::Number(2.0))
);
ctx.set_scalar("Foo", LiteralValue::Int(5));
engine.invalidate_source("Foo").unwrap();
engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 1, 1),
Some(LiteralValue::Number(5.0))
);
assert_eq!(
engine.get_cell_value("Sheet1", 2, 1),
Some(LiteralValue::Number(6.0))
);
}
#[test]
fn table_source_invalidate_marks_multiple_dependents_dirty() {
let ctx = SourceCtx::default();
let t1 = MemTable {
headers: vec!["Amount".into()],
data: vec![
vec![LiteralValue::Number(10.0)],
vec![LiteralValue::Number(20.0)],
],
};
ctx.set_table("Sales", Arc::new(t1));
let cfg = EvalConfig::default();
let mut engine: Engine<_> = Engine::new(ctx.clone(), cfg);
engine.add_sheet("Sheet1").unwrap();
engine.define_source_table("Sales", Some(1)).unwrap();
engine
.set_cell_formula(
"Sheet1",
1,
1,
formualizer_parse::parser::parse("=SUM(Sales[Amount])").unwrap(),
)
.unwrap();
engine
.set_cell_formula(
"Sheet1",
2,
1,
formualizer_parse::parser::parse("=SUM(Sales[Amount])+1").unwrap(),
)
.unwrap();
engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 1, 1),
Some(LiteralValue::Number(30.0))
);
assert_eq!(
engine.get_cell_value("Sheet1", 2, 1),
Some(LiteralValue::Number(31.0))
);
let t2 = MemTable {
headers: vec!["Amount".into()],
data: vec![
vec![LiteralValue::Number(1.0)],
vec![LiteralValue::Number(2.0)],
vec![LiteralValue::Number(3.0)],
],
};
ctx.set_table("Sales", Arc::new(t2));
engine.invalidate_source("Sales").unwrap();
engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 1, 1),
Some(LiteralValue::Number(6.0))
);
assert_eq!(
engine.get_cell_value("Sheet1", 2, 1),
Some(LiteralValue::Number(7.0))
);
}
#[test]
fn table_source_cached_within_evaluate_until() {
let ctx = SourceCtx::default();
let table = MemTable {
headers: vec!["Region".into(), "Amount".into()],
data: vec![
vec![LiteralValue::Text("N".into()), LiteralValue::Number(10.0)],
vec![LiteralValue::Text("S".into()), LiteralValue::Number(20.0)],
],
};
ctx.set_table("Sales", Arc::new(table));
let mut engine: Engine<_> = Engine::new(ctx.clone(), EvalConfig::default());
engine.add_sheet("Sheet1").unwrap();
engine.define_source_table("Sales", Some(1)).unwrap();
let ast = formualizer_parse::parser::parse("=SUM(Sales[Amount]) + SUM(Sales[Amount])").unwrap();
engine.set_cell_formula("Sheet1", 1, 1, ast).unwrap();
let v = engine
.evaluate_cell("Sheet1", 1, 1)
.unwrap()
.expect("computed value");
assert_eq!(v, LiteralValue::Number(60.0));
assert_eq!(ctx.table_calls(), 1);
}
#[test]
fn workbook_name_beats_source_scalar() {
let ctx = SourceCtx::default();
ctx.set_scalar("Foo", LiteralValue::Number(999.0));
let mut engine: Engine<_> = Engine::new(ctx.clone(), EvalConfig::default());
engine.add_sheet("Sheet1").unwrap();
engine
.set_cell_value("Sheet1", 1, 1, LiteralValue::Number(111.0))
.unwrap();
let sheet_id = engine.sheet_id("Sheet1").unwrap();
let target = CellRef::new(sheet_id, Coord::from_excel(1, 1, true, true));
engine
.define_name("Foo", NamedDefinition::Cell(target), NameScope::Workbook)
.unwrap();
engine.define_source_scalar("Foo", Some(1)).unwrap();
let ast = formualizer_parse::parser::parse("=Foo").unwrap();
engine.set_cell_formula("Sheet1", 1, 2, ast).unwrap();
let v = engine
.evaluate_cell("Sheet1", 1, 2)
.unwrap()
.expect("computed value");
assert_eq!(v, LiteralValue::Number(111.0));
assert_eq!(ctx.scalar_calls(), 0);
}
#[test]
fn workbook_table_beats_source_table() {
let ctx = SourceCtx::default();
let source_table = MemTable {
headers: vec!["Region".into(), "Amount".into()],
data: vec![
vec![LiteralValue::Text("X".into()), LiteralValue::Number(1000.0)],
vec![LiteralValue::Text("Y".into()), LiteralValue::Number(2000.0)],
],
};
ctx.set_table("Sales", Arc::new(source_table));
let mut engine: Engine<_> = Engine::new(ctx.clone(), EvalConfig::default());
engine.add_sheet("Sheet1").unwrap();
engine
.set_cell_value("Sheet1", 2, 2, LiteralValue::Number(10.0))
.unwrap();
engine
.set_cell_value("Sheet1", 3, 2, LiteralValue::Number(20.0))
.unwrap();
let sheet_id = engine.sheet_id("Sheet1").unwrap();
let start = CellRef::new(sheet_id, Coord::from_excel(1, 1, true, true));
let end = CellRef::new(sheet_id, Coord::from_excel(3, 2, true, true));
let range = RangeRef::new(start, end);
engine
.define_table(
"Sales",
range,
true,
vec!["Region".into(), "Amount".into()],
false,
)
.unwrap();
engine.define_source_table("Sales", Some(1)).unwrap();
let ast = formualizer_parse::parser::parse("=SUM(Sales[Amount])").unwrap();
engine.set_cell_formula("Sheet1", 1, 4, ast).unwrap();
let v = engine
.evaluate_cell("Sheet1", 1, 4)
.unwrap()
.expect("computed value");
assert_eq!(v, LiteralValue::Number(30.0));
assert_eq!(ctx.table_calls(), 0);
}
#[test]
fn volatile_source_recomputes_without_invalidate() {
let ctx = SourceCtx::default();
ctx.set_scalar("Foo", LiteralValue::Number(1.0));
let mut engine: Engine<_> = Engine::new(ctx.clone(), EvalConfig::default());
engine.add_sheet("Sheet1").unwrap();
engine.define_source_scalar("Foo", None).unwrap();
let ast = formualizer_parse::parser::parse("=Foo").unwrap();
engine.set_cell_formula("Sheet1", 1, 1, ast).unwrap();
engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 1, 1),
Some(LiteralValue::Number(1.0))
);
assert_eq!(ctx.scalar_calls(), 1);
ctx.set_scalar("Foo", LiteralValue::Number(2.0));
engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 1, 1),
Some(LiteralValue::Number(2.0))
);
assert_eq!(ctx.scalar_calls(), 2);
}
#[test]
fn nonvolatile_source_stays_stale_without_invalidate() {
let ctx = SourceCtx::default();
ctx.set_scalar("Foo", LiteralValue::Number(1.0));
let mut engine: Engine<_> = Engine::new(ctx.clone(), EvalConfig::default());
engine.add_sheet("Sheet1").unwrap();
engine.define_source_scalar("Foo", Some(1)).unwrap();
let ast = formualizer_parse::parser::parse("=Foo").unwrap();
engine.set_cell_formula("Sheet1", 1, 1, ast).unwrap();
engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 1, 1),
Some(LiteralValue::Number(1.0))
);
assert_eq!(ctx.scalar_calls(), 1);
ctx.set_scalar("Foo", LiteralValue::Number(2.0));
engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 1, 1),
Some(LiteralValue::Number(1.0))
);
assert_eq!(ctx.scalar_calls(), 1);
engine.invalidate_source("Foo").unwrap();
engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 1, 1),
Some(LiteralValue::Number(2.0))
);
assert_eq!(ctx.scalar_calls(), 2);
}
#[test]
fn cache_shared_across_vertices_in_evaluate_all() {
let ctx = SourceCtx::default();
let table = MemTable {
headers: vec!["Amount".into()],
data: vec![
vec![LiteralValue::Number(10.0)],
vec![LiteralValue::Number(20.0)],
],
};
ctx.set_table("Sales", Arc::new(table));
let mut engine: Engine<_> = Engine::new(ctx.clone(), EvalConfig::default());
engine.add_sheet("Sheet1").unwrap();
engine.define_source_table("Sales", Some(1)).unwrap();
let a1 = formualizer_parse::parser::parse("=SUM(Sales[Amount])").unwrap();
let a2 = formualizer_parse::parser::parse("=SUM(Sales[Amount])").unwrap();
engine.set_cell_formula("Sheet1", 1, 1, a1).unwrap();
engine.set_cell_formula("Sheet1", 2, 1, a2).unwrap();
engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 1, 1),
Some(LiteralValue::Number(30.0))
);
assert_eq!(
engine.get_cell_value("Sheet1", 2, 1),
Some(LiteralValue::Number(30.0))
);
assert_eq!(ctx.table_calls(), 1);
}
#[test]
fn cache_clears_between_calls() {
let ctx = SourceCtx::default();
let table = MemTable {
headers: vec!["Amount".into()],
data: vec![
vec![LiteralValue::Number(10.0)],
vec![LiteralValue::Number(20.0)],
],
};
ctx.set_table("Sales", Arc::new(table));
let mut engine: Engine<_> = Engine::new(ctx.clone(), EvalConfig::default());
engine.add_sheet("Sheet1").unwrap();
engine.define_source_table("Sales", Some(1)).unwrap();
let ast = formualizer_parse::parser::parse("=SUM(Sales[Amount])").unwrap();
engine.set_cell_formula("Sheet1", 1, 1, ast).unwrap();
engine.evaluate_cell("Sheet1", 1, 1).unwrap();
assert_eq!(ctx.table_calls(), 1);
engine.invalidate_source("Sales").unwrap();
engine.evaluate_cell("Sheet1", 1, 1).unwrap();
assert_eq!(ctx.table_calls(), 2);
}
#[test]
fn column_range_reversed_order() {
let ctx = SourceCtx::default();
let table = MemTable {
headers: vec!["A".into(), "B".into()],
data: vec![
vec![LiteralValue::Number(1.0), LiteralValue::Number(10.0)],
vec![LiteralValue::Number(2.0), LiteralValue::Number(20.0)],
],
};
ctx.set_table("Sales", Arc::new(table));
let mut engine: Engine<_> = Engine::new(ctx, EvalConfig::default());
engine.add_sheet("Sheet1").unwrap();
engine.define_source_table("Sales", Some(1)).unwrap();
let forward = formualizer_parse::parser::parse("=SUM(Sales[A:B])").unwrap();
let reverse = formualizer_parse::parser::parse("=SUM(Sales[B:A])").unwrap();
engine.set_cell_formula("Sheet1", 1, 1, forward).unwrap();
engine.set_cell_formula("Sheet1", 2, 1, reverse).unwrap();
engine.evaluate_all().unwrap();
let a = engine.get_cell_value("Sheet1", 1, 1).unwrap();
let b = engine.get_cell_value("Sheet1", 2, 1).unwrap();
assert_eq!(a, b);
assert_eq!(a, LiteralValue::Number(33.0));
}
#[test]
fn totals_missing_is_empty() {
let ctx = SourceCtx::default();
let table = MemTable {
headers: vec!["Amount".into()],
data: vec![
vec![LiteralValue::Number(10.0)],
vec![LiteralValue::Number(20.0)],
],
};
ctx.set_table("Sales", Arc::new(table));
let mut engine: Engine<_> = Engine::new(ctx, EvalConfig::default());
engine.add_sheet("Sheet1").unwrap();
engine.define_source_table("Sales", Some(1)).unwrap();
let ast = formualizer_parse::parser::parse("=SUM(Sales[#Totals])").unwrap();
engine.set_cell_formula("Sheet1", 1, 1, ast).unwrap();
engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 1, 1),
Some(LiteralValue::Number(0.0))
);
}
#[test]
fn unsupported_specifiers_error_kind() {
let ctx = SourceCtx::default();
let table = MemTable {
headers: vec!["A".into()],
data: vec![vec![LiteralValue::Number(1.0)]],
};
ctx.set_table("Sales", Arc::new(table));
let mut engine: Engine<_> = Engine::new(ctx, EvalConfig::default());
engine.add_sheet("Sheet1").unwrap();
engine.define_source_table("Sales", Some(1)).unwrap();
let ast = formualizer_parse::parser::parse("=SUM(Sales[@])").unwrap();
engine.set_cell_formula("Sheet1", 1, 1, ast).unwrap();
match engine.evaluate_cell("Sheet1", 1, 1).unwrap() {
Some(LiteralValue::Error(e)) => assert_eq!(e.kind, ExcelErrorKind::NImpl),
other => panic!("expected #N/IMPL!, got {other:?}"),
}
}
#[test]
fn define_source_duplicate_name_rejected() {
let ctx = SourceCtx::default();
let mut engine: Engine<_> = Engine::new(ctx, EvalConfig::default());
engine.define_source_scalar("Foo", Some(1)).unwrap();
let err = engine
.define_source_table("Foo", Some(1))
.expect_err("cannot define scalar and table under same name");
assert_eq!(err.kind, ExcelErrorKind::Name);
}
#[test]
fn invalidate_unknown_source_errors() {
let ctx = SourceCtx::default();
let mut engine: Engine<_> = Engine::new(ctx, EvalConfig::default());
let err = engine
.invalidate_source("Nope")
.expect_err("unknown source should error");
assert_eq!(err.kind, ExcelErrorKind::Name);
}