use super::super::geom::{Cell, Rect};
use crate::engine::arena::ValueRef;
use crate::engine::graph::DependencyGraph;
use crate::engine::{Engine, EvalConfig};
use crate::test_workbook::TestWorkbook;
use formualizer_common::LiteralValue;
use formualizer_parse::parse;
use formualizer_parse::parser::{ASTNode, ASTNodeType, ReferenceType};
fn shift(v: &mut u32, abs: bool, d: i64) {
if !abs {
*v = (i64::from(*v) + d) as u32;
}
}
fn shift_opt(v: &mut Option<u32>, abs: bool, d: i64) {
if let Some(x) = v {
shift(x, abs, d);
}
}
fn instantiate(ast: &mut ASTNode, dr: i64, dc: i64, lits: &mut std::slice::Iter<'_, LiteralValue>) {
match &mut ast.node_type {
ASTNodeType::Literal(v) => {
if let Some(x) = lits.next() {
*v = x.clone();
}
}
ASTNodeType::Omitted => {}
ASTNodeType::Reference { reference, .. } => match reference {
ReferenceType::Cell {
row,
col,
row_abs,
col_abs,
..
} => {
shift(row, *row_abs, dr);
shift(col, *col_abs, dc);
}
ReferenceType::Range {
start_row,
start_col,
end_row,
end_col,
start_row_abs,
start_col_abs,
end_row_abs,
end_col_abs,
..
} => {
shift_opt(start_row, *start_row_abs, dr);
shift_opt(end_row, *end_row_abs, dr);
shift_opt(start_col, *start_col_abs, dc);
shift_opt(end_col, *end_col_abs, dc);
}
_ => {}
},
ASTNodeType::UnaryOp { expr, .. } => instantiate(expr, dr, dc, lits),
ASTNodeType::BinaryOp { left, right, .. } => {
instantiate(left, dr, dc, lits);
instantiate(right, dr, dc, lits);
}
ASTNodeType::Function { args, .. } => {
for a in args {
instantiate(a, dr, dc, lits);
}
}
ASTNodeType::Call { callee, args } => {
instantiate(callee, dr, dc, lits);
for a in args {
instantiate(a, dr, dc, lits);
}
}
ASTNodeType::Array(rows) => {
for row in rows {
for x in row {
instantiate(x, dr, dc, lits);
}
}
}
}
}
fn strip(ast: &mut ASTNode) {
ast.source_token = None;
match &mut ast.node_type {
ASTNodeType::Reference { original, .. } => original.clear(),
ASTNodeType::UnaryOp { expr, .. } => strip(expr),
ASTNodeType::BinaryOp { left, right, .. } => {
strip(left);
strip(right);
}
ASTNodeType::Function { args, .. } => args.iter_mut().for_each(strip),
ASTNodeType::Call { callee, args } => {
strip(callee);
args.iter_mut().for_each(strip);
}
ASTNodeType::Array(rows) => rows.iter_mut().flatten().for_each(strip),
ASTNodeType::Literal(_) | ASTNodeType::Omitted => {}
}
}
fn check_views(g: &mut DependencyGraph) -> usize {
g.authority().expect("ready");
let vids: Vec<_> = g.vertices_with_formulas().collect();
let mut n = 0;
for v in vids {
let Some(cr) = g.get_cell_ref(v) else {
continue;
};
let cell: Cell = (cr.sheet_id, cr.coord.row(), cr.coord.col());
let store = g.authority_host().store();
let (template, _anchor, (dr, dc), row) =
store.formula_view(cell).expect("formula cell has a view");
let mut ast = g
.data_store()
.retrieve_ast(template, g.sheet_reg())
.expect("template AST");
let lits: Vec<LiteralValue> = row
.unwrap_or(&[])
.iter()
.map(|&x: &ValueRef| g.data_store().retrieve_value(x))
.collect();
instantiate(&mut ast, dr, dc, &mut lits.iter());
let mut own = g.get_formula(v).expect("own formula");
strip(&mut ast);
strip(&mut own);
assert_eq!(
format!("{:?}", ast.node_type),
format!("{:?}", own.node_type),
"view of {cell:?}"
);
n += 1;
}
n
}
fn engine() -> Engine<TestWorkbook> {
let mut e = Engine::new(TestWorkbook::new(), EvalConfig::default());
for r in 1..=40 {
e.set_cell_value("Sheet1", r, 1, LiteralValue::Number(f64::from(r)))
.unwrap();
}
e.define_table(
"T1",
crate::reference::RangeRef::new(
crate::reference::CellRef::new(0, crate::reference::Coord::new(0, 5, true, true)),
crate::reference::CellRef::new(0, crate::reference::Coord::new(9, 6, true, true)),
),
true,
vec!["X".into(), "Y".into()],
false,
)
.unwrap();
e
}
#[test]
fn families_become_nodes_with_literal_rows() {
let mut e = engine();
for r in 1..=30u32 {
e.set_cell_formula("Sheet1", r, 2, parse(format!("=A{r}*2")).unwrap())
.unwrap();
e.set_cell_formula(
"Sheet1",
r,
3,
parse(format!("=A{r}*{r}+\"x{r}\"")).unwrap(),
)
.unwrap();
}
for r in 1..=20u32 {
for (c, col) in [(4u32, "B"), (5, "C")] {
e.set_cell_formula(
"Sheet1",
r,
c,
parse(format!("=SUM($A$1:$A{r})+{col}{r}")).unwrap(),
)
.unwrap();
}
e.set_cell_formula("Sheet1", r, 8, parse("=SUM(T1[X])").unwrap())
.unwrap();
}
e.set_cell_formula("Sheet1", 35, 2, parse("=A1+A2").unwrap())
.unwrap();
let g = &mut e.graph;
g.authority_host_mut().state = super::super::host::HostState::Unbuilt;
g.authority().expect("ready");
let store = g.authority_host().store();
store.check().unwrap();
let dom = |c: Cell| {
let o = store.owner_at(c).unwrap();
let (_, d, fam) = store.owner_dom(o);
(d, fam)
};
assert_eq!(dom((0, 4, 1)), (Rect::new(0, 1, 29, 1), true));
assert_eq!(
dom((0, 4, 2)),
(Rect::new(0, 2, 29, 2), true),
"literal-distinct family is one node"
);
assert_eq!(dom((0, 0, 3)), (Rect::new(0, 3, 19, 4), true));
assert!(!dom((0, 34, 1)).1);
assert_eq!(dom((0, 3, 7)), (Rect::cell(3, 7), false));
assert!(
store
.owner_group_key(store.owner_at((0, 3, 7)).unwrap())
.is_none()
);
let slot_rows = store.slots().rows();
assert_eq!(slot_rows, 30 + 30, "B and C members carry literal rows");
let n = check_views(g);
assert!(n > 30 + 30 + 40 + 20);
}
#[test]
fn views_survive_punch_refill_and_repartition() {
let mut e = engine();
for r in 1..=30u32 {
e.set_cell_formula("Sheet1", r, 3, parse(format!("=A{r}*{r}")).unwrap())
.unwrap();
}
check_views(&mut e.graph);
for r in (2..=29u32).step_by(3) {
e.set_cell_value("Sheet1", r, 3, LiteralValue::Number(0.0))
.unwrap();
}
check_views(&mut e.graph);
for r in (2..=29u32).step_by(3) {
e.set_cell_formula("Sheet1", r, 3, parse(format!("=A{r}*{}", r + 100)).unwrap())
.unwrap();
}
for r in [5u32, 6, 7] {
e.set_cell_formula("Sheet1", r, 3, parse(format!("=A{r}*{r}")).unwrap())
.unwrap();
}
let n = check_views(&mut e.graph);
assert_eq!(n, 30);
let store = e.graph.authority_host().store();
store.check().unwrap();
assert!(store.stats.repartitions_committed > 0, "{:?}", store.stats);
let rebuilt = super::fixtures::rebuild_from_graph(&e);
assert_eq!(store.digest(), rebuilt.digest());
let o = rebuilt.owner_at((0, 0, 2)).unwrap();
assert_eq!(rebuilt.owner_dom(o).1, Rect::new(0, 2, 29, 2));
}
#[test]
fn formulas_over_the_slot_arity_stay_ungrouped() {
let mut e = engine();
let many: Vec<String> = (0..300).map(|k| k.to_string()).collect();
for r in 1..=3u32 {
let text = format!("=A{r}+SUM({})", many.join(","));
e.set_cell_formula("Sheet1", r, 2, parse(text).unwrap())
.unwrap();
}
let n = check_views(&mut e.graph);
assert_eq!(n, 3);
let store = e.graph.authority_host().store();
for r in 0..3u32 {
let o = store.owner_at((0, r, 1)).unwrap();
assert!(store.owner_group_key(o).is_none());
}
assert_eq!(store.slots().rows(), 0);
}