1use crate::SheetId;
2use crate::arrow_store::SheetStore;
3use crate::engine::eval_delta::{DeltaCollector, DeltaMode, EvalDelta};
4use crate::engine::named_range::{NameScope, NamedDefinition};
5use crate::engine::range_view::RangeView;
6use crate::engine::row_visibility::RowVisibilityState;
7use crate::engine::spill::{RegionLockManager, SpillMeta, SpillShape};
8use crate::engine::virtual_deps::VirtualDepBuilder;
9use crate::engine::{
10 DependencyGraph, EvalConfig, FormulaParseDiagnostic, FormulaParsePolicy, RowVisibilitySource,
11 Scheduler, VertexId, VertexKind, VisibilityMaskMode,
12};
13use crate::interpreter::Interpreter;
14use crate::reference::{CellRef, Coord, RangeRef};
15use crate::traits::FunctionProvider;
16use crate::traits::{EvaluationContext, Resolver};
17use chrono::Timelike;
18use formualizer_common::{col_letters_from_1based, parse_a1_1based};
19use formualizer_parse::parser::ReferenceType;
20use formualizer_parse::{ASTNode, ASTNodeType, ExcelError, ExcelErrorKind, LiteralValue};
21use rayon::ThreadPoolBuilder;
22use rustc_hash::{FxHashMap, FxHashSet};
23use std::sync::Arc;
24use std::sync::atomic::{AtomicBool, Ordering};
25
26type StagedFormulaEntry = (u32, u32, String);
27type ParsedFormulaEntry = (u32, u32, ASTNode);
28type StagedFormulaMap = std::collections::HashMap<String, Vec<StagedFormulaEntry>>;
29type PreparedFormulaBatches = Vec<(String, Vec<ParsedFormulaEntry>)>;
30type StagedFormulaBatches = Vec<(String, Vec<StagedFormulaEntry>)>;
31
32pub struct Engine<R> {
33 pub(crate) graph: DependencyGraph,
34 resolver: R,
35 pub config: EvalConfig,
36 workbook_load_limits: crate::engine::WorkbookLoadLimits,
37 clock: Arc<dyn crate::timezone::ClockProvider>,
38 thread_pool: Option<Arc<rayon::ThreadPool>>,
39 pub recalc_epoch: u64,
40 snapshot_id: std::sync::atomic::AtomicU64,
41 topology_epoch: u64,
42 cached_static_schedule: Option<CachedScheduleEntry>,
43 spill_mgr: ShimSpillManager,
44 arrow_sheets: SheetStore,
46 has_edited: bool,
48 overlay_compactions: u64,
50
51 computed_overlay_bytes_estimate: usize,
53 computed_overlay_mirroring_disabled: bool,
54 pub(crate) force_materialize_range_views: bool,
57 row_bounds_cache: std::sync::RwLock<Option<RowBoundsCache>>,
59 source_cache: Arc<std::sync::RwLock<SourceCache>>,
60 staged_formulas: StagedFormulaMap,
62 row_visibility: FxHashMap<SheetId, RowVisibilityState>,
64 row_visibility_mask_cache: std::sync::RwLock<
66 FxHashMap<VisibilityMaskCacheKey, std::sync::Arc<arrow_array::BooleanArray>>,
67 >,
68 formula_parse_diagnostics: Vec<FormulaParseDiagnostic>,
70 active_cancel_flag: Option<Arc<AtomicBool>>,
72
73 action_depth: u32,
78
79 last_virtual_dep_telemetry: VirtualDepTelemetry,
81 virtual_dep_fallback_activations: u64,
82}
83
84pub struct EngineAction<'a, R>
89where
90 R: EvaluationContext,
91{
92 engine: &'a mut Engine<R>,
93 name: String,
94 log: Option<*mut crate::engine::ChangeLog>,
97 arrow_undo: Option<*mut crate::engine::ArrowUndoBatch>,
100 atomic_policy: bool,
102}
103
104impl<'a, R> EngineAction<'a, R>
105where
106 R: EvaluationContext,
107{
108 #[inline]
109 fn addr_for(&mut self, sheet: &str, row: u32, col: u32) -> crate::reference::CellRef {
110 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
111 let coord = crate::reference::Coord::from_excel(row, col, true, true);
112 crate::reference::CellRef::new(sheet_id, coord)
113 }
114
115 #[inline]
116 pub fn name(&self) -> &str {
117 &self.name
118 }
119
120 #[inline]
121 pub fn set_cell_value(
122 &mut self,
123 sheet: &str,
124 row: u32,
125 col: u32,
126 value: LiteralValue,
127 ) -> Result<(), crate::engine::EditorError> {
128 if self.log.is_some() {
129 let old_value = self.engine.read_cell_value(sheet, row, col);
130 let old_formula = self.engine.read_cell_formula_ast(sheet, row, col);
131 let addr = self.addr_for(sheet, row, col);
132 let Some(log_ptr) = self.log else {
133 return Err(crate::engine::EditorError::TransactionFailed {
134 reason: "action_with_logger: missing ChangeLog".to_string(),
135 });
136 };
137
138 let old_comp = if self.arrow_undo.is_some() {
141 self.engine.read_computed_overlay_cell(sheet, row, col)
142 } else {
143 None
144 };
145
146 let delta_old_sem = if old_formula.is_some() {
147 None
148 } else {
149 Some(old_value.clone().unwrap_or(LiteralValue::Empty))
150 };
151
152 let start_len = unsafe { (&*log_ptr).len() };
153
154 let log = unsafe { &mut *log_ptr };
156 self.engine.edit_with_logger(log, |editor| {
157 editor.set_cell_value(addr, value.clone());
158 });
159 log.patch_last_cell_event_old_state(addr, old_value.clone(), old_formula.clone());
160
161 if let Some(undo_ptr) = self.arrow_undo {
162 let new_events = &unsafe { (&*log_ptr).events() }[start_len..];
164 let undo = unsafe { &mut *undo_ptr };
165 self.engine
166 .record_spill_ops_into_arrow_undo(undo, new_events);
167
168 let new_comp = self.engine.read_computed_overlay_cell(sheet, row, col);
170 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
171 let row0 = row.saturating_sub(1);
172 let col0 = col.saturating_sub(1);
173 let delta_new_sem = Some(value.clone());
174 undo.record_delta_cell(sheet_id, row0, col0, delta_old_sem, delta_new_sem);
175 undo.record_computed_cell(sheet_id, row0, col0, old_comp, new_comp);
176 }
177 Ok(())
178 } else {
179 self.engine
180 .set_cell_value(sheet, row, col, value)
181 .map_err(crate::engine::EditorError::from)
182 }
183 }
184
185 #[inline]
186 pub fn set_cell_formula(
187 &mut self,
188 sheet: &str,
189 row: u32,
190 col: u32,
191 ast: ASTNode,
192 ) -> Result<(), crate::engine::EditorError> {
193 if self.log.is_some() {
194 let old_value = self.engine.read_cell_value(sheet, row, col);
195 let old_formula = self.engine.read_cell_formula_ast(sheet, row, col);
196 let addr = self.addr_for(sheet, row, col);
197 let Some(log_ptr) = self.log else {
198 return Err(crate::engine::EditorError::TransactionFailed {
199 reason: "action_with_logger: missing ChangeLog".to_string(),
200 });
201 };
202
203 let delta_old = if self.arrow_undo.is_some() {
204 if old_formula.is_some() {
205 None
206 } else {
207 Some(old_value.clone().unwrap_or(LiteralValue::Empty))
208 }
209 } else {
210 None
211 };
212 let start_len = unsafe { (&*log_ptr).len() };
213
214 let log = unsafe { &mut *log_ptr };
216 self.engine.edit_with_logger(log, |editor| {
217 editor.set_cell_formula(addr, ast.clone());
218 });
219 log.patch_last_cell_event_old_state(addr, old_value, old_formula);
220
221 if let Some(undo_ptr) = self.arrow_undo {
222 let new_events = &unsafe { (&*log_ptr).events() }[start_len..];
223 let undo = unsafe { &mut *undo_ptr };
224 self.engine
225 .record_spill_ops_into_arrow_undo(undo, new_events);
226 let delta_new: Option<LiteralValue> = None;
227 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
228 let row0 = row.saturating_sub(1);
229 let col0 = col.saturating_sub(1);
230 undo.record_delta_cell(sheet_id, row0, col0, delta_old, delta_new);
231 }
232 Ok(())
233 } else {
234 self.engine
235 .set_cell_formula(sheet, row, col, ast)
236 .map_err(crate::engine::EditorError::from)
237 }
238 }
239
240 #[inline]
241 pub fn set_row_hidden(
242 &mut self,
243 sheet: &str,
244 row_1based: u32,
245 hidden: bool,
246 source: RowVisibilitySource,
247 ) -> Result<(), crate::engine::EditorError> {
248 if self.log.is_some() {
249 let sheet_id = self.engine.ensure_known_sheet_id(sheet)?;
250 let row0 = Engine::<R>::normalize_row_1based(row_1based)?;
251 let old_hidden = self
252 .engine
253 .row_visibility
254 .get(&sheet_id)
255 .map(|state| state.is_row_hidden(row0, Some(source)))
256 .unwrap_or(false);
257 if old_hidden == hidden {
258 return Ok(());
259 }
260
261 let _ = self
262 .engine
263 .set_row_hidden_by_sheet_id(sheet_id, row0, hidden, source);
264
265 let Some(log_ptr) = self.log else {
266 return Err(crate::engine::EditorError::TransactionFailed {
267 reason: "action_with_logger: missing ChangeLog".to_string(),
268 });
269 };
270 unsafe { &mut *log_ptr }.record(crate::engine::ChangeEvent::SetRowVisibility {
271 sheet_id,
272 row0,
273 source,
274 old_hidden,
275 new_hidden: hidden,
276 });
277
278 Ok(())
279 } else {
280 self.engine
281 .set_row_hidden(sheet, row_1based, hidden, source)
282 }
283 }
284
285 #[inline]
286 pub fn set_rows_hidden(
287 &mut self,
288 sheet: &str,
289 start_row_1based: u32,
290 end_row_1based: u32,
291 hidden: bool,
292 source: RowVisibilitySource,
293 ) -> Result<(), crate::engine::EditorError> {
294 if self.log.is_some() {
295 let sheet_id = self.engine.ensure_known_sheet_id(sheet)?;
296 let (start_row0, end_row0) =
297 Engine::<R>::normalize_row_range_1based(start_row_1based, end_row_1based)?;
298
299 let Some(log_ptr) = self.log else {
300 return Err(crate::engine::EditorError::TransactionFailed {
301 reason: "action_with_logger: missing ChangeLog".to_string(),
302 });
303 };
304 let log = unsafe { &mut *log_ptr };
305
306 for row0 in start_row0..=end_row0 {
307 let old_hidden = self
308 .engine
309 .row_visibility
310 .get(&sheet_id)
311 .map(|state| state.is_row_hidden(row0, Some(source)))
312 .unwrap_or(false);
313 if old_hidden == hidden {
314 continue;
315 }
316
317 let _ = self
318 .engine
319 .set_row_hidden_by_sheet_id(sheet_id, row0, hidden, source);
320
321 log.record(crate::engine::ChangeEvent::SetRowVisibility {
322 sheet_id,
323 row0,
324 source,
325 old_hidden,
326 new_hidden: hidden,
327 });
328 }
329
330 Ok(())
331 } else {
332 self.engine
333 .set_rows_hidden(sheet, start_row_1based, end_row_1based, hidden, source)
334 }
335 }
336
337 #[inline]
338 pub fn insert_rows(
339 &mut self,
340 sheet: &str,
341 before: u32,
342 count: u32,
343 ) -> Result<crate::engine::ShiftSummary, crate::engine::EditorError> {
344 if self.log.is_some() {
345 let Some(log_ptr) = self.log else {
346 return Err(crate::engine::EditorError::TransactionFailed {
347 reason: "action_atomic: missing ChangeLog".to_string(),
348 });
349 };
350
351 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
352 let before0 = before.saturating_sub(1);
353
354 let summary = {
356 let log = unsafe { &mut *log_ptr };
357 let mut out: Result<crate::engine::ShiftSummary, crate::engine::EditorError> =
358 Ok(crate::engine::ShiftSummary::default());
359 self.engine.edit_with_logger(log, |editor| {
360 out = editor.insert_rows(sheet_id, before0, count);
361 });
362 out?
363 };
364
365 self.engine.ensure_arrow_sheet(sheet);
367 if let Some(asheet) = self.engine.arrow_sheets.sheet_mut(sheet) {
368 asheet.insert_rows(before0 as usize, count as usize);
369 }
370 self.engine
371 .shift_row_visibility_insert(sheet_id, before0, count);
372 if let Some(undo_ptr) = self.arrow_undo {
373 unsafe { &mut *undo_ptr }.record_insert_rows(sheet_id, before0, count);
374 }
375 Ok(summary)
376 } else {
377 self.engine.insert_rows(sheet, before, count)
378 }
379 }
380
381 #[inline]
382 pub fn delete_rows(
383 &mut self,
384 sheet: &str,
385 start: u32,
386 count: u32,
387 ) -> Result<crate::engine::ShiftSummary, crate::engine::EditorError> {
388 if self.atomic_policy {
389 return Err(crate::engine::EditorError::TransactionUnsupported {
390 reason:
391 "delete_rows is not supported inside atomic actions (conservative rollback policy)"
392 .to_string(),
393 });
394 }
395 self.engine.delete_rows(sheet, start, count)
396 }
397
398 #[inline]
399 pub fn insert_columns(
400 &mut self,
401 sheet: &str,
402 before: u32,
403 count: u32,
404 ) -> Result<crate::engine::ShiftSummary, crate::engine::EditorError> {
405 if self.log.is_some() {
406 let Some(log_ptr) = self.log else {
407 return Err(crate::engine::EditorError::TransactionFailed {
408 reason: "action_atomic: missing ChangeLog".to_string(),
409 });
410 };
411
412 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
413 let before0 = before.saturating_sub(1);
414
415 let summary = {
416 let log = unsafe { &mut *log_ptr };
417 let mut out: Result<crate::engine::ShiftSummary, crate::engine::EditorError> =
418 Ok(crate::engine::ShiftSummary::default());
419 self.engine.edit_with_logger(log, |editor| {
420 out = editor.insert_columns(sheet_id, before0, count);
421 });
422 out?
423 };
424
425 self.engine.ensure_arrow_sheet(sheet);
426 if let Some(asheet) = self.engine.arrow_sheets.sheet_mut(sheet) {
427 asheet.insert_columns(before0 as usize, count as usize);
428 }
429 if let Some(undo_ptr) = self.arrow_undo {
430 unsafe { &mut *undo_ptr }.record_insert_cols(sheet_id, before0, count);
431 }
432 Ok(summary)
433 } else {
434 self.engine.insert_columns(sheet, before, count)
435 }
436 }
437
438 #[inline]
439 pub fn delete_columns(
440 &mut self,
441 sheet: &str,
442 start: u32,
443 count: u32,
444 ) -> Result<crate::engine::ShiftSummary, crate::engine::EditorError> {
445 if self.atomic_policy {
446 return Err(crate::engine::EditorError::TransactionUnsupported {
447 reason:
448 "delete_columns is not supported inside atomic actions (conservative rollback policy)"
449 .to_string(),
450 });
451 }
452 self.engine.delete_columns(sheet, start, count)
453 }
454
455 #[inline]
460 pub fn action<T>(
461 &mut self,
462 name: impl AsRef<str>,
463 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
464 ) -> Result<T, crate::engine::EditorError> {
465 self.engine.action(name, f)
466 }
467}
468
469struct ActionDepthGuard<'a, R> {
470 engine: *mut Engine<R>,
471 _marker: std::marker::PhantomData<&'a mut Engine<R>>,
472}
473
474impl<'a, R> Drop for ActionDepthGuard<'a, R> {
475 fn drop(&mut self) {
476 unsafe {
479 let e = &mut *self.engine;
480 e.action_depth = e.action_depth.saturating_sub(1);
481 }
482 }
483}
484
485#[derive(Default)]
486struct SourceCache {
487 scalars: FxHashMap<(String, Option<u64>), LiteralValue>,
488 tables: FxHashMap<(String, Option<u64>), Arc<dyn crate::traits::Table>>,
489}
490
491#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
492struct VisibilityMaskCacheKey {
493 sheet_id: SheetId,
494 start_row0: u32,
495 end_row0: u32,
496 mode: VisibilityMaskMode,
497 version: u64,
498}
499
500struct SourceCacheSession {
501 cache: Arc<std::sync::RwLock<SourceCache>>,
502}
503
504impl Drop for SourceCacheSession {
505 fn drop(&mut self) {
506 if let Ok(mut g) = self.cache.write() {
507 *g = SourceCache::default();
508 }
509 }
510}
511
512#[derive(Debug)]
513pub struct EvalResult {
514 pub computed_vertices: usize,
515 pub cycle_errors: usize,
516 pub elapsed: std::time::Duration,
517}
518
519#[derive(Debug, Clone, Default)]
520pub struct VirtualDepTelemetry {
521 pub candidate_vertices_total: usize,
522 pub vdeps_vertices_total: usize,
523 pub vdeps_edges_total: usize,
524 pub builder_elapsed_ms_total: u128,
525 pub schedule_virtual_passes: usize,
526 pub schedule_static_passes: usize,
527 pub schedule_cache_hits: usize,
528 pub schedule_cache_misses: usize,
529 pub reused_schedule_vertices_total: usize,
530 pub replan_iterations: usize,
531 pub changed_vdeps_total: usize,
532 pub bailout_reason: Option<&'static str>,
533 pub fallback_mode_activations: u64,
534}
535
536#[derive(Debug, Clone, Copy)]
537struct ScheduleBuildMeta {
538 candidate_vertices: usize,
539 vdeps_vertices: usize,
540 vdeps_edges: usize,
541 builder_elapsed_ms: u128,
542 used_virtual_schedule: bool,
543 schedule_cache_hit: bool,
544 schedule_cache_eligible: bool,
545}
546
547#[derive(Debug, Clone)]
548struct CachedScheduleEntry {
549 topology_epoch: u64,
550 candidate_vertices: Vec<VertexId>,
551 schedule: crate::engine::scheduler::Schedule,
552}
553
554type ScheduleBuildOutput = (
555 crate::engine::scheduler::Schedule,
556 FxHashMap<VertexId, Vec<VertexId>>,
557 ScheduleBuildMeta,
558);
559
560#[derive(Debug)]
562pub struct RecalcPlan {
563 schedule: crate::engine::Schedule,
564 has_dynamic_refs: bool,
565}
566
567impl RecalcPlan {
568 pub fn layer_count(&self) -> usize {
569 self.schedule.layers.len()
570 }
571
572 pub fn has_dynamic_refs(&self) -> bool {
573 self.has_dynamic_refs
574 }
575}
576
577#[cfg(test)]
578pub(crate) mod criteria_mask_test_hooks {
579 use std::cell::Cell;
580
581 thread_local! {
582 static TEXT_SEGMENTS_TOTAL: Cell<usize> = const { Cell::new(0) };
583 static TEXT_SEGMENTS_ALL_NULL: Cell<usize> = const { Cell::new(0) };
584 }
585
586 pub fn reset_text_segment_counters() {
587 TEXT_SEGMENTS_TOTAL.with(|c| c.set(0));
588 TEXT_SEGMENTS_ALL_NULL.with(|c| c.set(0));
589 }
590
591 pub fn text_segment_counters() -> (usize, usize) {
592 let a = TEXT_SEGMENTS_TOTAL.with(|c| c.get());
593 let b = TEXT_SEGMENTS_ALL_NULL.with(|c| c.get());
594 (a, b)
595 }
596
597 pub(crate) fn inc_total() {
598 TEXT_SEGMENTS_TOTAL.with(|c| c.set(c.get() + 1));
599 }
600 pub(crate) fn inc_all_null() {
601 TEXT_SEGMENTS_ALL_NULL.with(|c| c.set(c.get() + 1));
602 }
603}
604
605#[cfg(test)]
606pub(crate) mod visibility_mask_test_hooks {
607 use std::cell::Cell;
608
609 thread_local! {
610 static HITS: Cell<usize> = const { Cell::new(0) };
611 static MISSES: Cell<usize> = const { Cell::new(0) };
612 static EVICTIONS: Cell<usize> = const { Cell::new(0) };
613 }
614
615 pub fn reset() {
616 HITS.with(|c| c.set(0));
617 MISSES.with(|c| c.set(0));
618 EVICTIONS.with(|c| c.set(0));
619 }
620
621 pub fn counters() -> (usize, usize, usize) {
622 let hits = HITS.with(|c| c.get());
623 let misses = MISSES.with(|c| c.get());
624 let evictions = EVICTIONS.with(|c| c.get());
625 (hits, misses, evictions)
626 }
627
628 pub(crate) fn inc_hit() {
629 HITS.with(|c| c.set(c.get() + 1));
630 }
631
632 pub(crate) fn inc_miss() {
633 MISSES.with(|c| c.set(c.get() + 1));
634 }
635
636 pub(crate) fn inc_eviction() {
637 EVICTIONS.with(|c| c.set(c.get() + 1));
638 }
639}
640
641fn compute_criteria_mask(
642 view: &RangeView<'_>,
643 col_in_view: usize,
644 pred: &crate::args::CriteriaPredicate,
645) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
646 use crate::compute_prelude::{boolean, cmp, concat_arrays};
647 use arrow::compute::kernels::comparison::{ilike, nilike};
648 use arrow_array::{
649 Array as _, ArrayRef, BooleanArray, Float64Array, StringArray, builder::BooleanBuilder,
650 };
651
652 fn apply_numeric_pred(
654 chunk: &Float64Array,
655 pred: &crate::args::CriteriaPredicate,
656 ) -> Option<BooleanArray> {
657 match pred {
658 crate::args::CriteriaPredicate::Gt(n) => {
659 cmp::gt(chunk, &Float64Array::new_scalar(*n)).ok()
660 }
661 crate::args::CriteriaPredicate::Ge(n) => {
662 cmp::gt_eq(chunk, &Float64Array::new_scalar(*n)).ok()
663 }
664 crate::args::CriteriaPredicate::Lt(n) => {
665 cmp::lt(chunk, &Float64Array::new_scalar(*n)).ok()
666 }
667 crate::args::CriteriaPredicate::Le(n) => {
668 cmp::lt_eq(chunk, &Float64Array::new_scalar(*n)).ok()
669 }
670 crate::args::CriteriaPredicate::Eq(v) => match v {
671 formualizer_common::LiteralValue::Number(x) => {
672 cmp::eq(chunk, &Float64Array::new_scalar(*x)).ok()
673 }
674 formualizer_common::LiteralValue::Int(i) => {
675 cmp::eq(chunk, &Float64Array::new_scalar(*i as f64)).ok()
676 }
677 _ => None,
678 },
679 crate::args::CriteriaPredicate::Ne(v) => match v {
680 formualizer_common::LiteralValue::Number(x) => {
681 cmp::neq(chunk, &Float64Array::new_scalar(*x)).ok()
682 }
683 formualizer_common::LiteralValue::Int(i) => {
684 cmp::neq(chunk, &Float64Array::new_scalar(*i as f64)).ok()
685 }
686 _ => None,
687 },
688 _ => None,
689 }
690 }
691
692 let is_numeric_pred = matches!(
694 pred,
695 crate::args::CriteriaPredicate::Gt(_)
696 | crate::args::CriteriaPredicate::Ge(_)
697 | crate::args::CriteriaPredicate::Lt(_)
698 | crate::args::CriteriaPredicate::Le(_)
699 | crate::args::CriteriaPredicate::Eq(formualizer_common::LiteralValue::Number(_))
700 | crate::args::CriteriaPredicate::Eq(formualizer_common::LiteralValue::Int(_))
701 | crate::args::CriteriaPredicate::Ne(formualizer_common::LiteralValue::Number(_))
702 | crate::args::CriteriaPredicate::Ne(formualizer_common::LiteralValue::Int(_))
703 );
704
705 if is_numeric_pred {
709 let mut bool_parts: Vec<BooleanArray> = Vec::new();
710 for res in view.numbers_slices() {
711 let (_rs, _rl, cols_seg) = res.ok()?;
712 if col_in_view < cols_seg.len() {
713 let chunk = cols_seg[col_in_view].as_ref();
714 let mask = apply_numeric_pred(chunk, pred)?;
715 bool_parts.push(mask);
716 }
717 }
718
719 if bool_parts.is_empty() {
720 return None;
721 } else if bool_parts.len() == 1 {
722 return Some(std::sync::Arc::new(bool_parts.remove(0)));
723 } else {
724 let anys: Vec<&dyn arrow_array::Array> = bool_parts
726 .iter()
727 .map(|a| a as &dyn arrow_array::Array)
728 .collect();
729 let conc: ArrayRef = concat_arrays(&anys).ok()?;
730 let ba = conc.as_any().downcast_ref::<BooleanArray>()?.clone();
731 return Some(std::sync::Arc::new(ba));
732 }
733 }
734
735 let (text_kind, text_pat, empty_special) = match pred {
738 crate::args::CriteriaPredicate::Eq(formualizer_common::LiteralValue::Text(t)) => {
739 (0u8, t.to_lowercase(), t.is_empty())
740 }
741 crate::args::CriteriaPredicate::Ne(formualizer_common::LiteralValue::Text(t)) => {
742 (1u8, t.to_lowercase(), false)
743 }
744 crate::args::CriteriaPredicate::TextLike {
745 pattern,
746 case_insensitive,
747 } => {
748 let p = if *case_insensitive {
749 pattern.to_lowercase()
750 } else {
751 pattern.clone()
752 };
753 (2u8, p.replace('*', "%").replace('?', "_"), false)
754 }
755 _ => return None,
756 };
757
758 let pat = StringArray::new_scalar(text_pat);
759 let mut bool_parts: Vec<BooleanArray> = Vec::new();
760
761 for res in view.iter_row_chunks() {
762 let cs = res.ok()?;
763 if cs.row_len == 0 {
764 continue;
765 }
766 #[cfg(test)]
767 criteria_mask_test_hooks::inc_total();
768
769 let slices = view.slice_lowered_text(cs.row_start, cs.row_len);
770 if col_in_view >= slices.len() {
771 return None;
772 }
773
774 let seg_opt = slices[col_in_view].as_ref().map(|a| a.as_ref());
775 let seg = match seg_opt {
776 Some(s) => s,
777 None => {
778 #[cfg(test)]
779 criteria_mask_test_hooks::inc_all_null();
780 if text_kind == 0 && empty_special {
781 let mut bb = BooleanBuilder::with_capacity(cs.row_len);
783 bb.append_n(cs.row_len, true);
784 bool_parts.push(bb.finish());
785 } else {
786 bool_parts.push(BooleanArray::new_null(cs.row_len));
788 }
789 continue;
790 }
791 };
792
793 let seg_sa = seg.as_any().downcast_ref::<StringArray>()?;
794 let mut m = match text_kind {
795 0 => ilike(seg_sa, &pat).ok()?,
796 1 => nilike(seg_sa, &pat).ok()?,
797 2 => ilike(seg_sa, &pat).ok()?,
798 _ => return None,
799 };
800
801 if text_kind == 0 && empty_special {
802 let mut bb = BooleanBuilder::with_capacity(seg_sa.len());
804 for i in 0..seg_sa.len() {
805 bb.append_value(seg_sa.is_null(i));
806 }
807 let nulls = bb.finish();
808 m = boolean::or_kleene(&m, &nulls).ok()?;
809 }
810
811 bool_parts.push(m);
812 }
813
814 if bool_parts.is_empty() {
815 None
816 } else if bool_parts.len() == 1 {
817 Some(std::sync::Arc::new(bool_parts.remove(0)))
818 } else {
819 let anys: Vec<&dyn arrow_array::Array> = bool_parts
820 .iter()
821 .map(|a| a as &dyn arrow_array::Array)
822 .collect();
823 let conc: ArrayRef = concat_arrays(&anys).ok()?;
824 let ba = conc.as_any().downcast_ref::<BooleanArray>()?.clone();
825 Some(std::sync::Arc::new(ba))
826 }
827}
828
829#[derive(Debug, Clone)]
830pub struct LayerInfo {
831 pub vertex_count: usize,
832 pub parallel_eligible: bool,
833 pub sample_cells: Vec<String>, }
835
836#[derive(Debug, Clone)]
837pub struct EvalPlan {
838 pub total_vertices_to_evaluate: usize,
839 pub layers: Vec<LayerInfo>,
840 pub cycles_detected: usize,
841 pub dirty_count: usize,
842 pub volatile_count: usize,
843 pub parallel_enabled: bool,
844 pub estimated_parallel_layers: usize,
845 pub target_cells: Vec<String>,
846}
847
848impl<R> Engine<R>
849where
850 R: EvaluationContext,
851{
852 pub fn new(resolver: R, config: EvalConfig) -> Self {
853 crate::builtins::load_builtins();
854
855 let clock = config.deterministic_mode.build_clock().unwrap_or_else(|_| {
856 #[cfg(feature = "system-clock")]
857 {
858 Arc::new(crate::timezone::SystemClock::new(
859 crate::timezone::TimeZoneSpec::default(),
860 ))
861 }
862 #[cfg(not(feature = "system-clock"))]
863 {
864 Arc::new(crate::timezone::FixedClock::new(
865 chrono::DateTime::UNIX_EPOCH,
866 crate::timezone::TimeZoneSpec::Utc,
867 ))
868 }
869 });
870
871 let thread_pool = if config.enable_parallel {
873 let mut builder = ThreadPoolBuilder::new();
874 if let Some(max_threads) = config.max_threads {
875 builder = builder.num_threads(max_threads);
876 }
877
878 match builder.build() {
879 Ok(pool) => Some(Arc::new(pool)),
880 Err(_) => {
881 None
883 }
884 }
885 } else {
886 None
887 };
888
889 let mut engine = Self {
890 graph: DependencyGraph::new_with_config(config.clone()),
891 resolver,
892 config,
893 workbook_load_limits: crate::engine::WorkbookLoadLimits::default(),
894 clock,
895 thread_pool,
896 recalc_epoch: 0,
897 snapshot_id: std::sync::atomic::AtomicU64::new(1),
898 topology_epoch: 0,
899 cached_static_schedule: None,
900 spill_mgr: ShimSpillManager::default(),
901 arrow_sheets: SheetStore::default(),
902 has_edited: false,
903 overlay_compactions: 0,
904 computed_overlay_bytes_estimate: 0,
905 computed_overlay_mirroring_disabled: false,
906 force_materialize_range_views: false,
907 row_bounds_cache: std::sync::RwLock::new(None),
908 source_cache: Arc::new(std::sync::RwLock::new(SourceCache::default())),
909 staged_formulas: std::collections::HashMap::new(),
910 row_visibility: FxHashMap::default(),
911 row_visibility_mask_cache: std::sync::RwLock::new(FxHashMap::default()),
912 formula_parse_diagnostics: Vec::new(),
913 active_cancel_flag: None,
914 action_depth: 0,
915 last_virtual_dep_telemetry: VirtualDepTelemetry::default(),
916 virtual_dep_fallback_activations: 0,
917 };
918 engine.config.arrow_storage_enabled = true;
920 engine.config.delta_overlay_enabled = true;
921 engine.config.write_formula_overlay_enabled = true;
922 let default_sheet = engine.graph.default_sheet_name().to_string();
923 engine.ensure_arrow_sheet(&default_sheet);
924 engine
925 }
926
927 pub fn with_thread_pool(
929 resolver: R,
930 config: EvalConfig,
931 thread_pool: Arc<rayon::ThreadPool>,
932 ) -> Self {
933 crate::builtins::load_builtins();
934 let clock = config.deterministic_mode.build_clock().unwrap_or_else(|_| {
935 #[cfg(feature = "system-clock")]
936 {
937 Arc::new(crate::timezone::SystemClock::new(
938 crate::timezone::TimeZoneSpec::default(),
939 ))
940 }
941 #[cfg(not(feature = "system-clock"))]
942 {
943 Arc::new(crate::timezone::FixedClock::new(
944 chrono::DateTime::UNIX_EPOCH,
945 crate::timezone::TimeZoneSpec::Utc,
946 ))
947 }
948 });
949 let mut engine = Self {
950 graph: DependencyGraph::new_with_config(config.clone()),
951 resolver,
952 config,
953 workbook_load_limits: crate::engine::WorkbookLoadLimits::default(),
954 clock,
955 thread_pool: Some(thread_pool),
956 recalc_epoch: 0,
957 snapshot_id: std::sync::atomic::AtomicU64::new(1),
958 topology_epoch: 0,
959 cached_static_schedule: None,
960 spill_mgr: ShimSpillManager::default(),
961 arrow_sheets: SheetStore::default(),
962 has_edited: false,
963 overlay_compactions: 0,
964 computed_overlay_bytes_estimate: 0,
965 computed_overlay_mirroring_disabled: false,
966 force_materialize_range_views: false,
967 row_bounds_cache: std::sync::RwLock::new(None),
968 source_cache: Arc::new(std::sync::RwLock::new(SourceCache::default())),
969 staged_formulas: std::collections::HashMap::new(),
970 row_visibility: FxHashMap::default(),
971 row_visibility_mask_cache: std::sync::RwLock::new(FxHashMap::default()),
972 formula_parse_diagnostics: Vec::new(),
973 active_cancel_flag: None,
974 action_depth: 0,
975 last_virtual_dep_telemetry: VirtualDepTelemetry::default(),
976 virtual_dep_fallback_activations: 0,
977 };
978 engine.config.arrow_storage_enabled = true;
980 engine.config.delta_overlay_enabled = true;
981 engine.config.write_formula_overlay_enabled = true;
982 let default_sheet = engine.graph.default_sheet_name().to_string();
983 engine.ensure_arrow_sheet(&default_sheet);
984 engine
985 }
986
987 pub fn workbook_load_limits(&self) -> &crate::engine::WorkbookLoadLimits {
988 &self.workbook_load_limits
989 }
990
991 pub fn set_workbook_load_limits(&mut self, limits: crate::engine::WorkbookLoadLimits) {
992 self.workbook_load_limits = limits;
993 }
994
995 fn clear_source_cache(&self) {
996 if let Ok(mut g) = self.source_cache.write() {
997 *g = SourceCache::default();
998 }
999 }
1000
1001 pub fn last_virtual_dep_telemetry(&self) -> &VirtualDepTelemetry {
1002 &self.last_virtual_dep_telemetry
1003 }
1004
1005 pub fn virtual_dep_fallback_activations(&self) -> u64 {
1006 self.virtual_dep_fallback_activations
1007 }
1008
1009 fn reset_virtual_dep_telemetry_if_disabled(&mut self) {
1010 if !self.config.enable_virtual_dep_telemetry {
1011 self.last_virtual_dep_telemetry = VirtualDepTelemetry {
1012 fallback_mode_activations: self.virtual_dep_fallback_activations,
1013 ..VirtualDepTelemetry::default()
1014 };
1015 }
1016 }
1017
1018 fn source_cache_session(&self) -> SourceCacheSession {
1019 self.clear_source_cache();
1020 SourceCacheSession {
1021 cache: self.source_cache.clone(),
1022 }
1023 }
1024
1025 fn resolve_source_scalar_cached(
1026 &self,
1027 name: &str,
1028 version: Option<u64>,
1029 ) -> Result<LiteralValue, ExcelError> {
1030 let key = (name.to_string(), version);
1031 if let Ok(mut g) = self.source_cache.write() {
1032 if let Some(v) = g.scalars.get(&key) {
1033 return Ok(v.clone());
1034 }
1035
1036 let v = self.resolver.resolve_source_scalar(name).map_err(|err| {
1037 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
1038 ExcelError::new(ExcelErrorKind::Ref)
1039 .with_message(format!("Unresolved source scalar: {name}"))
1040 } else {
1041 err
1042 }
1043 })?;
1044 g.scalars.insert(key, v.clone());
1045 Ok(v)
1046 } else {
1047 self.resolver.resolve_source_scalar(name).map_err(|err| {
1048 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
1049 ExcelError::new(ExcelErrorKind::Ref)
1050 .with_message(format!("Unresolved source scalar: {name}"))
1051 } else {
1052 err
1053 }
1054 })
1055 }
1056 }
1057
1058 fn resolve_source_table_cached(
1059 &self,
1060 name: &str,
1061 version: Option<u64>,
1062 ) -> Result<Arc<dyn crate::traits::Table>, ExcelError> {
1063 let key = (name.to_string(), version);
1064 if let Ok(mut g) = self.source_cache.write() {
1065 if let Some(t) = g.tables.get(&key) {
1066 return Ok(t.clone());
1067 }
1068
1069 let t = self.resolver.resolve_source_table(name).map_err(|err| {
1070 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
1071 ExcelError::new(ExcelErrorKind::Ref)
1072 .with_message(format!("Unresolved source table: {name}"))
1073 } else {
1074 err
1075 }
1076 })?;
1077 let t: Arc<dyn crate::traits::Table> = Arc::from(t);
1078 g.tables.insert(key, t.clone());
1079 Ok(t)
1080 } else {
1081 self.resolver
1082 .resolve_source_table(name)
1083 .map_err(|err| {
1084 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
1085 ExcelError::new(ExcelErrorKind::Ref)
1086 .with_message(format!("Unresolved source table: {name}"))
1087 } else {
1088 err
1089 }
1090 })
1091 .map(Arc::from)
1092 }
1093 }
1094
1095 fn source_table_to_range_view(
1096 &self,
1097 table: &dyn crate::traits::Table,
1098 spec: &Option<formualizer_parse::parser::TableSpecifier>,
1099 ) -> Result<RangeView<'static>, ExcelError> {
1100 use formualizer_parse::parser::{SpecialItem, TableSpecifier};
1101
1102 let owned = match spec {
1103 Some(TableSpecifier::Column(c)) => {
1104 let c = c.trim();
1105 if c == "@" || c.contains('[') || c.contains(']') || c.contains(',') {
1106 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
1107 "Complex structured references not yet supported".to_string(),
1108 ));
1109 }
1110 table.get_column(c)?.materialise().into_owned()
1111 }
1112 Some(TableSpecifier::ColumnRange(start, end)) => {
1113 let cols = table.columns();
1114 let start = start.trim();
1115 let end = end.trim();
1116 let start_key = start.to_lowercase();
1117 let end_key = end.to_lowercase();
1118 let start_idx = cols.iter().position(|n| n.to_lowercase() == start_key);
1119 let end_idx = cols.iter().position(|n| n.to_lowercase() == end_key);
1120 if let (Some(mut si), Some(mut ei)) = (start_idx, end_idx) {
1121 if si > ei {
1122 std::mem::swap(&mut si, &mut ei);
1123 }
1124 let h = table.data_height();
1125 let w = ei - si + 1;
1126 let mut rows = vec![vec![LiteralValue::Empty; w]; h];
1127 for (offset, ci) in (si..=ei).enumerate() {
1128 let cname = &cols[ci];
1129 let col_range = table.get_column(cname)?;
1130 let (rh, _) = col_range.dimensions();
1131 for (r, row) in rows.iter_mut().enumerate().take(h.min(rh)) {
1132 row[offset] = col_range.get(r, 0)?;
1133 }
1134 }
1135 rows
1136 } else {
1137 return Err(ExcelError::new(ExcelErrorKind::Ref)
1138 .with_message("Column range refers to unknown column(s)".to_string()));
1139 }
1140 }
1141 Some(TableSpecifier::SpecialItem(SpecialItem::Headers))
1142 | Some(TableSpecifier::Headers) => table
1143 .headers_row()
1144 .map(|r| r.materialise().into_owned())
1145 .unwrap_or_default(),
1146 Some(TableSpecifier::SpecialItem(SpecialItem::Totals))
1147 | Some(TableSpecifier::Totals) => table
1148 .totals_row()
1149 .map(|r| r.materialise().into_owned())
1150 .unwrap_or_default(),
1151 Some(TableSpecifier::SpecialItem(SpecialItem::Data)) | Some(TableSpecifier::Data) => {
1152 table
1153 .data_body()
1154 .map(|r| r.materialise().into_owned())
1155 .unwrap_or_default()
1156 }
1157 Some(TableSpecifier::SpecialItem(SpecialItem::All)) | Some(TableSpecifier::All) => {
1158 let mut out: Vec<Vec<LiteralValue>> = Vec::new();
1159 if let Some(h) = table.headers_row() {
1160 out.extend(h.iter_rows());
1161 }
1162 if let Some(body) = table.data_body() {
1163 out.extend(body.iter_rows());
1164 }
1165 if let Some(tr) = table.totals_row() {
1166 out.extend(tr.iter_rows());
1167 }
1168 out
1169 }
1170 Some(TableSpecifier::SpecialItem(SpecialItem::ThisRow)) => {
1171 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
1172 "@ (This Row) requires table-aware context; not yet supported".to_string(),
1173 ));
1174 }
1175 Some(TableSpecifier::Row(_)) | Some(TableSpecifier::Combination(_)) => {
1176 return Err(ExcelError::new(ExcelErrorKind::NImpl)
1177 .with_message("Complex structured references not yet supported".to_string()));
1178 }
1179 None => {
1180 return Err(ExcelError::new(ExcelErrorKind::NImpl)
1181 .with_message("Table reference without specifier is unsupported".to_string()));
1182 }
1183 };
1184
1185 Ok(RangeView::from_owned_rows(owned, self.config.date_system))
1186 }
1187
1188 pub fn default_sheet_id(&self) -> SheetId {
1189 self.graph.default_sheet_id()
1190 }
1191
1192 pub fn default_sheet_name(&self) -> &str {
1193 self.graph.default_sheet_name()
1194 }
1195
1196 pub fn set_workbook_seed(&mut self, seed: u64) {
1198 self.config.workbook_seed = seed;
1199 }
1200
1201 pub fn set_volatile_level(&mut self, level: crate::traits::VolatileLevel) {
1203 self.config.volatile_level = level;
1204 }
1205
1206 pub fn set_deterministic_mode(
1208 &mut self,
1209 mode: crate::engine::DeterministicMode,
1210 ) -> Result<(), ExcelError> {
1211 let clock = mode.build_clock()?;
1212 self.config.deterministic_mode = mode;
1213 self.clock = clock;
1214 Ok(())
1215 }
1216
1217 fn validate_deterministic_mode(&self) -> Result<(), ExcelError> {
1218 self.config.deterministic_mode.validate()
1219 }
1220
1221 pub fn sheet_id(&self, name: &str) -> Option<SheetId> {
1222 self.graph.sheet_id(name)
1223 }
1224
1225 pub fn sheet_id_mut(&mut self, name: &str) -> SheetId {
1226 self.add_sheet(name)
1227 .unwrap_or_else(|_| self.graph.sheet_id_mut(name))
1228 }
1229
1230 pub fn sheet_name(&self, id: SheetId) -> &str {
1231 self.graph.sheet_name(id)
1232 }
1233
1234 pub fn add_sheet(&mut self, name: &str) -> Result<SheetId, ExcelError> {
1235 let id = self.graph.add_sheet(name)?;
1236 self.ensure_arrow_sheet(name);
1237 self.mark_topology_edited();
1238 Ok(id)
1239 }
1240
1241 fn ensure_arrow_sheet(&mut self, name: &str) {
1242 if self.arrow_sheets.sheet(name).is_some() {
1243 return;
1244 }
1245 self.arrow_sheets
1246 .sheets
1247 .push(crate::arrow_store::ArrowSheet {
1248 name: std::sync::Arc::<str>::from(name),
1249 columns: Vec::new(),
1250 nrows: 0,
1251 chunk_starts: Vec::new(),
1252 chunk_rows: 32 * 1024,
1253 });
1254 }
1255
1256 pub fn remove_sheet(&mut self, sheet_id: SheetId) -> Result<(), ExcelError> {
1257 let name = self.graph.sheet_name(sheet_id).to_string();
1258 self.graph.remove_sheet(sheet_id)?;
1259 self.arrow_sheets.sheets.retain(|s| s.name.as_ref() != name);
1260 if self.row_visibility.remove(&sheet_id).is_some() {
1261 self.invalidate_row_visibility_mask_cache();
1262 }
1263 Ok(())
1264 }
1265
1266 fn rename_sheet_in_arrow_store(&mut self, target_name: &str, new_name: &str) -> bool {
1268 if let Some(asheet) = self
1269 .arrow_sheets
1270 .sheets
1271 .iter_mut()
1272 .find(|s| s.name.as_ref() == target_name)
1273 {
1274 asheet.name = std::sync::Arc::<str>::from(new_name);
1275 return true;
1276 }
1277 false
1278 }
1279
1280 pub fn rename_sheet(&mut self, sheet_id: SheetId, new_name: &str) -> Result<(), ExcelError> {
1281 let old_name = self.graph.sheet_name(sheet_id).to_string();
1282
1283 self.rename_sheet_in_arrow_store(&old_name, new_name);
1286
1287 match self.graph.rename_sheet(sheet_id, new_name) {
1289 Ok(_) => {
1290 let sheet_vertices: Vec<VertexId> =
1292 self.graph.vertices_in_sheet(sheet_id).collect();
1293 for v_id in sheet_vertices {
1294 self.graph.mark_vertex_dirty(v_id);
1295 }
1296 self.mark_topology_edited();
1297 Ok(())
1298 }
1299 Err(e) => {
1300 self.rename_sheet_in_arrow_store(new_name, &old_name);
1302 Err(e)
1303 }
1304 }
1305 }
1306
1307 pub fn named_ranges_iter(
1308 &self,
1309 ) -> impl Iterator<Item = (&String, &crate::engine::named_range::NamedRange)> {
1310 self.graph.named_ranges_iter()
1311 }
1312
1313 pub fn sheet_named_ranges_iter(
1314 &self,
1315 ) -> impl Iterator<Item = (&(SheetId, String), &crate::engine::named_range::NamedRange)> {
1316 self.graph.sheet_named_ranges_iter()
1317 }
1318
1319 pub fn resolve_name_entry(
1320 &self,
1321 name: &str,
1322 current_sheet: SheetId,
1323 ) -> Option<&crate::engine::named_range::NamedRange> {
1324 self.graph.resolve_name_entry(name, current_sheet)
1325 }
1326
1327 pub fn named_ranges_snapshot(&self) -> Vec<crate::engine::named_range::NamedRangeSnapshot> {
1328 let mut out: Vec<crate::engine::named_range::NamedRangeSnapshot> = Vec::new();
1329
1330 for (name, named) in self.graph.named_ranges_iter() {
1331 out.push(crate::engine::named_range::NamedRangeSnapshot {
1332 name: name.clone(),
1333 scope: NameScope::Workbook,
1334 definition: named.definition.clone(),
1335 });
1336 }
1337
1338 for ((sheet_id, name), named) in self.graph.sheet_named_ranges_iter() {
1339 out.push(crate::engine::named_range::NamedRangeSnapshot {
1340 name: name.clone(),
1341 scope: NameScope::Sheet(*sheet_id),
1342 definition: named.definition.clone(),
1343 });
1344 }
1345
1346 out.sort_by(|a, b| {
1347 let a_scope = match a.scope {
1348 NameScope::Workbook => (0u8, 0u32),
1349 NameScope::Sheet(id) => (1u8, u32::from(id)),
1350 };
1351 let b_scope = match b.scope {
1352 NameScope::Workbook => (0u8, 0u32),
1353 NameScope::Sheet(id) => (1u8, u32::from(id)),
1354 };
1355 a_scope.cmp(&b_scope).then_with(|| a.name.cmp(&b.name))
1356 });
1357
1358 out
1359 }
1360
1361 pub fn named_ranges_snapshot_for_sheet(
1362 &self,
1363 sheet_id: SheetId,
1364 ) -> Vec<crate::engine::named_range::NamedRangeSnapshot> {
1365 self.named_ranges_snapshot()
1366 .into_iter()
1367 .filter(|entry| match entry.scope {
1368 NameScope::Workbook => true,
1369 NameScope::Sheet(id) => id == sheet_id,
1370 })
1371 .collect()
1372 }
1373
1374 pub fn define_name(
1375 &mut self,
1376 name: &str,
1377 definition: NamedDefinition,
1378 scope: NameScope,
1379 ) -> Result<(), ExcelError> {
1380 self.graph.define_name(name, definition, scope)?;
1381 self.mark_topology_edited();
1382 Ok(())
1383 }
1384
1385 pub fn update_name(
1386 &mut self,
1387 name: &str,
1388 definition: NamedDefinition,
1389 scope: NameScope,
1390 ) -> Result<(), ExcelError> {
1391 self.graph.update_name(name, definition, scope)?;
1392 self.mark_topology_edited();
1393 Ok(())
1394 }
1395
1396 pub fn delete_name(&mut self, name: &str, scope: NameScope) -> Result<(), ExcelError> {
1397 self.graph.delete_name(name, scope)?;
1398 self.mark_topology_edited();
1399 Ok(())
1400 }
1401
1402 pub fn define_table(
1403 &mut self,
1404 name: &str,
1405 range: crate::reference::RangeRef,
1406 header_row: bool,
1407 headers: Vec<String>,
1408 totals_row: bool,
1409 ) -> Result<(), ExcelError> {
1410 self.graph
1411 .define_table(name, range, header_row, headers, totals_row)?;
1412 self.mark_topology_edited();
1413 Ok(())
1414 }
1415
1416 pub fn define_source_scalar(
1417 &mut self,
1418 name: &str,
1419 version: Option<u64>,
1420 ) -> Result<(), ExcelError> {
1421 self.graph.define_source_scalar(name, version)?;
1422 self.mark_topology_edited();
1423 Ok(())
1424 }
1425
1426 pub fn define_source_table(
1427 &mut self,
1428 name: &str,
1429 version: Option<u64>,
1430 ) -> Result<(), ExcelError> {
1431 self.graph.define_source_table(name, version)?;
1432 self.mark_topology_edited();
1433 Ok(())
1434 }
1435
1436 pub fn set_source_scalar_version(
1437 &mut self,
1438 name: &str,
1439 version: Option<u64>,
1440 ) -> Result<(), ExcelError> {
1441 self.graph.set_source_scalar_version(name, version)
1442 }
1443
1444 pub fn set_source_table_version(
1445 &mut self,
1446 name: &str,
1447 version: Option<u64>,
1448 ) -> Result<(), ExcelError> {
1449 self.graph.set_source_table_version(name, version)
1450 }
1451
1452 pub fn invalidate_source(&mut self, name: &str) -> Result<(), ExcelError> {
1453 self.graph.invalidate_source(name)
1454 }
1455
1456 pub fn vertex_value(&self, vertex: VertexId) -> Option<LiteralValue> {
1457 self.graph.get_value(vertex)
1458 }
1459
1460 pub fn graph_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
1461 self.graph.get_cell_value(sheet, row, col)
1462 }
1463
1464 pub fn vertex_for_cell(&self, cell: &CellRef) -> Option<VertexId> {
1465 self.graph.get_vertex_for_cell(cell)
1466 }
1467
1468 pub fn evaluation_vertices(&self) -> Vec<VertexId> {
1469 self.graph.get_evaluation_vertices()
1470 }
1471
1472 pub fn set_first_load_assume_new(&mut self, enabled: bool) {
1473 self.graph.set_first_load_assume_new(enabled);
1474 }
1475
1476 pub fn reset_ensure_touched(&mut self) {
1477 self.graph.reset_ensure_touched();
1478 }
1479
1480 pub fn finalize_sheet_index(&mut self, sheet: &str) {
1481 self.graph.finalize_sheet_index(sheet);
1482 }
1483
1484 pub fn action<T>(
1493 &mut self,
1494 name: impl AsRef<str>,
1495 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
1496 ) -> Result<T, crate::engine::EditorError> {
1497 if self.action_depth != 0 {
1498 return Err(crate::engine::EditorError::TransactionFailed {
1499 reason: "Nested Engine::action calls are not supported (ticket 614: commit-only surface)"
1500 .to_string(),
1501 });
1502 }
1503
1504 self.action_depth = 1;
1505 let engine_ptr: *mut Engine<R> = self;
1506 let _guard = ActionDepthGuard {
1507 engine: engine_ptr,
1508 _marker: std::marker::PhantomData,
1509 };
1510
1511 let mut tx = EngineAction {
1512 engine: self,
1513 name: name.as_ref().to_string(),
1514 log: None,
1515 arrow_undo: None,
1516 atomic_policy: false,
1517 };
1518 f(&mut tx)
1519 }
1520
1521 pub fn action_atomic<T>(
1525 &mut self,
1526 name: impl Into<String>,
1527 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
1528 ) -> Result<T, crate::engine::EditorError> {
1529 let (v, _j) = self.action_atomic_journal(name, f)?;
1530 Ok(v)
1531 }
1532
1533 pub fn action_atomic_journal<T>(
1535 &mut self,
1536 name: impl Into<String>,
1537 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
1538 ) -> Result<(T, crate::engine::ActionJournal), crate::engine::EditorError> {
1539 if self.action_depth != 0 {
1540 return Err(crate::engine::EditorError::TransactionFailed {
1541 reason: "Nested Engine::action calls are not supported (deterministic rule)"
1542 .to_string(),
1543 });
1544 }
1545
1546 self.action_depth = 1;
1547 let engine_ptr: *mut Engine<R> = self;
1548 let _guard = ActionDepthGuard {
1549 engine: engine_ptr,
1550 _marker: std::marker::PhantomData,
1551 };
1552
1553 let name_str = name.into();
1554 let mut log = crate::engine::ChangeLog::new();
1555 let start_len = log.len();
1556 self.action_atomic_impl(&mut log, start_len, name_str, f)
1557 }
1558
1559 fn action_atomic_impl<T>(
1560 &mut self,
1561 log: &mut crate::engine::ChangeLog,
1562 start_len: usize,
1563 name: String,
1564 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
1565 ) -> Result<(T, crate::engine::ActionJournal), crate::engine::EditorError> {
1566 let mut arrow_undo = crate::engine::ArrowUndoBatch::default();
1567 let arrow_ptr: *mut crate::engine::ArrowUndoBatch = &mut arrow_undo;
1568
1569 let log_ptr: *mut crate::engine::ChangeLog = log;
1570 let mut tx = EngineAction {
1571 engine: self,
1572 name: name.clone(),
1573 log: Some(log_ptr),
1574 arrow_undo: Some(arrow_ptr),
1575 atomic_policy: true,
1576 };
1577
1578 let res = f(&mut tx);
1579
1580 let graph_events: Vec<crate::engine::ChangeEvent> =
1582 unsafe { (&*log_ptr).events() }[start_len..].to_vec();
1583 let graph_batch = crate::engine::GraphUndoBatch {
1584 events: graph_events,
1585 };
1586 let affected_cells = arrow_undo.ops.len();
1587 let journal = crate::engine::ActionJournal {
1588 name,
1589 graph: graph_batch,
1590 arrow: arrow_undo,
1591 affected_cells,
1592 };
1593
1594 match res {
1595 Ok(v) => {
1596 if !journal.graph.is_empty() || !journal.arrow.is_empty() {
1597 self.mark_data_edited();
1598 }
1599 Ok((v, journal))
1600 }
1601 Err(e) => {
1602 if let Err(rb) = self.rollback_from_action_journal(&journal) {
1603 return Err(crate::engine::EditorError::TransactionFailed {
1604 reason: format!(
1605 "Engine::action_atomic rollback failed after error '{e}': {rb}"
1606 ),
1607 });
1608 }
1609 Err(e)
1610 }
1611 }
1612 }
1613
1614 pub fn action_with_logger<T>(
1621 &mut self,
1622 log: &mut crate::engine::ChangeLog,
1623 name: impl AsRef<str>,
1624 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
1625 ) -> Result<T, crate::engine::EditorError> {
1626 if self.action_depth != 0 {
1627 return Err(crate::engine::EditorError::TransactionFailed {
1628 reason: "Nested Engine::action calls are not supported (deterministic rule)"
1629 .to_string(),
1630 });
1631 }
1632
1633 self.action_depth = 1;
1634 let engine_ptr: *mut Engine<R> = self;
1635 let _guard = ActionDepthGuard {
1636 engine: engine_ptr,
1637 _marker: std::marker::PhantomData,
1638 };
1639
1640 let start_len = log.len();
1641 let name_str = name.as_ref().to_string();
1642 log.begin_compound(name_str.clone());
1643
1644 let res = self.action_atomic_impl(log, start_len, name_str, f);
1647
1648 match res {
1649 Ok((v, _journal)) => {
1650 log.end_compound();
1651 Ok(v)
1652 }
1653 Err(e) => {
1654 log.end_compound();
1656 log.truncate(start_len);
1657 Err(e)
1658 }
1659 }
1660 }
1661
1662 fn rollback_from_action_journal(
1663 &mut self,
1664 journal: &crate::engine::ActionJournal,
1665 ) -> Result<(), crate::engine::EditorError> {
1666 journal.graph.undo(&mut self.graph)?;
1668 self.apply_inverse_row_visibility_events(&journal.graph.events);
1670 self.apply_arrow_undo_batch(&journal.arrow, true);
1672 Ok(())
1673 }
1674
1675 fn rollback_from_change_events(
1676 &mut self,
1677 events: &[crate::engine::ChangeEvent],
1678 ) -> Result<(), crate::engine::EditorError> {
1679 use crate::engine::ChangeEvent;
1680
1681 {
1683 let mut editor = crate::engine::VertexEditor::new(&mut self.graph);
1684 let mut compound_stack: Vec<usize> = Vec::new();
1685 for ev in events.iter().rev() {
1686 match ev {
1687 ChangeEvent::CompoundEnd { depth } => compound_stack.push(*depth),
1688 ChangeEvent::CompoundStart { depth, .. } => {
1689 if compound_stack.last() == Some(depth) {
1690 compound_stack.pop();
1691 }
1692 }
1693 ChangeEvent::SetRowVisibility { .. } => {
1694 }
1696 _ => {
1697 editor.apply_inverse(ev.clone())?;
1698 }
1699 }
1700 }
1701 }
1702
1703 for ev in events.iter().rev() {
1705 self.apply_inverse_row_visibility_event(ev);
1706 }
1707
1708 for ev in events.iter().rev() {
1710 self.mirror_inverse_change_to_arrow(ev);
1711 }
1712
1713 Ok(())
1714 }
1715
1716 fn read_cell_formula_ast(&self, sheet: &str, row: u32, col: u32) -> Option<ASTNode> {
1717 let sheet_id = self.graph.sheet_id(sheet)?;
1718 let coord = Coord::from_excel(row, col, true, true);
1719 let cell = CellRef::new(sheet_id, coord);
1720 let vid = self.graph.get_vertex_for_cell(&cell)?;
1721 let ast_id = self.graph.get_formula_id(vid)?;
1722 self.graph
1723 .data_store()
1724 .retrieve_ast(ast_id, self.graph.sheet_reg())
1725 }
1726
1727 pub fn edit_with_logger<T>(
1728 &mut self,
1729 log: &mut crate::engine::ChangeLog,
1730 f: impl FnOnce(&mut crate::engine::VertexEditor) -> T,
1731 ) -> T {
1732 let start_len = log.len();
1734
1735 struct ArrowSpillReader<'a> {
1738 sheets: &'a crate::arrow_store::SheetStore,
1739 }
1740 impl crate::engine::graph::editor::vertex_editor::SpillValueReader for ArrowSpillReader<'_> {
1741 fn read_cell_value(
1742 &self,
1743 sheet: &str,
1744 row: u32,
1745 col: u32,
1746 ) -> Option<formualizer_common::LiteralValue> {
1747 use formualizer_common::LiteralValue;
1748 let asheet = self.sheets.sheet(sheet)?;
1749 let r0 = row.saturating_sub(1) as usize;
1750 let c0 = col.saturating_sub(1) as usize;
1751 let v = asheet.get_cell_value(r0, c0);
1752 if matches!(v, LiteralValue::Empty) {
1753 None
1754 } else {
1755 Some(v)
1756 }
1757 }
1758 }
1759
1760 let ret = {
1761 let spill_reader = ArrowSpillReader {
1762 sheets: &self.arrow_sheets,
1763 };
1764 let mut editor = crate::engine::VertexEditor::with_logger_and_spill_reader(
1765 &mut self.graph,
1766 log,
1767 &spill_reader,
1768 );
1769 f(&mut editor)
1770 };
1771
1772 for ev in &log.events()[start_len..] {
1775 self.mirror_forward_change_to_arrow(ev);
1776 }
1777
1778 ret
1779 }
1780
1781 pub fn undo_logged(
1782 &mut self,
1783 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
1784 log: &mut crate::engine::ChangeLog,
1785 ) -> Result<(), crate::engine::EditorError> {
1786 let batch = undo.undo(&mut self.graph, log)?;
1787 for item in batch.iter().rev() {
1788 self.apply_inverse_row_visibility_event(&item.event);
1789 }
1790 self.mirror_undo_batch_to_arrow(&batch);
1791 Ok(())
1792 }
1793
1794 pub fn redo_logged(
1795 &mut self,
1796 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
1797 log: &mut crate::engine::ChangeLog,
1798 ) -> Result<(), crate::engine::EditorError> {
1799 let batch = undo.redo(&mut self.graph, log)?;
1800 for item in &batch {
1801 self.apply_forward_row_visibility_event(&item.event);
1802 }
1803 self.mirror_redo_batch_to_arrow(&batch);
1804 Ok(())
1805 }
1806
1807 pub fn undo_action(
1811 &mut self,
1812 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
1813 ) -> Result<(), crate::engine::EditorError> {
1814 let Some(journal) = undo.pop_undo_action() else {
1815 return Ok(());
1816 };
1817
1818 journal.graph.undo(&mut self.graph)?;
1819 self.apply_inverse_row_visibility_events(&journal.graph.events);
1820 self.apply_arrow_undo_batch(&journal.arrow, true);
1821
1822 undo.push_redo_action(journal);
1823 Ok(())
1824 }
1825
1826 pub fn redo_action(
1830 &mut self,
1831 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
1832 ) -> Result<(), crate::engine::EditorError> {
1833 let Some(journal) = undo.pop_redo_action() else {
1834 return Ok(());
1835 };
1836
1837 journal.graph.redo(&mut self.graph)?;
1838 self.apply_forward_row_visibility_events(&journal.graph.events);
1839 self.apply_arrow_undo_batch(&journal.arrow, false);
1840
1841 undo.push_done_action(journal);
1842 Ok(())
1843 }
1844
1845 fn cellref_to_sheet_row_col(&self, addr: &crate::reference::CellRef) -> (String, u32, u32) {
1846 let sheet = self.graph.sheet_name(addr.sheet_id).to_string();
1847 let row = addr.coord.row() + 1;
1849 let col = addr.coord.col() + 1;
1850 (sheet, row, col)
1851 }
1852
1853 fn mirror_undo_batch_to_arrow(
1854 &mut self,
1855 batch: &[crate::engine::graph::editor::undo_engine::UndoBatchItem],
1856 ) {
1857 for item in batch.iter().rev() {
1859 self.mirror_inverse_change_to_arrow(&item.event);
1860 }
1861 }
1862
1863 fn mirror_redo_batch_to_arrow(
1864 &mut self,
1865 batch: &[crate::engine::graph::editor::undo_engine::UndoBatchItem],
1866 ) {
1867 for item in batch.iter() {
1869 self.mirror_forward_change_to_arrow(&item.event);
1870 }
1871 }
1872
1873 fn mirror_inverse_change_to_arrow(&mut self, ev: &crate::engine::ChangeEvent) {
1874 use crate::engine::ChangeEvent;
1875 use formualizer_common::LiteralValue;
1876
1877 match ev {
1878 ChangeEvent::SetValue {
1879 addr,
1880 old_value,
1881 old_formula,
1882 ..
1883 } => {
1884 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
1885 if old_formula.is_some() {
1886 self.clear_delta_overlay_cell(&sheet, row, col);
1887 } else {
1888 let v = old_value.clone().unwrap_or(LiteralValue::Empty);
1889 self.mirror_value_to_overlay(&sheet, row, col, &v);
1890 }
1891 }
1892 ChangeEvent::SetFormula {
1893 addr,
1894 old_value,
1895 old_formula,
1896 ..
1897 } => {
1898 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
1899 if old_formula.is_some() {
1900 self.clear_delta_overlay_cell(&sheet, row, col);
1901 } else {
1902 let v = old_value.clone().unwrap_or(LiteralValue::Empty);
1903 self.mirror_value_to_overlay(&sheet, row, col, &v);
1904 }
1905 }
1906 ChangeEvent::SpillCommitted { old, new, .. } => {
1907 self.mirror_spill_snapshot(new, true);
1909 if let Some(snap) = old {
1910 self.mirror_spill_snapshot(snap, false);
1911 }
1912 }
1913 ChangeEvent::SpillCleared { old, .. } => {
1914 self.mirror_spill_snapshot(old, false);
1916 }
1917 ChangeEvent::SetRowVisibility { .. } => {
1918 }
1920 _ => {}
1921 }
1922 }
1923
1924 fn mirror_forward_change_to_arrow(&mut self, ev: &crate::engine::ChangeEvent) {
1925 use crate::engine::ChangeEvent;
1926
1927 match ev {
1928 ChangeEvent::SetValue { addr, new, .. } => {
1929 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
1930 self.mirror_value_to_overlay(&sheet, row, col, new);
1931 }
1932 ChangeEvent::SetFormula { addr, .. } => {
1933 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
1934 self.clear_delta_overlay_cell(&sheet, row, col);
1935 }
1937 ChangeEvent::SpillCommitted { old, new, .. } => {
1938 if let Some(snap) = old {
1939 self.mirror_spill_snapshot(snap, true);
1940 }
1941 self.mirror_spill_snapshot(new, false);
1942 }
1943 ChangeEvent::SpillCleared { old, .. } => {
1944 self.mirror_spill_snapshot(old, true);
1945 }
1946 ChangeEvent::SetRowVisibility { .. } => {
1947 }
1949 _ => {
1950 }
1952 }
1953 }
1954
1955 fn mirror_spill_snapshot(
1956 &mut self,
1957 snap: &crate::engine::graph::editor::change_log::SpillSnapshot,
1958 clear_only: bool,
1959 ) {
1960 use formualizer_common::LiteralValue;
1961
1962 let mut i = 0usize;
1963 for row in &snap.values {
1964 for v in row {
1965 if let Some(cell) = snap.target_cells.get(i) {
1966 let (sheet, r, c) = self.cellref_to_sheet_row_col(cell);
1967 let out = if clear_only {
1968 LiteralValue::Empty
1969 } else {
1970 v.clone()
1971 };
1972 self.mirror_value_to_computed_overlay(&sheet, r, c, &out);
1973 }
1974 i += 1;
1975 }
1976 }
1977 if clear_only {
1979 for cell in snap.target_cells.iter().skip(i) {
1980 let (sheet, r, c) = self.cellref_to_sheet_row_col(cell);
1981 self.mirror_value_to_computed_overlay(&sheet, r, c, &LiteralValue::Empty);
1982 }
1983 }
1984 }
1985
1986 pub fn set_default_sheet_by_name(&mut self, name: &str) {
1987 self.graph.set_default_sheet_by_name(name);
1988 }
1989
1990 pub fn set_default_sheet_by_id(&mut self, id: SheetId) {
1991 self.graph.set_default_sheet_by_id(id);
1992 }
1993
1994 pub fn set_sheet_index_mode(&mut self, mode: crate::engine::SheetIndexMode) {
1995 self.graph.set_sheet_index_mode(mode);
1996 }
1997
1998 fn clear_cached_static_schedule(&mut self) {
1999 self.cached_static_schedule = None;
2000 }
2001
2002 pub fn mark_data_edited(&mut self) {
2005 self.snapshot_id
2006 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2007 self.has_edited = true;
2008 }
2009
2010 pub fn mark_topology_edited(&mut self) {
2012 self.snapshot_id
2013 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2014 self.topology_epoch = self.topology_epoch.wrapping_add(1);
2015 self.clear_cached_static_schedule();
2016 self.has_edited = true;
2017 }
2018
2019 pub fn sheet_store(&self) -> &SheetStore {
2021 &self.arrow_sheets
2022 }
2023
2024 pub fn sheet_store_mut(&mut self) -> &mut SheetStore {
2026 &mut self.arrow_sheets
2027 }
2028
2029 pub fn stage_formula_text(&mut self, sheet: &str, row: u32, col: u32, text: String) {
2031 self.staged_formulas
2032 .entry(sheet.to_string())
2033 .or_default()
2034 .push((row, col, text));
2035 }
2036
2037 pub fn get_staged_formula_text(&self, sheet: &str, row: u32, col: u32) -> Option<String> {
2039 self.staged_formulas.get(sheet).and_then(|v| {
2040 v.iter()
2041 .find(|(r, c, _)| *r == row && *c == col)
2042 .map(|(_, _, s)| s.clone())
2043 })
2044 }
2045
2046 pub fn formula_parse_diagnostics(&self) -> &[FormulaParseDiagnostic] {
2047 &self.formula_parse_diagnostics
2048 }
2049
2050 pub fn take_formula_parse_diagnostics(&mut self) -> Vec<FormulaParseDiagnostic> {
2051 std::mem::take(&mut self.formula_parse_diagnostics)
2052 }
2053
2054 pub fn clear_formula_parse_diagnostics(&mut self) {
2055 self.formula_parse_diagnostics.clear();
2056 }
2057
2058 pub fn handle_formula_parse_error(
2059 &mut self,
2060 sheet: &str,
2061 row: u32,
2062 col: u32,
2063 formula: &str,
2064 message: String,
2065 ) -> Result<Option<ASTNode>, ExcelError> {
2066 let policy = self.config.formula_parse_policy;
2067
2068 if policy == FormulaParsePolicy::Strict {
2069 let col_a1 = col_letters_from_1based(col).unwrap_or_else(|_| "?".to_string());
2070 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(format!(
2071 "Formula parse error at {sheet}!{col_a1}{row}: {message}"
2072 )));
2073 }
2074
2075 self.formula_parse_diagnostics.push(FormulaParseDiagnostic {
2076 sheet: sheet.to_string(),
2077 row,
2078 col,
2079 formula: formula.to_string(),
2080 message: message.clone(),
2081 policy,
2082 });
2083
2084 match policy {
2085 FormulaParsePolicy::Strict => unreachable!(),
2086 FormulaParsePolicy::KeepCachedValue => Ok(None),
2087 FormulaParsePolicy::AsText => Ok(Some(ASTNode::new(
2088 ASTNodeType::Literal(LiteralValue::Text(formula.to_string())),
2089 None,
2090 ))),
2091 FormulaParsePolicy::CoerceToError => {
2092 let err = ExcelError::new(ExcelErrorKind::Error)
2093 .with_message(format!("Malformed formula: {message}"));
2094 Ok(Some(ASTNode::new(
2095 ASTNodeType::Literal(LiteralValue::Error(err)),
2096 None,
2097 )))
2098 }
2099 }
2100 }
2101
2102 pub fn build_graph_all(&mut self) -> Result<(), formualizer_parse::ExcelError> {
2104 if self.staged_formulas.is_empty() {
2105 return Ok(());
2106 }
2107 let staged = std::mem::take(&mut self.staged_formulas);
2109 for sheet in staged.keys() {
2110 let _ = self.add_sheet(sheet);
2111 }
2112
2113 let mut prepared: PreparedFormulaBatches = Vec::new();
2115 for (sheet, entries) in staged {
2116 let mut formulas: Vec<ParsedFormulaEntry> = Vec::new();
2117 let mut cache: rustc_hash::FxHashMap<String, ASTNode> =
2118 rustc_hash::FxHashMap::default();
2119 cache.reserve(4096);
2120
2121 for (row, col, txt) in entries {
2122 let key = if txt.starts_with('=') {
2123 txt
2124 } else {
2125 format!("={txt}")
2126 };
2127 let ast = if let Some(p) = cache.get(&key) {
2128 Some(p.clone())
2129 } else {
2130 match formualizer_parse::parser::parse(&key) {
2131 Ok(parsed) => {
2132 cache.insert(key.clone(), parsed.clone());
2133 Some(parsed)
2134 }
2135 Err(e) => {
2136 self.handle_formula_parse_error(&sheet, row, col, &key, e.to_string())?
2137 }
2138 }
2139 };
2140
2141 if let Some(ast) = ast {
2142 formulas.push((row, col, ast));
2143 }
2144 }
2145
2146 if !formulas.is_empty() {
2147 prepared.push((sheet, formulas));
2148 }
2149 }
2150
2151 if !prepared.is_empty() {
2152 let mut builder = self.begin_bulk_ingest();
2153 for (sheet, formulas) in prepared {
2154 let sid = builder.add_sheet(&sheet);
2155 builder.add_formulas(sid, formulas.into_iter());
2156 }
2157 let _ = builder.finish();
2158 }
2159 Ok(())
2160 }
2161
2162 pub fn build_graph_for_sheets<'a, I: IntoIterator<Item = &'a str>>(
2164 &mut self,
2165 sheets: I,
2166 ) -> Result<(), formualizer_parse::ExcelError> {
2167 let mut collected: StagedFormulaBatches = Vec::new();
2168 for s in sheets {
2169 if let Some(entries) = self.staged_formulas.remove(s) {
2170 collected.push((s.to_string(), entries));
2171 }
2172 }
2173
2174 if collected.is_empty() {
2175 return Ok(());
2176 }
2177
2178 for (sheet, _) in &collected {
2179 let _ = self.add_sheet(sheet);
2180 }
2181
2182 let mut prepared: PreparedFormulaBatches = Vec::new();
2184 let mut cache: rustc_hash::FxHashMap<String, ASTNode> = rustc_hash::FxHashMap::default();
2185 cache.reserve(4096);
2186
2187 for (sheet, entries) in collected {
2188 let mut formulas: Vec<ParsedFormulaEntry> = Vec::new();
2189 for (row, col, txt) in entries {
2190 let key = if txt.starts_with('=') {
2191 txt
2192 } else {
2193 format!("={txt}")
2194 };
2195 let ast = if let Some(p) = cache.get(&key) {
2196 Some(p.clone())
2197 } else {
2198 match formualizer_parse::parser::parse(&key) {
2199 Ok(parsed) => {
2200 cache.insert(key.clone(), parsed.clone());
2201 Some(parsed)
2202 }
2203 Err(e) => {
2204 self.handle_formula_parse_error(&sheet, row, col, &key, e.to_string())?
2205 }
2206 }
2207 };
2208
2209 if let Some(ast) = ast {
2210 formulas.push((row, col, ast));
2211 }
2212 }
2213 if !formulas.is_empty() {
2214 prepared.push((sheet, formulas));
2215 }
2216 }
2217
2218 if !prepared.is_empty() {
2219 let mut builder = self.begin_bulk_ingest();
2220 for (sheet, formulas) in prepared {
2221 let sid = builder.add_sheet(&sheet);
2222 builder.add_formulas(sid, formulas.into_iter());
2223 }
2224 let _ = builder.finish();
2225 }
2226 Ok(())
2227 }
2228
2229 pub fn begin_bulk_ingest_arrow(
2231 &mut self,
2232 ) -> crate::engine::arrow_ingest::ArrowBulkIngestBuilder<'_, R> {
2233 crate::engine::arrow_ingest::ArrowBulkIngestBuilder::new(self)
2234 }
2235
2236 pub fn begin_bulk_update_arrow(
2238 &mut self,
2239 ) -> crate::engine::arrow_ingest::ArrowBulkUpdateBuilder<'_, R> {
2240 crate::engine::arrow_ingest::ArrowBulkUpdateBuilder::new(self)
2241 }
2242
2243 fn ensure_known_sheet_id(&self, sheet: &str) -> Result<SheetId, crate::engine::EditorError> {
2244 self.graph.sheet_id(sheet).ok_or(
2245 crate::engine::graph::editor::vertex_editor::EditorError::InvalidName {
2246 name: sheet.to_string(),
2247 reason: "Unknown sheet".to_string(),
2248 },
2249 )
2250 }
2251
2252 fn normalize_row_1based(row_1based: u32) -> Result<u32, crate::engine::EditorError> {
2253 if row_1based == 0 {
2254 return Err(crate::engine::EditorError::OutOfBounds { row: 0, col: 0 });
2255 }
2256 Ok(row_1based - 1)
2257 }
2258
2259 fn normalize_row_range_1based(
2260 start_row_1based: u32,
2261 end_row_1based: u32,
2262 ) -> Result<(u32, u32), crate::engine::EditorError> {
2263 if start_row_1based == 0 || end_row_1based == 0 {
2264 return Err(crate::engine::EditorError::OutOfBounds { row: 0, col: 0 });
2265 }
2266 if start_row_1based > end_row_1based {
2267 return Err(crate::engine::EditorError::TransactionFailed {
2268 reason: "Row range start is greater than end".to_string(),
2269 });
2270 }
2271 Ok((start_row_1based - 1, end_row_1based - 1))
2272 }
2273
2274 fn invalidate_row_visibility_mask_cache(&self) {
2275 if let Ok(mut cache) = self.row_visibility_mask_cache.write() {
2276 cache.clear();
2277 }
2278 }
2279
2280 fn set_row_hidden_by_sheet_id(
2281 &mut self,
2282 sheet_id: SheetId,
2283 row0: u32,
2284 hidden: bool,
2285 source: RowVisibilitySource,
2286 ) -> bool {
2287 let changed = {
2288 let state = self.row_visibility.entry(sheet_id).or_default();
2289 state.set_row_hidden(row0, hidden, source)
2290 };
2291
2292 let remove_entry = self
2293 .row_visibility
2294 .get(&sheet_id)
2295 .map(|state| state.is_empty())
2296 .unwrap_or(false);
2297 if remove_entry {
2298 self.row_visibility.remove(&sheet_id);
2299 }
2300
2301 if changed {
2302 self.invalidate_row_visibility_mask_cache();
2303 }
2304
2305 changed
2306 }
2307
2308 fn set_rows_hidden_by_sheet_id(
2309 &mut self,
2310 sheet_id: SheetId,
2311 start_row0: u32,
2312 end_row0: u32,
2313 hidden: bool,
2314 source: RowVisibilitySource,
2315 ) -> bool {
2316 let changed = {
2317 let state = self.row_visibility.entry(sheet_id).or_default();
2318 state.set_rows_hidden(start_row0, end_row0, hidden, source)
2319 };
2320
2321 let remove_entry = self
2322 .row_visibility
2323 .get(&sheet_id)
2324 .map(|state| state.is_empty())
2325 .unwrap_or(false);
2326 if remove_entry {
2327 self.row_visibility.remove(&sheet_id);
2328 }
2329
2330 if changed {
2331 self.invalidate_row_visibility_mask_cache();
2332 }
2333
2334 changed
2335 }
2336
2337 fn shift_row_visibility_insert(&mut self, sheet_id: SheetId, before0: u32, count: u32) {
2338 if count == 0 {
2339 return;
2340 }
2341 let mut changed = false;
2342 let remove_entry = if let Some(state) = self.row_visibility.get_mut(&sheet_id) {
2343 changed = state.insert_rows(before0, count);
2344 state.is_empty()
2345 } else {
2346 false
2347 };
2348 if remove_entry {
2349 self.row_visibility.remove(&sheet_id);
2350 }
2351 if changed {
2352 self.invalidate_row_visibility_mask_cache();
2353 }
2354 }
2355
2356 fn shift_row_visibility_delete(&mut self, sheet_id: SheetId, start0: u32, count: u32) {
2357 if count == 0 {
2358 return;
2359 }
2360 let mut changed = false;
2361 let remove_entry = if let Some(state) = self.row_visibility.get_mut(&sheet_id) {
2362 changed = state.delete_rows(start0, count);
2363 state.is_empty()
2364 } else {
2365 false
2366 };
2367 if remove_entry {
2368 self.row_visibility.remove(&sheet_id);
2369 }
2370 if changed {
2371 self.invalidate_row_visibility_mask_cache();
2372 }
2373 }
2374
2375 fn apply_inverse_row_visibility_event(&mut self, event: &crate::engine::ChangeEvent) {
2376 if let crate::engine::ChangeEvent::SetRowVisibility {
2377 sheet_id,
2378 row0,
2379 source,
2380 old_hidden,
2381 ..
2382 } = event
2383 {
2384 let _ = self.set_row_hidden_by_sheet_id(*sheet_id, *row0, *old_hidden, *source);
2385 }
2386 }
2387
2388 fn apply_forward_row_visibility_event(&mut self, event: &crate::engine::ChangeEvent) {
2389 if let crate::engine::ChangeEvent::SetRowVisibility {
2390 sheet_id,
2391 row0,
2392 source,
2393 new_hidden,
2394 ..
2395 } = event
2396 {
2397 let _ = self.set_row_hidden_by_sheet_id(*sheet_id, *row0, *new_hidden, *source);
2398 }
2399 }
2400
2401 fn apply_inverse_row_visibility_events(&mut self, events: &[crate::engine::ChangeEvent]) {
2402 for event in events.iter().rev() {
2403 self.apply_inverse_row_visibility_event(event);
2404 }
2405 }
2406
2407 fn apply_forward_row_visibility_events(&mut self, events: &[crate::engine::ChangeEvent]) {
2408 for event in events {
2409 self.apply_forward_row_visibility_event(event);
2410 }
2411 }
2412
2413 pub fn set_row_hidden(
2414 &mut self,
2415 sheet: &str,
2416 row_1based: u32,
2417 hidden: bool,
2418 source: RowVisibilitySource,
2419 ) -> Result<(), crate::engine::EditorError> {
2420 let sheet_id = self.ensure_known_sheet_id(sheet)?;
2421 let row0 = Self::normalize_row_1based(row_1based)?;
2422 if self.set_row_hidden_by_sheet_id(sheet_id, row0, hidden, source) {
2423 self.mark_data_edited();
2424 }
2425 Ok(())
2426 }
2427
2428 pub fn set_rows_hidden(
2429 &mut self,
2430 sheet: &str,
2431 start_row_1based: u32,
2432 end_row_1based: u32,
2433 hidden: bool,
2434 source: RowVisibilitySource,
2435 ) -> Result<(), crate::engine::EditorError> {
2436 let sheet_id = self.ensure_known_sheet_id(sheet)?;
2437 let (start_row0, end_row0) =
2438 Self::normalize_row_range_1based(start_row_1based, end_row_1based)?;
2439 if self.set_rows_hidden_by_sheet_id(sheet_id, start_row0, end_row0, hidden, source) {
2440 self.mark_data_edited();
2441 }
2442 Ok(())
2443 }
2444
2445 pub fn is_row_hidden(
2446 &self,
2447 sheet: &str,
2448 row_1based: u32,
2449 source: Option<RowVisibilitySource>,
2450 ) -> Option<bool> {
2451 let sheet_id = self.graph.sheet_id(sheet)?;
2452 let row0 = row_1based.checked_sub(1)?;
2453 Some(
2454 self.row_visibility
2455 .get(&sheet_id)
2456 .map(|state| state.is_row_hidden(row0, source))
2457 .unwrap_or(false),
2458 )
2459 }
2460
2461 pub fn row_visibility_version(&self, sheet: &str) -> Option<u64> {
2462 let sheet_id = self.graph.sheet_id(sheet)?;
2463 Some(
2464 self.row_visibility
2465 .get(&sheet_id)
2466 .map(|state| state.version())
2467 .unwrap_or(0),
2468 )
2469 }
2470
2471 fn build_row_visibility_mask_for_view(
2472 &self,
2473 view: &RangeView<'_>,
2474 mode: VisibilityMaskMode,
2475 ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
2476 let sheet_rows = view.sheet().nrows as usize;
2477 if sheet_rows == 0 || view.start_row() >= sheet_rows {
2478 return Some(std::sync::Arc::new(arrow_array::BooleanArray::new_null(0)));
2479 }
2480
2481 let sheet_id = self.graph.sheet_id(view.sheet_name())?;
2482 let start_row0 = view.start_row() as u32;
2483 let end_row0 = view.end_row().min(sheet_rows.saturating_sub(1)) as u32;
2484 let version = self
2485 .row_visibility
2486 .get(&sheet_id)
2487 .map(|state| state.version())
2488 .unwrap_or(0);
2489 let key = VisibilityMaskCacheKey {
2490 sheet_id,
2491 start_row0,
2492 end_row0,
2493 mode,
2494 version,
2495 };
2496
2497 if let Ok(cache) = self.row_visibility_mask_cache.read()
2498 && let Some(mask) = cache.get(&key)
2499 {
2500 #[cfg(test)]
2501 visibility_mask_test_hooks::inc_hit();
2502 return Some(mask.clone());
2503 }
2504
2505 #[cfg(test)]
2506 visibility_mask_test_hooks::inc_miss();
2507
2508 let state = self.row_visibility.get(&sheet_id);
2509 let mut out = Vec::with_capacity((end_row0 - start_row0 + 1) as usize);
2510 for row0 in start_row0..=end_row0 {
2511 let manual_hidden = state
2512 .map(|s| s.is_row_hidden(row0, Some(RowVisibilitySource::Manual)))
2513 .unwrap_or(false);
2514 let filter_hidden = state
2515 .map(|s| s.is_row_hidden(row0, Some(RowVisibilitySource::Filter)))
2516 .unwrap_or(false);
2517
2518 let include = match mode {
2519 VisibilityMaskMode::IncludeAll => true,
2520 VisibilityMaskMode::ExcludeManualHidden => !manual_hidden,
2521 VisibilityMaskMode::ExcludeFilterHidden => !filter_hidden,
2522 VisibilityMaskMode::ExcludeManualOrFilterHidden => {
2523 !(manual_hidden || filter_hidden)
2524 }
2525 };
2526 out.push(include);
2527 }
2528
2529 let mask = std::sync::Arc::new(arrow_array::BooleanArray::from(out));
2530 if let Ok(mut cache) = self.row_visibility_mask_cache.write() {
2531 const MAX_CACHE_ENTRIES: usize = 4096;
2532 if cache.len() >= MAX_CACHE_ENTRIES {
2533 cache.clear();
2534 #[cfg(test)]
2535 visibility_mask_test_hooks::inc_eviction();
2536 }
2537 cache.insert(key, mask.clone());
2538 }
2539
2540 Some(mask)
2541 }
2542
2543 pub fn insert_rows(
2545 &mut self,
2546 sheet: &str,
2547 before: u32,
2548 count: u32,
2549 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
2550 {
2551 use crate::engine::graph::editor::vertex_editor::VertexEditor;
2552 let sheet_id = self.ensure_known_sheet_id(sheet)?;
2553 let before0 = before.saturating_sub(1);
2554 let summary = {
2555 let mut editor = VertexEditor::new(&mut self.graph);
2556 editor.insert_rows(sheet_id, before0, count)?
2557 };
2558 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
2559 let before0 = before0 as usize;
2560 asheet.insert_rows(before0, count as usize);
2561 }
2562 self.shift_row_visibility_insert(sheet_id, before0, count);
2563 self.mark_topology_edited();
2564 Ok(summary)
2565 }
2566
2567 pub fn delete_rows(
2569 &mut self,
2570 sheet: &str,
2571 start: u32,
2572 count: u32,
2573 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
2574 {
2575 use crate::engine::graph::editor::vertex_editor::VertexEditor;
2576 let sheet_id = self.ensure_known_sheet_id(sheet)?;
2577 let start0 = start.saturating_sub(1);
2578 let summary = {
2579 let mut editor = VertexEditor::new(&mut self.graph);
2580 editor.delete_rows(sheet_id, start0, count)?
2581 };
2582 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
2583 let start0 = start0 as usize;
2584 asheet.delete_rows(start0, count as usize);
2585 }
2586 self.shift_row_visibility_delete(sheet_id, start0, count);
2587 self.mark_topology_edited();
2588 Ok(summary)
2589 }
2590
2591 pub fn insert_columns(
2593 &mut self,
2594 sheet: &str,
2595 before: u32,
2596 count: u32,
2597 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
2598 {
2599 use crate::engine::graph::editor::vertex_editor::VertexEditor;
2600 let sheet_id = self.graph.sheet_id(sheet).ok_or(
2601 crate::engine::graph::editor::vertex_editor::EditorError::InvalidName {
2602 name: sheet.to_string(),
2603 reason: "Unknown sheet".to_string(),
2604 },
2605 )?;
2606 let before0 = before.saturating_sub(1);
2607 let summary = {
2608 let mut editor = VertexEditor::new(&mut self.graph);
2609 editor.insert_columns(sheet_id, before0, count)?
2610 };
2611 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
2612 let before0 = before0 as usize;
2613 asheet.insert_columns(before0, count as usize);
2614 }
2615 self.mark_topology_edited();
2616 Ok(summary)
2617 }
2618
2619 pub fn delete_columns(
2621 &mut self,
2622 sheet: &str,
2623 start: u32,
2624 count: u32,
2625 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
2626 {
2627 use crate::engine::graph::editor::vertex_editor::VertexEditor;
2628 let sheet_id = self.graph.sheet_id(sheet).ok_or(
2629 crate::engine::graph::editor::vertex_editor::EditorError::InvalidName {
2630 name: sheet.to_string(),
2631 reason: "Unknown sheet".to_string(),
2632 },
2633 )?;
2634 let start0 = start.saturating_sub(1);
2635 let summary = {
2636 let mut editor = VertexEditor::new(&mut self.graph);
2637 editor.delete_columns(sheet_id, start0, count)?
2638 };
2639 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
2640 let start0 = start0 as usize;
2641 asheet.delete_columns(start0, count as usize);
2642 }
2643 self.mark_topology_edited();
2644 Ok(summary)
2645 }
2646 fn arrow_used_row_bounds(
2648 &self,
2649 sheet: &str,
2650 start_col: u32,
2651 end_col: u32,
2652 ) -> Option<(u32, u32)> {
2653 let a = self.sheet_store().sheet(sheet)?;
2654 if a.columns.is_empty() {
2655 return None;
2656 }
2657 let sc0 = start_col.saturating_sub(1) as usize;
2658 let ec0 = end_col.saturating_sub(1) as usize;
2659 let col_hi = a.columns.len().saturating_sub(1);
2660 if sc0 > col_hi {
2661 return None;
2662 }
2663 let ec0 = ec0.min(col_hi);
2664 let snap = self.data_snapshot_id();
2666 let mut min_r0: Option<usize> = None;
2667 for ci in sc0..=ec0 {
2668 let sheet_id = self.graph.sheet_id(sheet)?;
2669 if let Some((Some(mv), _)) = self.row_bounds_cache.read().ok().and_then(|g| {
2670 g.as_ref()
2671 .and_then(|c| c.get_row_bounds(sheet_id, ci, snap))
2672 }) {
2673 let mv = mv as usize;
2674 min_r0 = Some(min_r0.map(|m| m.min(mv)).unwrap_or(mv));
2675 continue;
2676 }
2677 let (min_c, max_c) = Self::scan_column_used_bounds(a, ci);
2679 if let Ok(mut g) = self.row_bounds_cache.write() {
2680 g.get_or_insert_with(|| RowBoundsCache::new(snap))
2681 .put_row_bounds(sheet_id, ci, snap, (min_c, max_c));
2682 }
2683 if let Some(m) = min_c {
2684 min_r0 = Some(min_r0.map(|mm| mm.min(m as usize)).unwrap_or(m as usize));
2685 }
2686 }
2687 min_r0?;
2688 let mut max_r0: Option<usize> = None;
2689 for ci in sc0..=ec0 {
2690 let sheet_id = self.graph.sheet_id(sheet)?;
2691 if let Some((_, Some(mv))) = self.row_bounds_cache.read().ok().and_then(|g| {
2692 g.as_ref()
2693 .and_then(|c| c.get_row_bounds(sheet_id, ci, snap))
2694 }) {
2695 let mv = mv as usize;
2696 max_r0 = Some(max_r0.map(|m| m.max(mv)).unwrap_or(mv));
2697 continue;
2698 }
2699 let (_min_c, max_c) = Self::scan_column_used_bounds(a, ci);
2700 if let Ok(mut g) = self.row_bounds_cache.write() {
2701 g.get_or_insert_with(|| RowBoundsCache::new(snap))
2702 .put_row_bounds(sheet_id, ci, snap, (_min_c, max_c));
2703 }
2704 if let Some(m) = max_c {
2705 max_r0 = Some(max_r0.map(|mm| mm.max(m as usize)).unwrap_or(m as usize));
2706 }
2707 }
2708 match (min_r0, max_r0) {
2709 (Some(a0), Some(b0)) => Some(((a0 as u32) + 1, (b0 as u32) + 1)),
2710 _ => None,
2711 }
2712 }
2713
2714 fn scan_column_used_bounds(
2715 a: &crate::arrow_store::ArrowSheet,
2716 ci: usize,
2717 ) -> (Option<u32>, Option<u32>) {
2718 let col = &a.columns[ci];
2719
2720 let mut min_r0: Option<u32> = None;
2722 for (chunk_idx, chunk) in col.chunks.iter().enumerate() {
2723 let tags = chunk.type_tag.values();
2724 for (off, &t) in tags.iter().enumerate() {
2725 let overlay_non_empty = chunk
2726 .overlay
2727 .get(off)
2728 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
2729 .unwrap_or(false)
2730 || chunk
2731 .computed_overlay
2732 .get(off)
2733 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
2734 .unwrap_or(false);
2735 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
2736 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
2737 break;
2738 };
2739 let row0 = chunk_start + off;
2740 min_r0 = Some(row0 as u32);
2741 break;
2742 }
2743 }
2744 if min_r0.is_some() {
2745 break;
2746 }
2747 }
2748 if min_r0.is_none() && !col.sparse_chunks.is_empty() {
2749 let mut sparse_idxs: Vec<usize> = col.sparse_chunks.keys().copied().collect();
2750 sparse_idxs.sort_unstable();
2751 for chunk_idx in sparse_idxs {
2752 let Some(chunk) = col.sparse_chunks.get(&chunk_idx) else {
2753 continue;
2754 };
2755 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
2756 continue;
2757 };
2758 let tags = chunk.type_tag.values();
2759 for (off, &t) in tags.iter().enumerate() {
2760 let overlay_non_empty = chunk
2761 .overlay
2762 .get(off)
2763 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
2764 .unwrap_or(false)
2765 || chunk
2766 .computed_overlay
2767 .get(off)
2768 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
2769 .unwrap_or(false);
2770 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
2771 let row0 = chunk_start + off;
2772 min_r0 = Some(row0 as u32);
2773 break;
2774 }
2775 }
2776 if min_r0.is_some() {
2777 break;
2778 }
2779 }
2780 }
2781
2782 let mut max_r0: Option<u32> = None;
2784 if !col.sparse_chunks.is_empty() {
2785 let mut sparse_idxs: Vec<usize> = col.sparse_chunks.keys().copied().collect();
2786 sparse_idxs.sort_unstable_by(|a, b| b.cmp(a));
2787 for chunk_idx in sparse_idxs {
2788 let Some(chunk) = col.sparse_chunks.get(&chunk_idx) else {
2789 continue;
2790 };
2791 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
2792 continue;
2793 };
2794 let tags = chunk.type_tag.values();
2795 for (rev_idx, &t) in tags.iter().enumerate().rev() {
2796 let overlay_non_empty = chunk
2797 .overlay
2798 .get(rev_idx)
2799 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
2800 .unwrap_or(false)
2801 || chunk
2802 .computed_overlay
2803 .get(rev_idx)
2804 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
2805 .unwrap_or(false);
2806 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
2807 let row0 = chunk_start + rev_idx;
2808 max_r0 = Some(row0 as u32);
2809 break;
2810 }
2811 }
2812 if max_r0.is_some() {
2813 break;
2814 }
2815 }
2816 }
2817 if max_r0.is_none() {
2818 for (chunk_idx, chunk) in col.chunks.iter().enumerate().rev() {
2819 let tags = chunk.type_tag.values();
2820 for (rev_idx, &t) in tags.iter().enumerate().rev() {
2821 let overlay_non_empty = chunk
2822 .overlay
2823 .get(rev_idx)
2824 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
2825 .unwrap_or(false)
2826 || chunk
2827 .computed_overlay
2828 .get(rev_idx)
2829 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
2830 .unwrap_or(false);
2831 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
2832 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
2833 break;
2834 };
2835 let row0 = chunk_start + rev_idx;
2836 max_r0 = Some(row0 as u32);
2837 break;
2838 }
2839 }
2840 if max_r0.is_some() {
2841 break;
2842 }
2843 }
2844 }
2845
2846 (min_r0, max_r0)
2847 }
2848
2849 fn arrow_used_col_bounds(
2851 &self,
2852 sheet: &str,
2853 start_row: u32,
2854 end_row: u32,
2855 ) -> Option<(u32, u32)> {
2856 let a = self.sheet_store().sheet(sheet)?;
2857 if a.columns.is_empty() {
2858 return None;
2859 }
2860 let sr0 = start_row.saturating_sub(1) as usize;
2861 let er0 = end_row.saturating_sub(1) as usize;
2862 if sr0 > er0 {
2863 return None;
2864 }
2865 let mut min_c0: Option<usize> = None;
2868 let mut max_c0: Option<usize> = None;
2869 for (ci, col) in a.columns.iter().enumerate() {
2871 let mut any_in_range = false;
2872
2873 let scan_chunk = |chunk_idx: usize, chunk: &crate::arrow_store::ColumnChunk| -> bool {
2874 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
2875 return false;
2876 };
2877 let chunk_len = chunk.type_tag.len();
2878 if chunk_len == 0 {
2879 return false;
2880 }
2881 let chunk_end = chunk_start + chunk_len.saturating_sub(1);
2882 if sr0 > chunk_end || er0 < chunk_start {
2884 return false;
2885 }
2886 let start_off = sr0.max(chunk_start) - chunk_start;
2887 let end_off = er0.min(chunk_end) - chunk_start;
2888 let tags = chunk.type_tag.values();
2889 for off in start_off..=end_off {
2890 let overlay_non_empty = chunk
2891 .overlay
2892 .get(off)
2893 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
2894 .unwrap_or(false)
2895 || chunk
2896 .computed_overlay
2897 .get(off)
2898 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
2899 .unwrap_or(false);
2900 if overlay_non_empty || tags[off] != crate::arrow_store::TypeTag::Empty as u8 {
2901 return true;
2902 }
2903 }
2904 false
2905 };
2906
2907 for (chunk_idx, chunk) in col.chunks.iter().enumerate() {
2908 if scan_chunk(chunk_idx, chunk) {
2909 any_in_range = true;
2910 break;
2911 }
2912 }
2913
2914 if !any_in_range && !col.sparse_chunks.is_empty() {
2915 for (&chunk_idx, chunk) in col.sparse_chunks.iter() {
2916 if scan_chunk(chunk_idx, chunk) {
2917 any_in_range = true;
2918 break;
2919 }
2920 }
2921 }
2922
2923 if any_in_range {
2924 min_c0 = Some(min_c0.map(|m| m.min(ci)).unwrap_or(ci));
2925 max_c0 = Some(max_c0.map(|m| m.max(ci)).unwrap_or(ci));
2926 }
2927 }
2928 match (min_c0, max_c0) {
2929 (Some(a0), Some(b0)) => Some(((a0 as u32) + 1, (b0 as u32) + 1)),
2930 _ => None,
2931 }
2932 }
2933
2934 fn formula_row_bounds_for_columns(
2935 &self,
2936 sheet: &str,
2937 start_col: u32,
2938 end_col: u32,
2939 ) -> Option<(u32, u32)> {
2940 let sheet_id = self.graph.sheet_id(sheet)?;
2941 let sc0 = start_col.saturating_sub(1);
2942 let ec0 = end_col.saturating_sub(1);
2943 let mut min_r0: Option<u32> = None;
2944 let mut max_r0: Option<u32> = None;
2945
2946 if let Some(index) = self.graph.sheet_index(sheet_id) {
2947 for vid in index.vertices_in_col_range(sc0, ec0) {
2948 if !matches!(
2949 self.graph.get_vertex_kind(vid),
2950 VertexKind::FormulaScalar | VertexKind::FormulaArray
2951 ) {
2952 continue;
2953 }
2954 let row0 = self.graph.vertex_coord(vid).row();
2955 min_r0 = Some(min_r0.map(|m| m.min(row0)).unwrap_or(row0));
2956 max_r0 = Some(max_r0.map(|m| m.max(row0)).unwrap_or(row0));
2957 }
2958 } else {
2959 for vid in self.graph.vertices_in_sheet(sheet_id) {
2960 if !matches!(
2961 self.graph.get_vertex_kind(vid),
2962 VertexKind::FormulaScalar | VertexKind::FormulaArray
2963 ) {
2964 continue;
2965 }
2966 let coord = self.graph.vertex_coord(vid);
2967 let col0 = coord.col();
2968 if col0 < sc0 || col0 > ec0 {
2969 continue;
2970 }
2971 let row0 = coord.row();
2972 min_r0 = Some(min_r0.map(|m| m.min(row0)).unwrap_or(row0));
2973 max_r0 = Some(max_r0.map(|m| m.max(row0)).unwrap_or(row0));
2974 }
2975 }
2976
2977 match (min_r0, max_r0) {
2978 (Some(a0), Some(b0)) => Some((a0 + 1, b0 + 1)),
2979 _ => None,
2980 }
2981 }
2982
2983 fn formula_col_bounds_for_rows(
2984 &self,
2985 sheet: &str,
2986 start_row: u32,
2987 end_row: u32,
2988 ) -> Option<(u32, u32)> {
2989 let sheet_id = self.graph.sheet_id(sheet)?;
2990 let sr0 = start_row.saturating_sub(1);
2991 let er0 = end_row.saturating_sub(1);
2992 let mut min_c0: Option<u32> = None;
2993 let mut max_c0: Option<u32> = None;
2994
2995 if let Some(index) = self.graph.sheet_index(sheet_id) {
2996 for vid in index.vertices_in_row_range(sr0, er0) {
2997 if !matches!(
2998 self.graph.get_vertex_kind(vid),
2999 VertexKind::FormulaScalar | VertexKind::FormulaArray
3000 ) {
3001 continue;
3002 }
3003 let col0 = self.graph.vertex_coord(vid).col();
3004 min_c0 = Some(min_c0.map(|m| m.min(col0)).unwrap_or(col0));
3005 max_c0 = Some(max_c0.map(|m| m.max(col0)).unwrap_or(col0));
3006 }
3007 } else {
3008 for vid in self.graph.vertices_in_sheet(sheet_id) {
3009 if !matches!(
3010 self.graph.get_vertex_kind(vid),
3011 VertexKind::FormulaScalar | VertexKind::FormulaArray
3012 ) {
3013 continue;
3014 }
3015 let coord = self.graph.vertex_coord(vid);
3016 let row0 = coord.row();
3017 if row0 < sr0 || row0 > er0 {
3018 continue;
3019 }
3020 let col0 = coord.col();
3021 min_c0 = Some(min_c0.map(|m| m.min(col0)).unwrap_or(col0));
3022 max_c0 = Some(max_c0.map(|m| m.max(col0)).unwrap_or(col0));
3023 }
3024 }
3025
3026 match (min_c0, max_c0) {
3027 (Some(a0), Some(b0)) => Some((a0 + 1, b0 + 1)),
3028 _ => None,
3029 }
3030 }
3031
3032 fn union_used_bounds(
3033 first: Option<(u32, u32)>,
3034 second: Option<(u32, u32)>,
3035 ) -> Option<(u32, u32)> {
3036 match (first, second) {
3037 (Some((a0, b0)), Some((a1, b1))) => Some((a0.min(a1), b0.max(b1))),
3038 (Some(bounds), None) | (None, Some(bounds)) => Some(bounds),
3039 (None, None) => None,
3040 }
3041 }
3042
3043 fn mirror_value_to_overlay(&mut self, sheet: &str, row: u32, col: u32, value: &LiteralValue) {
3046 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
3047 return;
3048 }
3049 if self.arrow_sheets.sheet(sheet).is_none() {
3050 self.arrow_sheets
3051 .sheets
3052 .push(crate::arrow_store::ArrowSheet {
3053 name: std::sync::Arc::<str>::from(sheet),
3054 columns: Vec::new(),
3055 nrows: 0,
3056 chunk_starts: Vec::new(),
3057 chunk_rows: 32 * 1024,
3058 });
3059 }
3060
3061 let row0 = row.saturating_sub(1) as usize;
3062 let col0 = col.saturating_sub(1) as usize;
3063
3064 let asheet = self
3065 .arrow_sheets
3066 .sheet_mut(sheet)
3067 .expect("ArrowSheet must exist");
3068
3069 let cur_cols = asheet.columns.len();
3070 if col0 >= cur_cols {
3071 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
3072 }
3073
3074 if row0 >= asheet.nrows as usize {
3075 if asheet.columns.is_empty() {
3076 asheet.insert_columns(0, 1);
3077 }
3078 asheet.ensure_row_capacity(row0 + 1);
3079 }
3080 if let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) {
3081 use crate::arrow_store::OverlayValue;
3082 let ov = match value {
3083 LiteralValue::Empty => OverlayValue::Empty,
3084 LiteralValue::Int(i) => OverlayValue::Number(*i as f64),
3085 LiteralValue::Number(n) => OverlayValue::Number(*n),
3086 LiteralValue::Boolean(b) => OverlayValue::Boolean(*b),
3087 LiteralValue::Text(s) => OverlayValue::Text(std::sync::Arc::from(s.clone())),
3088 LiteralValue::Error(e) => {
3089 OverlayValue::Error(crate::arrow_store::map_error_code(e.kind))
3090 }
3091 LiteralValue::Date(d) => {
3092 let dt = d.and_hms_opt(0, 0, 0).unwrap();
3093 let serial = crate::builtins::datetime::datetime_to_serial_for(
3094 self.config.date_system,
3095 &dt,
3096 );
3097 OverlayValue::DateTime(serial)
3098 }
3099 LiteralValue::DateTime(dt) => {
3100 let serial = crate::builtins::datetime::datetime_to_serial_for(
3101 self.config.date_system,
3102 dt,
3103 );
3104 OverlayValue::DateTime(serial)
3105 }
3106 LiteralValue::Time(t) => {
3107 let serial = t.num_seconds_from_midnight() as f64 / 86_400.0;
3108 OverlayValue::DateTime(serial)
3109 }
3110 LiteralValue::Duration(d) => {
3111 let serial = d.num_seconds() as f64 / 86_400.0;
3112 OverlayValue::Duration(serial)
3113 }
3114 LiteralValue::Pending => OverlayValue::Pending,
3115 LiteralValue::Array(_) => OverlayValue::Error(crate::arrow_store::map_error_code(
3116 formualizer_common::ExcelErrorKind::Value,
3117 )),
3118 };
3119 if let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) {
3120 let _ = ch.overlay.set(in_off, ov);
3121 let _ = ch.computed_overlay.remove(in_off);
3126 } else {
3127 return;
3128 }
3129 let abs_threshold = 1024usize;
3131 let frac_den = 50usize;
3132 let freed = asheet.maybe_compact_chunk(col0, ch_idx, abs_threshold, frac_den);
3133 if freed > 0 {
3134 self.overlay_compactions = self.overlay_compactions.saturating_add(1);
3135 }
3136 }
3137 }
3138
3139 fn clear_delta_overlay_cell(&mut self, sheet: &str, row: u32, col: u32) {
3144 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
3145 return;
3146 }
3147 let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) else {
3148 return;
3149 };
3150 let row0 = row.saturating_sub(1) as usize;
3151 let col0 = col.saturating_sub(1) as usize;
3152 if row0 >= asheet.nrows as usize {
3153 return;
3154 }
3155 if col0 >= asheet.columns.len() {
3156 return;
3157 }
3158 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
3159 return;
3160 };
3161 if let Some(ch) = asheet.columns[col0].chunk_mut(ch_idx) {
3162 let _ = ch.overlay.remove(in_off);
3163 }
3164 }
3165
3166 #[inline]
3167 fn overlay_value_to_literal(&self, ov: &crate::arrow_store::OverlayValue) -> LiteralValue {
3168 use crate::arrow_store::OverlayValue;
3169 match ov {
3170 OverlayValue::Empty => LiteralValue::Empty,
3171 OverlayValue::Number(n) => LiteralValue::Number(*n),
3172 OverlayValue::DateTime(serial) => LiteralValue::from_serial_number(*serial),
3173 OverlayValue::Duration(serial) => {
3174 let nanos_f = *serial * 86_400.0 * 1_000_000_000.0;
3175 let nanos = nanos_f.round().clamp(i64::MIN as f64, i64::MAX as f64) as i64;
3176 LiteralValue::Duration(chrono::Duration::nanoseconds(nanos))
3177 }
3178 OverlayValue::Boolean(b) => LiteralValue::Boolean(*b),
3179 OverlayValue::Text(s) => LiteralValue::Text((**s).to_string()),
3180 OverlayValue::Error(code) => {
3181 let kind = crate::arrow_store::unmap_error_code(*code);
3182 LiteralValue::Error(formualizer_common::ExcelError::new(kind))
3183 }
3184 OverlayValue::Pending => LiteralValue::Pending,
3185 }
3186 }
3187
3188 #[inline]
3189 fn literal_to_overlay_value(&self, value: &LiteralValue) -> crate::arrow_store::OverlayValue {
3190 use crate::arrow_store::OverlayValue;
3191 match value {
3192 LiteralValue::Empty => OverlayValue::Empty,
3193 LiteralValue::Int(i) => OverlayValue::Number(*i as f64),
3194 LiteralValue::Number(n) => OverlayValue::Number(*n),
3195 LiteralValue::Boolean(b) => OverlayValue::Boolean(*b),
3196 LiteralValue::Text(s) => OverlayValue::Text(std::sync::Arc::from(s.clone())),
3197 LiteralValue::Error(e) => {
3198 OverlayValue::Error(crate::arrow_store::map_error_code(e.kind))
3199 }
3200 LiteralValue::Date(d) => {
3201 let dt = d.and_hms_opt(0, 0, 0).unwrap();
3202 let serial =
3203 crate::builtins::datetime::datetime_to_serial_for(self.config.date_system, &dt);
3204 OverlayValue::DateTime(serial)
3205 }
3206 LiteralValue::DateTime(dt) => {
3207 let serial =
3208 crate::builtins::datetime::datetime_to_serial_for(self.config.date_system, dt);
3209 OverlayValue::DateTime(serial)
3210 }
3211 LiteralValue::Time(t) => {
3212 let serial = t.num_seconds_from_midnight() as f64 / 86_400.0;
3213 OverlayValue::DateTime(serial)
3214 }
3215 LiteralValue::Duration(d) => {
3216 let serial = d.num_seconds() as f64 / 86_400.0;
3217 OverlayValue::Duration(serial)
3218 }
3219 LiteralValue::Pending => OverlayValue::Pending,
3220 LiteralValue::Array(_) => OverlayValue::Error(crate::arrow_store::map_error_code(
3221 formualizer_common::ExcelErrorKind::Value,
3222 )),
3223 }
3224 }
3225
3226 fn read_delta_overlay_cell(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
3229 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
3230 return None;
3231 }
3232 let asheet = self.arrow_sheets.sheet(sheet)?;
3233 let row0 = row.saturating_sub(1) as usize;
3234 let col0 = col.saturating_sub(1) as usize;
3235 if row0 >= asheet.nrows as usize || col0 >= asheet.columns.len() {
3236 return None;
3237 }
3238 let (ch_idx, in_off) = asheet.chunk_of_row(row0)?;
3239 let ch = asheet.columns[col0].chunk(ch_idx)?;
3240 ch.overlay
3241 .get(in_off)
3242 .map(|ov| self.overlay_value_to_literal(ov))
3243 }
3244
3245 fn read_computed_overlay_cell(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
3248 if !(self.config.arrow_storage_enabled
3249 && self.config.delta_overlay_enabled
3250 && self.config.write_formula_overlay_enabled)
3251 {
3252 return None;
3253 }
3254 let asheet = self.arrow_sheets.sheet(sheet)?;
3255 let row0 = row.saturating_sub(1) as usize;
3256 let col0 = col.saturating_sub(1) as usize;
3257 if row0 >= asheet.nrows as usize || col0 >= asheet.columns.len() {
3258 return None;
3259 }
3260 let (ch_idx, in_off) = asheet.chunk_of_row(row0)?;
3261 let ch = asheet.columns[col0].chunk(ch_idx)?;
3262 ch.computed_overlay
3263 .get(in_off)
3264 .map(|ov| self.overlay_value_to_literal(ov))
3265 }
3266
3267 fn set_delta_overlay_cell_raw(
3268 &mut self,
3269 sheet: &str,
3270 row: u32,
3271 col: u32,
3272 value: Option<LiteralValue>,
3273 ) {
3274 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
3275 return;
3276 }
3277
3278 self.ensure_arrow_sheet(sheet);
3279 let ov_opt = value.as_ref().map(|v| self.literal_to_overlay_value(v));
3280 let row0 = row.saturating_sub(1) as usize;
3281 let col0 = col.saturating_sub(1) as usize;
3282 let asheet = self
3283 .arrow_sheets
3284 .sheet_mut(sheet)
3285 .expect("ArrowSheet must exist");
3286
3287 let cur_cols = asheet.columns.len();
3288 if col0 >= cur_cols {
3289 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
3290 }
3291 if row0 >= asheet.nrows as usize {
3292 if asheet.columns.is_empty() {
3293 asheet.insert_columns(0, 1);
3294 }
3295 asheet.ensure_row_capacity(row0 + 1);
3296 }
3297
3298 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
3299 return;
3300 };
3301 let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) else {
3302 return;
3303 };
3304
3305 if let Some(ov) = ov_opt {
3306 let _ = ch.overlay.set(in_off, ov);
3307 } else {
3308 let _ = ch.overlay.remove(in_off);
3309 }
3310 }
3311
3312 fn set_computed_overlay_cell_raw(
3313 &mut self,
3314 sheet: &str,
3315 row: u32,
3316 col: u32,
3317 value: Option<LiteralValue>,
3318 ) {
3319 if !(self.config.arrow_storage_enabled
3320 && self.config.delta_overlay_enabled
3321 && self.config.write_formula_overlay_enabled)
3322 {
3323 return;
3324 }
3325
3326 self.ensure_arrow_sheet(sheet);
3327 let ov_opt = value.as_ref().map(|v| self.literal_to_overlay_value(v));
3328 let row0 = row.saturating_sub(1) as usize;
3329 let col0 = col.saturating_sub(1) as usize;
3330 let asheet = self
3331 .arrow_sheets
3332 .sheet_mut(sheet)
3333 .expect("ArrowSheet must exist");
3334
3335 let cur_cols = asheet.columns.len();
3336 if col0 >= cur_cols {
3337 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
3338 }
3339 if row0 >= asheet.nrows as usize {
3340 if asheet.columns.is_empty() {
3341 asheet.insert_columns(0, 1);
3342 }
3343 asheet.ensure_row_capacity(row0 + 1);
3344 }
3345
3346 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
3347 return;
3348 };
3349 let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) else {
3350 return;
3351 };
3352
3353 let delta = if let Some(ov) = ov_opt {
3354 ch.computed_overlay.set(in_off, ov)
3355 } else {
3356 ch.computed_overlay.remove(in_off)
3357 };
3358 self.adjust_computed_overlay_bytes(delta);
3359 }
3360
3361 fn apply_arrow_undo_batch(&mut self, batch: &crate::engine::ArrowUndoBatch, undo: bool) {
3362 use crate::engine::ArrowOp;
3363
3364 let iter: Box<dyn Iterator<Item = &ArrowOp>> = if undo {
3365 Box::new(batch.ops.iter().rev())
3366 } else {
3367 Box::new(batch.ops.iter())
3368 };
3369
3370 for op in iter {
3371 match op {
3372 ArrowOp::SetDeltaCell {
3373 sheet_id,
3374 row0,
3375 col0,
3376 old,
3377 new,
3378 } => {
3379 let sheet = self.graph.sheet_name(*sheet_id).to_string();
3380 let v = if undo { old.clone() } else { new.clone() };
3381 self.set_delta_overlay_cell_raw(&sheet, row0 + 1, col0 + 1, v);
3382 }
3383 ArrowOp::SetComputedCell {
3384 sheet_id,
3385 row0,
3386 col0,
3387 old,
3388 new,
3389 } => {
3390 let sheet = self.graph.sheet_name(*sheet_id).to_string();
3391 let v = if undo { old.clone() } else { new.clone() };
3392 self.set_computed_overlay_cell_raw(&sheet, row0 + 1, col0 + 1, v);
3393 }
3394 ArrowOp::RestoreComputedRect {
3395 sheet_id,
3396 sr0,
3397 sc0,
3398 er0,
3399 ec0,
3400 old,
3401 new,
3402 } => {
3403 let sheet = self.graph.sheet_name(*sheet_id).to_string();
3404 let vals = if undo { old } else { new };
3405 let height = (*er0).saturating_sub(*sr0) as usize + 1;
3406 let width = (*ec0).saturating_sub(*sc0) as usize + 1;
3407 for r in 0..height {
3408 for c in 0..width {
3409 let v = vals
3410 .get(r)
3411 .and_then(|row| row.get(c))
3412 .cloned()
3413 .unwrap_or(LiteralValue::Empty);
3414 self.set_computed_overlay_cell_raw(
3415 &sheet,
3416 *sr0 + 1 + r as u32,
3417 *sc0 + 1 + c as u32,
3418 Some(v),
3419 );
3420 }
3421 }
3422 }
3423 ArrowOp::InsertRows {
3424 sheet_id,
3425 before0,
3426 count,
3427 } => {
3428 let sheet = self.graph.sheet_name(*sheet_id).to_string();
3429 self.ensure_arrow_sheet(&sheet);
3430 if let Some(asheet) = self.arrow_sheets.sheet_mut(&sheet) {
3431 if undo {
3432 asheet.delete_rows(*before0 as usize, *count as usize);
3433 } else {
3434 asheet.insert_rows(*before0 as usize, *count as usize);
3435 }
3436 }
3437 }
3438 ArrowOp::InsertCols {
3439 sheet_id,
3440 before0,
3441 count,
3442 } => {
3443 let sheet = self.graph.sheet_name(*sheet_id).to_string();
3444 self.ensure_arrow_sheet(&sheet);
3445 if let Some(asheet) = self.arrow_sheets.sheet_mut(&sheet) {
3446 if undo {
3447 asheet.delete_columns(*before0 as usize, *count as usize);
3448 } else {
3449 asheet.insert_columns(*before0 as usize, *count as usize);
3450 }
3451 }
3452 }
3453 }
3454 }
3455 }
3456
3457 fn record_spill_ops_into_arrow_undo(
3458 &mut self,
3459 undo: &mut crate::engine::ArrowUndoBatch,
3460 events: &[crate::engine::ChangeEvent],
3461 ) {
3462 use crate::engine::ChangeEvent;
3463 use formualizer_common::LiteralValue;
3464
3465 #[allow(clippy::type_complexity)]
3466 let rect_from_snapshot =
3467 |snap: &crate::engine::graph::editor::change_log::SpillSnapshot|
3468 -> Option<(SheetId, u32, u32, u32, u32, Vec<Vec<LiteralValue>>)> {
3469 if snap.target_cells.is_empty() {
3470 return None;
3471 }
3472 let sheet_id = snap.target_cells[0].sheet_id;
3473 let sr0 = snap.target_cells[0].coord.row();
3474 let sc0 = snap.target_cells[0].coord.col();
3475 if snap.values.is_empty() || snap.values[0].is_empty() {
3476 return None;
3477 }
3478 let h = snap.values.len() as u32;
3479 let w = snap.values[0].len() as u32;
3480 let er0 = sr0.saturating_add(h.saturating_sub(1));
3481 let ec0 = sc0.saturating_add(w.saturating_sub(1));
3482 Some((sheet_id, sr0, sc0, er0, ec0, snap.values.clone()))
3483 };
3484
3485 for ev in events {
3486 match ev {
3487 ChangeEvent::SpillCommitted { old, new, .. } => {
3488 if let Some((sid, sr0, sc0, er0, ec0, new_vals)) = rect_from_snapshot(new) {
3489 let old_vals = if let Some(old_snap) = old {
3490 rect_from_snapshot(old_snap)
3491 .map(|(_, _, _, _, _, v)| v)
3492 .unwrap_or_else(|| {
3493 vec![
3494 vec![LiteralValue::Empty; new_vals[0].len()];
3495 new_vals.len()
3496 ]
3497 })
3498 } else {
3499 vec![vec![LiteralValue::Empty; new_vals[0].len()]; new_vals.len()]
3500 };
3501 undo.record_restore_computed_rect(
3502 sid, sr0, sc0, er0, ec0, old_vals, new_vals,
3503 );
3504 }
3505 }
3506 ChangeEvent::SpillCleared { old, .. } => {
3507 if let Some((sid, sr0, sc0, er0, ec0, old_vals)) = rect_from_snapshot(old) {
3508 let new_vals =
3509 vec![vec![LiteralValue::Empty; old_vals[0].len()]; old_vals.len()];
3510 undo.record_restore_computed_rect(
3511 sid, sr0, sc0, er0, ec0, old_vals, new_vals,
3512 );
3513 }
3514 }
3515 _ => {}
3516 }
3517 }
3518 }
3519
3520 fn mirror_value_to_computed_overlay(
3526 &mut self,
3527 sheet: &str,
3528 row: u32,
3529 col: u32,
3530 value: &LiteralValue,
3531 ) {
3532 if !(self.config.arrow_storage_enabled
3533 && self.config.delta_overlay_enabled
3534 && self.config.write_formula_overlay_enabled)
3535 {
3536 return;
3537 }
3538 if self.computed_overlay_mirroring_disabled {
3539 return;
3540 }
3541
3542 if self.arrow_sheets.sheet(sheet).is_none() {
3543 self.arrow_sheets
3544 .sheets
3545 .push(crate::arrow_store::ArrowSheet {
3546 name: std::sync::Arc::<str>::from(sheet),
3547 columns: Vec::new(),
3548 nrows: 0,
3549 chunk_starts: Vec::new(),
3550 chunk_rows: 32 * 1024,
3551 });
3552 }
3553
3554 let row0 = row.saturating_sub(1) as usize;
3555 let col0 = col.saturating_sub(1) as usize;
3556
3557 let asheet = self
3558 .arrow_sheets
3559 .sheet_mut(sheet)
3560 .expect("ArrowSheet must exist");
3561
3562 let cur_cols = asheet.columns.len();
3563 if col0 >= cur_cols {
3564 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
3565 }
3566
3567 if row0 >= asheet.nrows as usize {
3568 if asheet.columns.is_empty() {
3569 asheet.insert_columns(0, 1);
3570 }
3571 asheet.ensure_row_capacity(row0 + 1);
3572 }
3573
3574 if let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) {
3575 use crate::arrow_store::OverlayValue;
3576 let ov = match value {
3577 LiteralValue::Empty => OverlayValue::Empty,
3578 LiteralValue::Int(i) => OverlayValue::Number(*i as f64),
3579 LiteralValue::Number(n) => OverlayValue::Number(*n),
3580 LiteralValue::Boolean(b) => OverlayValue::Boolean(*b),
3581 LiteralValue::Text(s) => OverlayValue::Text(std::sync::Arc::from(s.clone())),
3582 LiteralValue::Error(e) => {
3583 OverlayValue::Error(crate::arrow_store::map_error_code(e.kind))
3584 }
3585 LiteralValue::Date(d) => {
3586 let dt = d.and_hms_opt(0, 0, 0).unwrap();
3587 let serial = crate::builtins::datetime::datetime_to_serial_for(
3588 self.config.date_system,
3589 &dt,
3590 );
3591 OverlayValue::DateTime(serial)
3592 }
3593 LiteralValue::DateTime(dt) => {
3594 let serial = crate::builtins::datetime::datetime_to_serial_for(
3595 self.config.date_system,
3596 dt,
3597 );
3598 OverlayValue::DateTime(serial)
3599 }
3600 LiteralValue::Time(t) => {
3601 let serial = t.num_seconds_from_midnight() as f64 / 86_400.0;
3602 OverlayValue::DateTime(serial)
3603 }
3604 LiteralValue::Duration(d) => {
3605 let serial = d.num_seconds() as f64 / 86_400.0;
3606 OverlayValue::Duration(serial)
3607 }
3608 LiteralValue::Pending => OverlayValue::Pending,
3609 LiteralValue::Array(_) => OverlayValue::Error(crate::arrow_store::map_error_code(
3610 formualizer_common::ExcelErrorKind::Value,
3611 )),
3612 };
3613
3614 let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) else {
3615 return;
3616 };
3617
3618 let delta = ch.computed_overlay.set(in_off, ov);
3619 self.adjust_computed_overlay_bytes(delta);
3620
3621 if let Some(cap) = self.config.max_overlay_memory_bytes
3622 && self.computed_overlay_bytes_estimate > cap
3623 {
3624 self.disable_computed_overlay_mirroring_due_to_budget(cap);
3625 }
3626 }
3627 }
3628
3629 #[inline]
3630 fn adjust_computed_overlay_bytes(&mut self, delta: isize) {
3631 if delta >= 0 {
3632 self.computed_overlay_bytes_estimate = self
3633 .computed_overlay_bytes_estimate
3634 .saturating_add(delta as usize);
3635 } else {
3636 self.computed_overlay_bytes_estimate = self
3637 .computed_overlay_bytes_estimate
3638 .saturating_sub((-delta) as usize);
3639 }
3640 }
3641
3642 fn clear_all_computed_overlays(&mut self) {
3643 let mut freed_total = 0usize;
3644 for sh in self.arrow_sheets.sheets.iter_mut() {
3645 for col in sh.columns.iter_mut() {
3646 for ch in col.chunks.iter_mut() {
3647 freed_total = freed_total.saturating_add(ch.computed_overlay.clear());
3648 }
3649 for ch in col.sparse_chunks.values_mut() {
3650 freed_total = freed_total.saturating_add(ch.computed_overlay.clear());
3651 }
3652 }
3653 }
3654 self.computed_overlay_bytes_estimate = self
3655 .computed_overlay_bytes_estimate
3656 .saturating_sub(freed_total);
3657 }
3658
3659 fn disable_computed_overlay_mirroring_due_to_budget(&mut self, _cap: usize) {
3660 self.compact_all_computed_overlays();
3663 }
3664
3665 fn compact_all_computed_overlays(&mut self) {
3668 let mut freed_total = 0usize;
3669 for sheet in self.arrow_sheets.sheets.iter_mut() {
3670 for col_idx in 0..sheet.columns.len() {
3671 let num_dense = sheet.columns[col_idx].chunks.len();
3673 for ch_idx in 0..num_dense {
3674 freed_total += sheet.compact_computed_overlay_chunk(col_idx, ch_idx);
3675 }
3676 let sparse_keys: Vec<usize> = sheet.columns[col_idx]
3678 .sparse_chunks
3679 .keys()
3680 .copied()
3681 .collect();
3682 for ch_idx in sparse_keys {
3683 freed_total += sheet.compact_computed_overlay_sparse_chunk(col_idx, ch_idx);
3684 }
3685 }
3686 }
3687 self.computed_overlay_bytes_estimate = self
3688 .computed_overlay_bytes_estimate
3689 .saturating_sub(freed_total);
3690 self.overlay_compactions = self.overlay_compactions.saturating_add(1);
3691 }
3692
3693 fn mirror_vertex_value_to_overlay(&mut self, vertex_id: VertexId, value: &LiteralValue) {
3694 if !(self.config.arrow_storage_enabled
3695 && self.config.delta_overlay_enabled
3696 && self.config.write_formula_overlay_enabled)
3697 {
3698 return;
3699 }
3700 if !matches!(
3701 self.graph.get_vertex_kind(vertex_id),
3702 VertexKind::FormulaScalar | VertexKind::FormulaArray
3703 ) {
3704 return;
3705 }
3706 let Some(cell) = self.graph.get_cell_ref(vertex_id) else {
3707 return;
3708 };
3709 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
3710 self.mirror_value_to_computed_overlay(
3711 &sheet_name,
3712 cell.coord.row() + 1,
3713 cell.coord.col() + 1,
3714 value,
3715 );
3716 }
3717
3718 pub fn overlay_memory_usage(&self) -> usize {
3720 self.computed_overlay_bytes_estimate
3721 }
3722
3723 fn resolve_sheet_locator_for_write(
3724 &mut self,
3725 loc: formualizer_common::SheetLocator<'_>,
3726 current_sheet: &str,
3727 ) -> Result<SheetId, ExcelError> {
3728 Ok(match loc {
3729 formualizer_common::SheetLocator::Id(id) => id,
3730 formualizer_common::SheetLocator::Name(name) => self.graph.sheet_id_mut(name.as_ref()),
3731 formualizer_common::SheetLocator::Current => self.graph.sheet_id_mut(current_sheet),
3732 })
3733 }
3734
3735 fn resolve_sheet_locator_for_read(
3736 &self,
3737 loc: formualizer_common::SheetLocator<'_>,
3738 current_sheet: &str,
3739 ) -> Result<SheetId, ExcelError> {
3740 match loc {
3741 formualizer_common::SheetLocator::Id(id) => Ok(id),
3742 formualizer_common::SheetLocator::Name(name) => self
3743 .graph
3744 .sheet_id(name.as_ref())
3745 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref)),
3746 formualizer_common::SheetLocator::Current => self
3747 .graph
3748 .sheet_id(current_sheet)
3749 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref)),
3750 }
3751 }
3752
3753 pub fn set_cell_value(
3755 &mut self,
3756 sheet: &str,
3757 row: u32,
3758 col: u32,
3759 value: LiteralValue,
3760 ) -> Result<(), ExcelError> {
3761 self.graph.set_cell_value(sheet, row, col, value.clone())?;
3762 self.mirror_value_to_overlay(sheet, row, col, &value);
3764 self.snapshot_id
3766 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
3767 self.has_edited = true;
3768 Ok(())
3769 }
3770
3771 pub fn set_cell_value_ref(
3772 &mut self,
3773 cell: formualizer_common::SheetCellRef<'_>,
3774 current_sheet: &str,
3775 value: LiteralValue,
3776 ) -> Result<(), ExcelError> {
3777 let owned = cell.into_owned();
3778 let sheet_id = self.resolve_sheet_locator_for_write(owned.sheet, current_sheet)?;
3779 let sheet_name = self.graph.sheet_name(sheet_id).to_string();
3780 self.set_cell_value(
3781 &sheet_name,
3782 owned.coord.row() + 1,
3783 owned.coord.col() + 1,
3784 value,
3785 )
3786 }
3787
3788 pub fn set_cell_formula_ref(
3789 &mut self,
3790 cell: formualizer_common::SheetCellRef<'_>,
3791 current_sheet: &str,
3792 ast: ASTNode,
3793 ) -> Result<(), ExcelError> {
3794 let owned = cell.into_owned();
3795 let sheet_id = self.resolve_sheet_locator_for_write(owned.sheet, current_sheet)?;
3796 let sheet_name = self.graph.sheet_name(sheet_id).to_string();
3797 self.set_cell_formula(
3798 &sheet_name,
3799 owned.coord.row() + 1,
3800 owned.coord.col() + 1,
3801 ast,
3802 )
3803 }
3804
3805 pub fn get_cell_value_ref(
3806 &self,
3807 cell: formualizer_common::SheetCellRef<'_>,
3808 current_sheet: &str,
3809 ) -> Result<Option<LiteralValue>, ExcelError> {
3810 let owned = cell.into_owned();
3811 let sheet_id = self.resolve_sheet_locator_for_read(owned.sheet, current_sheet)?;
3812 let sheet_name = self.graph.sheet_name(sheet_id);
3813 Ok(self.get_cell_value(sheet_name, owned.coord.row() + 1, owned.coord.col() + 1))
3814 }
3815
3816 pub fn resolve_range_view_sheet_ref<'c>(
3817 &'c self,
3818 r: &formualizer_common::SheetRef<'_>,
3819 current_sheet: &str,
3820 ) -> Result<RangeView<'c>, ExcelError> {
3821 use formualizer_common::SheetLocator;
3822
3823 let sheet_to_opt_name = |loc: SheetLocator<'_>| -> Result<Option<String>, ExcelError> {
3824 match loc {
3825 SheetLocator::Current => Ok(None),
3826 SheetLocator::Name(name) => Ok(Some(name.as_ref().to_string())),
3827 SheetLocator::Id(id) => Ok(Some(self.graph.sheet_name(id).to_string())),
3828 }
3829 };
3830
3831 let rt = match r {
3832 formualizer_common::SheetRef::Cell(cell) => ReferenceType::Cell {
3833 sheet: sheet_to_opt_name(cell.sheet.clone())?,
3834 row: cell.coord.row() + 1,
3835 col: cell.coord.col() + 1,
3836 row_abs: cell.coord.row_abs(),
3837 col_abs: cell.coord.col_abs(),
3838 },
3839 formualizer_common::SheetRef::Range(range) => ReferenceType::Range {
3840 sheet: sheet_to_opt_name(range.sheet.clone())?,
3841 start_row: range.start_row.map(|b| b.index + 1),
3842 start_col: range.start_col.map(|b| b.index + 1),
3843 end_row: range.end_row.map(|b| b.index + 1),
3844 end_col: range.end_col.map(|b| b.index + 1),
3845 start_row_abs: range.start_row.map(|b| b.abs).unwrap_or(false),
3846 start_col_abs: range.start_col.map(|b| b.abs).unwrap_or(false),
3847 end_row_abs: range.end_row.map(|b| b.abs).unwrap_or(false),
3848 end_col_abs: range.end_col.map(|b| b.abs).unwrap_or(false),
3849 },
3850 };
3851
3852 crate::traits::EvaluationContext::resolve_range_view(self, &rt, current_sheet)
3853 }
3854
3855 pub fn set_cell_formula(
3857 &mut self,
3858 sheet: &str,
3859 row: u32,
3860 col: u32,
3861 ast: ASTNode,
3862 ) -> Result<(), ExcelError> {
3863 let volatile = self.is_ast_volatile_with_provider(&ast);
3864 self.graph
3865 .set_cell_formula_with_volatility(sheet, row, col, ast, volatile)?;
3866
3867 self.clear_delta_overlay_cell(sheet, row, col);
3872
3873 self.mark_topology_edited();
3875 Ok(())
3876 }
3877
3878 pub fn bulk_set_formulas<I>(&mut self, sheet: &str, items: I) -> Result<usize, ExcelError>
3880 where
3881 I: IntoIterator<Item = (u32, u32, ASTNode)>,
3882 {
3883 let collected: Vec<(u32, u32, ASTNode)> = items.into_iter().collect();
3884 let vol_flags: Vec<bool> = collected
3885 .iter()
3886 .map(|(_, _, ast)| self.is_ast_volatile_with_provider(ast))
3887 .collect();
3888 let n = self
3889 .graph
3890 .bulk_set_formulas_with_volatility(sheet, collected, vol_flags)?;
3891 if n > 0 {
3893 self.mark_topology_edited();
3894 }
3895 Ok(n)
3896 }
3897
3898 #[inline]
3899 fn normalize_public_cell_read(v: LiteralValue) -> Option<LiteralValue> {
3900 match v {
3901 LiteralValue::Empty => None,
3902 LiteralValue::Int(i) => Some(LiteralValue::Number(i as f64)),
3903 other => Some(other),
3904 }
3905 }
3906
3907 pub fn get_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
3909 self.read_cell_value(sheet, row, col)
3910 .and_then(Self::normalize_public_cell_read)
3911 }
3912
3913 pub(crate) fn read_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
3915 let asheet = self.sheet_store().sheet(sheet)?;
3916 let r0 = row.saturating_sub(1) as usize;
3917 let c0 = col.saturating_sub(1) as usize;
3918 let v = asheet.get_cell_value(r0, c0);
3919 if matches!(v, LiteralValue::Empty) {
3920 None
3921 } else {
3922 Some(v)
3923 }
3924 }
3925
3926 pub(crate) fn read_range_values(
3928 &self,
3929 sheet: &str,
3930 sr: u32,
3931 sc: u32,
3932 er: u32,
3933 ec: u32,
3934 ) -> RangeView<'_> {
3935 let Some(asheet) = self.sheet_store().sheet(sheet) else {
3936 return RangeView::from_owned_rows(Vec::new(), self.config.date_system);
3937 };
3938 if er < sr || ec < sc {
3939 return asheet.range_view(1, 1, 0, 0);
3940 }
3941 let sr0 = sr.saturating_sub(1) as usize;
3942 let sc0 = sc.saturating_sub(1) as usize;
3943 let er0 = er.saturating_sub(1) as usize;
3944 let ec0 = ec.saturating_sub(1) as usize;
3945 asheet.range_view(sr0, sc0, er0, ec0)
3946 }
3947
3948 pub fn get_cell(
3950 &self,
3951 sheet: &str,
3952 row: u32,
3953 col: u32,
3954 ) -> Option<(Option<formualizer_parse::ASTNode>, Option<LiteralValue>)> {
3955 let v = self.get_cell_value(sheet, row, col);
3956 let sheet_id = self.graph.sheet_id(sheet)?;
3957 let coord = Coord::from_excel(row, col, true, true);
3958 let cell = CellRef::new(sheet_id, coord);
3959 let vid = self.graph.get_vertex_for_cell(&cell)?;
3960 let ast = self.graph.get_formula_id(vid).and_then(|ast_id| {
3961 self.graph
3962 .data_store()
3963 .retrieve_ast(ast_id, self.graph.sheet_reg())
3964 });
3965 Some((ast, v))
3966 }
3967
3968 pub fn begin_batch(&mut self) {
3970 self.graph.begin_batch();
3971 }
3972
3973 pub fn end_batch(&mut self) {
3975 self.graph.end_batch();
3976 }
3977
3978 #[inline]
3981 fn record_cell_if_changed(
3982 delta: &mut DeltaCollector,
3983 cell: &CellRef,
3984 old: &LiteralValue,
3985 new: &LiteralValue,
3986 ) {
3987 if old != new {
3988 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
3989 }
3990 }
3991
3992 pub fn evaluate_vertex(&mut self, vertex_id: VertexId) -> Result<LiteralValue, ExcelError> {
3993 self.evaluate_vertex_impl(vertex_id, None)
3994 }
3995
3996 fn evaluate_vertex_impl(
3997 &mut self,
3998 vertex_id: VertexId,
3999 delta: Option<&mut DeltaCollector>,
4000 ) -> Result<LiteralValue, ExcelError> {
4001 let mut delta = delta;
4002 if !self.graph.vertex_exists(vertex_id) {
4004 return Err(ExcelError::new(formualizer_common::ExcelErrorKind::Ref)
4005 .with_message(format!("Vertex not found: {vertex_id:?}")));
4006 }
4007
4008 let kind = self.graph.get_vertex_kind(vertex_id);
4010 let sheet_id = self.graph.get_vertex_sheet_id(vertex_id);
4011
4012 let ast_id = match kind {
4013 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
4014 if let Some(ast_id) = self.graph.get_formula_id(vertex_id) {
4015 ast_id
4016 } else {
4017 return Ok(LiteralValue::Number(0.0));
4018 }
4019 }
4020 VertexKind::Empty | VertexKind::Cell => {
4021 if let Some(cell_ref) = self.graph.get_cell_ref(vertex_id) {
4022 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
4023 let row = cell_ref.coord.row() + 1;
4024 let col = cell_ref.coord.col() + 1;
4025 if let Some(v) = self.read_cell_value(sheet_name, row, col) {
4026 return Ok(v);
4027 }
4028 }
4029 return Ok(LiteralValue::Number(0.0));
4030 }
4031 VertexKind::NamedScalar => {
4032 let value = self.evaluate_named_scalar(vertex_id, sheet_id)?;
4033 return Ok(value);
4034 }
4035 VertexKind::NamedArray => {
4036 let value = self.evaluate_named_array(vertex_id, sheet_id)?;
4037 return Ok(value);
4038 }
4039 VertexKind::InfiniteRange
4040 | VertexKind::Range
4041 | VertexKind::External
4042 | VertexKind::Table => {
4043 return Ok(LiteralValue::Number(0.0));
4045 }
4046 };
4047
4048 let sheet_name = self.graph.sheet_name(sheet_id);
4050 let cell_ref = self
4051 .graph
4052 .get_cell_ref(vertex_id)
4053 .expect("cell ref for vertex");
4054 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
4055
4056 let result =
4057 interpreter.evaluate_arena_ast(ast_id, self.graph.data_store(), self.graph.sheet_reg());
4058
4059 match result {
4061 Ok(cv) => {
4062 let result_literal = cv.into_literal();
4063 match result_literal {
4064 LiteralValue::Array(rows) => {
4065 self.graph
4067 .set_kind(vertex_id, crate::engine::vertex::VertexKind::FormulaArray);
4068 let anchor = self
4070 .graph
4071 .get_cell_ref(vertex_id)
4072 .expect("cell ref for vertex");
4073 let sheet_id = anchor.sheet_id;
4074 let h = rows.len() as u32;
4075 let w = rows.first().map(|r| r.len()).unwrap_or(0) as u32;
4076
4077 let spill_cells = (h as u64).saturating_mul(w as u64);
4079 if spill_cells > self.config.spill.max_spill_cells as u64 {
4080 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
4081 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
4082 .with_message("SpillTooLarge")
4083 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
4084 expected_rows: h,
4085 expected_cols: w,
4086 });
4087 let spill_val = LiteralValue::Error(spill_err.clone());
4088 if let Some(d) = delta.as_deref_mut() {
4089 let old = self
4090 .read_cell_value(
4091 self.graph.sheet_name(anchor.sheet_id),
4092 anchor.coord.row() + 1,
4093 anchor.coord.col() + 1,
4094 )
4095 .unwrap_or(LiteralValue::Empty);
4096 if old != spill_val {
4097 d.record_cell(
4098 anchor.sheet_id,
4099 anchor.coord.row(),
4100 anchor.coord.col(),
4101 );
4102 }
4103 }
4104 self.graph.update_vertex_value(vertex_id, spill_val.clone());
4105 if self.config.arrow_storage_enabled
4106 && self.config.delta_overlay_enabled
4107 && self.config.write_formula_overlay_enabled
4108 {
4109 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
4110 self.mirror_value_to_computed_overlay(
4111 &sheet_name,
4112 anchor.coord.row() + 1,
4113 anchor.coord.col() + 1,
4114 &spill_val,
4115 );
4116 }
4117 return Ok(spill_val);
4118 }
4119 const PACKED_MAX_ROW: u32 = 1_048_575; const PACKED_MAX_COL: u32 = 16_383; let end_row = anchor.coord.row().saturating_add(h).saturating_sub(1);
4123 let end_col = anchor.coord.col().saturating_add(w).saturating_sub(1);
4124 if end_row > PACKED_MAX_ROW || end_col > PACKED_MAX_COL {
4125 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
4126 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
4127 .with_message("Spill exceeds sheet bounds")
4128 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
4129 expected_rows: h,
4130 expected_cols: w,
4131 });
4132 let spill_val = LiteralValue::Error(spill_err.clone());
4133 if let Some(d) = delta.as_deref_mut() {
4134 let old = self
4135 .read_cell_value(
4136 self.graph.sheet_name(anchor.sheet_id),
4137 anchor.coord.row() + 1,
4138 anchor.coord.col() + 1,
4139 )
4140 .unwrap_or(LiteralValue::Empty);
4141 if old != spill_val {
4142 d.record_cell(
4143 anchor.sheet_id,
4144 anchor.coord.row(),
4145 anchor.coord.col(),
4146 );
4147 }
4148 }
4149 self.graph.update_vertex_value(vertex_id, spill_val.clone());
4150 if self.config.arrow_storage_enabled
4151 && self.config.delta_overlay_enabled
4152 && self.config.write_formula_overlay_enabled
4153 {
4154 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
4155 self.mirror_value_to_computed_overlay(
4156 &sheet_name,
4157 anchor.coord.row() + 1,
4158 anchor.coord.col() + 1,
4159 &spill_val,
4160 );
4161 }
4162 return Ok(spill_val);
4163 }
4164 let mut targets = Vec::new();
4165 for r in 0..h {
4166 for c in 0..w {
4167 targets.push(self.graph.make_cell_ref_internal(
4168 sheet_id,
4169 anchor.coord.row() + r,
4170 anchor.coord.col() + c,
4171 ));
4172 }
4173 }
4174
4175 match self.spill_mgr.reserve(
4177 vertex_id,
4178 anchor,
4179 SpillShape { rows: h, cols: w },
4180 SpillMeta {
4181 epoch: self.recalc_epoch,
4182 config: self.config.spill,
4183 },
4184 ) {
4185 Ok(()) => {
4186 if let Err(e) = self.commit_spill_and_mirror(
4190 vertex_id,
4191 &targets,
4192 rows.clone(),
4193 delta.as_deref_mut(),
4194 None,
4195 ) {
4196 self.clear_spill_projection_and_mirror(
4198 vertex_id,
4199 delta.as_deref_mut(),
4200 );
4201 if let Some(d) = delta.as_deref_mut() {
4202 let old = self
4203 .read_cell_value(
4204 self.graph.sheet_name(anchor.sheet_id),
4205 anchor.coord.row() + 1,
4206 anchor.coord.col() + 1,
4207 )
4208 .unwrap_or(LiteralValue::Empty);
4209 let new = LiteralValue::Error(e.clone());
4210 if old != new {
4211 d.record_cell(
4212 anchor.sheet_id,
4213 anchor.coord.row(),
4214 anchor.coord.col(),
4215 );
4216 }
4217 }
4218 let err_val = LiteralValue::Error(e.clone());
4219 self.graph.update_vertex_value(vertex_id, err_val.clone());
4220 if self.config.arrow_storage_enabled
4221 && self.config.delta_overlay_enabled
4222 && self.config.write_formula_overlay_enabled
4223 {
4224 let sheet_name =
4225 self.graph.sheet_name(anchor.sheet_id).to_string();
4226 self.mirror_value_to_computed_overlay(
4227 &sheet_name,
4228 anchor.coord.row() + 1,
4229 anchor.coord.col() + 1,
4230 &err_val,
4231 );
4232 }
4233 return Ok(err_val);
4234 }
4235 let top_left = rows
4237 .first()
4238 .and_then(|r| r.first())
4239 .cloned()
4240 .unwrap_or(LiteralValue::Empty);
4241 self.graph.update_vertex_value(vertex_id, top_left.clone());
4242 Ok(top_left)
4243 }
4244 Err(e) => {
4245 self.clear_spill_projection_and_mirror(
4246 vertex_id,
4247 delta.as_deref_mut(),
4248 );
4249 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
4250 .with_message(
4251 e.message.unwrap_or_else(|| "Spill blocked".to_string()),
4252 )
4253 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
4254 expected_rows: h,
4255 expected_cols: w,
4256 });
4257 let spill_val = LiteralValue::Error(spill_err.clone());
4258 if let Some(d) = delta.as_deref_mut() {
4259 let old = self
4260 .read_cell_value(
4261 self.graph.sheet_name(anchor.sheet_id),
4262 anchor.coord.row() + 1,
4263 anchor.coord.col() + 1,
4264 )
4265 .unwrap_or(LiteralValue::Empty);
4266 if old != spill_val {
4267 d.record_cell(
4268 anchor.sheet_id,
4269 anchor.coord.row(),
4270 anchor.coord.col(),
4271 );
4272 }
4273 }
4274 self.graph.update_vertex_value(vertex_id, spill_val.clone());
4275 if self.config.arrow_storage_enabled
4276 && self.config.delta_overlay_enabled
4277 && self.config.write_formula_overlay_enabled
4278 {
4279 let sheet_name =
4280 self.graph.sheet_name(anchor.sheet_id).to_string();
4281 self.mirror_value_to_computed_overlay(
4282 &sheet_name,
4283 anchor.coord.row() + 1,
4284 anchor.coord.col() + 1,
4285 &spill_val,
4286 );
4287 }
4288 Ok(spill_val)
4289 }
4290 }
4291 }
4292 other => {
4293 let spill_cells = self
4295 .graph
4296 .spill_cells_for_anchor(vertex_id)
4297 .map(|cells| cells.to_vec())
4298 .unwrap_or_default();
4299 if let Some(d) = delta.as_deref_mut()
4300 && let Some(anchor) = self.graph.get_cell_ref_for_vertex(vertex_id)
4301 {
4302 if spill_cells.is_empty() {
4303 let old = self
4304 .read_cell_value(
4305 self.graph.sheet_name(anchor.sheet_id),
4306 anchor.coord.row() + 1,
4307 anchor.coord.col() + 1,
4308 )
4309 .unwrap_or(LiteralValue::Empty);
4310 if old != other {
4311 d.record_cell(
4312 anchor.sheet_id,
4313 anchor.coord.row(),
4314 anchor.coord.col(),
4315 );
4316 }
4317 } else {
4318 for cell in spill_cells.iter() {
4319 let sheet_name = self.graph.sheet_name(cell.sheet_id);
4320 let old = self
4321 .get_cell_value(
4322 sheet_name,
4323 cell.coord.row() + 1,
4324 cell.coord.col() + 1,
4325 )
4326 .unwrap_or(LiteralValue::Empty);
4327 let new = if cell.sheet_id == anchor.sheet_id
4328 && cell.coord.row() == anchor.coord.row()
4329 && cell.coord.col() == anchor.coord.col()
4330 {
4331 other.clone()
4332 } else {
4333 LiteralValue::Empty
4334 };
4335 Self::record_cell_if_changed(d, cell, &old, &new);
4336 }
4337 }
4338 }
4339 self.graph.clear_spill_region(vertex_id);
4340 if self.config.arrow_storage_enabled
4341 && self.config.delta_overlay_enabled
4342 && self.config.write_formula_overlay_enabled
4343 {
4344 let empty = LiteralValue::Empty;
4345 for cell in spill_cells.iter() {
4346 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
4347 self.mirror_value_to_computed_overlay(
4348 &sheet_name,
4349 cell.coord.row() + 1,
4350 cell.coord.col() + 1,
4351 &empty,
4352 );
4353 }
4354 }
4355 self.graph.update_vertex_value(vertex_id, other.clone());
4356 if self.config.arrow_storage_enabled
4358 && self.config.delta_overlay_enabled
4359 && self.config.write_formula_overlay_enabled
4360 {
4361 let anchor = self
4362 .graph
4363 .get_cell_ref(vertex_id)
4364 .expect("cell ref for vertex");
4365 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
4366 self.mirror_value_to_computed_overlay(
4367 &sheet_name,
4368 anchor.coord.row() + 1,
4369 anchor.coord.col() + 1,
4370 &other,
4371 );
4372 }
4373 Ok(other)
4374 }
4375 }
4376 }
4377 Err(e) => {
4378 let spill_cells = self
4381 .graph
4382 .spill_cells_for_anchor(vertex_id)
4383 .map(|cells| cells.to_vec())
4384 .unwrap_or_default();
4385 let err_val = LiteralValue::Error(e.clone());
4386 if let Some(d) = delta
4387 && let Some(anchor) = self.graph.get_cell_ref_for_vertex(vertex_id)
4388 {
4389 if spill_cells.is_empty() {
4390 let old = self
4391 .read_cell_value(
4392 self.graph.sheet_name(anchor.sheet_id),
4393 anchor.coord.row() + 1,
4394 anchor.coord.col() + 1,
4395 )
4396 .unwrap_or(LiteralValue::Empty);
4397 if old != err_val {
4398 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
4399 }
4400 } else {
4401 for cell in spill_cells.iter() {
4402 let sheet_name = self.graph.sheet_name(cell.sheet_id);
4403 let old = self
4404 .get_cell_value(
4405 sheet_name,
4406 cell.coord.row() + 1,
4407 cell.coord.col() + 1,
4408 )
4409 .unwrap_or(LiteralValue::Empty);
4410 let new = if cell.sheet_id == anchor.sheet_id
4411 && cell.coord.row() == anchor.coord.row()
4412 && cell.coord.col() == anchor.coord.col()
4413 {
4414 err_val.clone()
4415 } else {
4416 LiteralValue::Empty
4417 };
4418 Self::record_cell_if_changed(d, cell, &old, &new);
4419 }
4420 }
4421 }
4422 self.graph.clear_spill_region(vertex_id);
4423 if self.config.arrow_storage_enabled
4424 && self.config.delta_overlay_enabled
4425 && self.config.write_formula_overlay_enabled
4426 {
4427 let empty = LiteralValue::Empty;
4428 for cell in spill_cells.iter() {
4429 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
4430 self.mirror_value_to_computed_overlay(
4431 &sheet_name,
4432 cell.coord.row() + 1,
4433 cell.coord.col() + 1,
4434 &empty,
4435 );
4436 }
4437 }
4438 self.graph.update_vertex_value(vertex_id, err_val.clone());
4439 if self.config.arrow_storage_enabled
4440 && self.config.delta_overlay_enabled
4441 && self.config.write_formula_overlay_enabled
4442 {
4443 let anchor = self
4444 .graph
4445 .get_cell_ref(vertex_id)
4446 .expect("cell ref for vertex");
4447 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
4448 self.mirror_value_to_computed_overlay(
4449 &sheet_name,
4450 anchor.coord.row() + 1,
4451 anchor.coord.col() + 1,
4452 &err_val,
4453 );
4454 }
4455 Ok(err_val)
4456 }
4457 }
4458 }
4459
4460 fn evaluate_named_scalar(
4461 &mut self,
4462 vertex_id: VertexId,
4463 sheet_id: SheetId,
4464 ) -> Result<LiteralValue, ExcelError> {
4465 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
4466 ExcelError::new(ExcelErrorKind::Name)
4467 .with_message("Named range metadata missing".to_string())
4468 })?;
4469
4470 match &named_range.definition {
4471 NamedDefinition::Cell(cell_ref) => {
4472 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
4473 let row = cell_ref.coord.row() + 1;
4474 let col = cell_ref.coord.col() + 1;
4475
4476 if let Some(dep_vertex) = self.graph.get_vertex_for_cell(cell_ref)
4477 && matches!(
4478 self.graph.get_vertex_kind(dep_vertex),
4479 VertexKind::FormulaScalar | VertexKind::FormulaArray
4480 )
4481 {
4482 let value = self.evaluate_vertex(dep_vertex)?;
4484 self.graph.update_vertex_value(vertex_id, value.clone());
4485 Ok(value)
4486 } else {
4487 let value = self
4488 .get_cell_value(sheet_name, row, col)
4489 .unwrap_or(LiteralValue::Empty);
4490 self.graph.update_vertex_value(vertex_id, value.clone());
4491 Ok(value)
4492 }
4493 }
4494 NamedDefinition::Literal(v) => {
4495 let out = v.clone();
4496 self.graph.update_vertex_value(vertex_id, out.clone());
4497 Ok(out)
4498 }
4499 NamedDefinition::Formula { ast, .. } => {
4500 let context_sheet = match named_range.scope {
4501 NameScope::Sheet(id) => id,
4502 NameScope::Workbook => sheet_id,
4503 };
4504 let sheet_name = self.graph.sheet_name(context_sheet);
4505 let cell_ref = self
4506 .graph
4507 .get_cell_ref(vertex_id)
4508 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
4509 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
4510 match interpreter.evaluate_ast(ast) {
4511 Ok(cv) => {
4512 let value = cv.into_literal();
4513 match value {
4514 LiteralValue::Array(_) => {
4515 let err = ExcelError::new(ExcelErrorKind::NImpl)
4516 .with_message("Array result in scalar named range".to_string());
4517 let err_val = LiteralValue::Error(err.clone());
4518 self.graph.update_vertex_value(vertex_id, err_val.clone());
4519 Ok(err_val)
4520 }
4521 other => {
4522 self.graph.update_vertex_value(vertex_id, other.clone());
4523 Ok(other)
4524 }
4525 }
4526 }
4527 Err(err) => {
4528 let err_val = LiteralValue::Error(err.clone());
4529 self.graph.update_vertex_value(vertex_id, err_val.clone());
4530 Ok(err_val)
4531 }
4532 }
4533 }
4534 NamedDefinition::Range(_) => Err(ExcelError::new(ExcelErrorKind::Value)
4535 .with_message("Range-valued name evaluated as scalar".to_string())),
4536 }
4537 }
4538
4539 fn evaluate_named_array(
4540 &mut self,
4541 vertex_id: VertexId,
4542 sheet_id: SheetId,
4543 ) -> Result<LiteralValue, ExcelError> {
4544 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
4545 ExcelError::new(ExcelErrorKind::Name)
4546 .with_message("Named range metadata missing".to_string())
4547 })?;
4548
4549 let out = match &named_range.definition {
4550 NamedDefinition::Range(range_ref) => {
4551 if range_ref.start.sheet_id != range_ref.end.sheet_id {
4552 return Err(ExcelError::new(ExcelErrorKind::Ref)
4553 .with_message("Named range cannot span sheets".to_string()));
4554 }
4555
4556 let sheet_name = self.graph.sheet_name(range_ref.start.sheet_id);
4557 let sr0 = range_ref.start.coord.row();
4558 let sc0 = range_ref.start.coord.col();
4559 let er0 = range_ref.end.coord.row();
4560 let ec0 = range_ref.end.coord.col();
4561 if sr0 > er0 || sc0 > ec0 {
4562 return Err(ExcelError::new(ExcelErrorKind::Ref)
4563 .with_message("Invalid named range bounds".to_string()));
4564 }
4565
4566 let h = (er0 - sr0 + 1) as usize;
4567 let w = (ec0 - sc0 + 1) as usize;
4568 let cell_count = (h as u64).saturating_mul(w as u64);
4569 if cell_count > self.config.spill.max_spill_cells as u64 {
4570 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
4571 "Named range too large to materialize as an array".to_string(),
4572 ));
4573 }
4574
4575 let mut rows = Vec::with_capacity(h);
4576 for r0 in sr0..=er0 {
4577 let mut row = Vec::with_capacity(w);
4578 for c0 in sc0..=ec0 {
4579 let v = self
4580 .get_cell_value(sheet_name, r0 + 1, c0 + 1)
4581 .unwrap_or(LiteralValue::Empty);
4582 row.push(v);
4583 }
4584 rows.push(row);
4585 }
4586 LiteralValue::Array(rows)
4587 }
4588 NamedDefinition::Cell(cell_ref) => {
4589 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
4590 let row = cell_ref.coord.row() + 1;
4591 let col = cell_ref.coord.col() + 1;
4592 let v = self
4593 .get_cell_value(sheet_name, row, col)
4594 .unwrap_or(LiteralValue::Empty);
4595 LiteralValue::Array(vec![vec![v]])
4596 }
4597 NamedDefinition::Literal(v) => LiteralValue::Array(vec![vec![v.clone()]]),
4598 NamedDefinition::Formula { ast, .. } => {
4599 let context_sheet = match named_range.scope {
4600 NameScope::Sheet(id) => id,
4601 NameScope::Workbook => sheet_id,
4602 };
4603 let sheet_name = self.graph.sheet_name(context_sheet);
4604 let cell_ref = self
4605 .graph
4606 .get_cell_ref(vertex_id)
4607 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
4608 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
4609 match interpreter.evaluate_ast(ast) {
4610 Ok(cv) => {
4611 let v = cv.into_literal();
4612 match v {
4613 LiteralValue::Array(_) => v,
4614 other => LiteralValue::Array(vec![vec![other]]),
4615 }
4616 }
4617 Err(err) => LiteralValue::Error(err),
4618 }
4619 }
4620 };
4621
4622 self.graph.update_vertex_value(vertex_id, out.clone());
4623 Ok(out)
4624 }
4625
4626 pub fn evaluate_until(
4628 &mut self,
4629 targets: &[(&str, u32, u32)],
4630 ) -> Result<EvalResult, ExcelError> {
4631 #[cfg(feature = "tracing")]
4632 let _span_eval = tracing::info_span!("evaluate_until", targets = targets.len()).entered();
4633 let start = crate::instant::FzInstant::now();
4634 let _source_cache = self.source_cache_session();
4635
4636 let mut target_addrs = Vec::new();
4638 for (sheet, row, col) in targets {
4639 let sheet_id = self.graph.sheet_id_mut(sheet);
4642 let coord = Coord::from_excel(*row, *col, true, true);
4643 target_addrs.push(CellRef::new(sheet_id, coord));
4644 }
4645
4646 let mut target_vertex_ids = Vec::new();
4648 for addr in &target_addrs {
4649 if let Some(vertex_id) = self.graph.get_vertex_id_for_address(addr) {
4650 target_vertex_ids.push(*vertex_id);
4651 }
4652 }
4653
4654 if target_vertex_ids.is_empty() {
4655 return Ok(EvalResult {
4656 computed_vertices: 0,
4657 cycle_errors: 0,
4658 elapsed: start.elapsed(),
4659 });
4660 }
4661
4662 #[cfg(feature = "tracing")]
4664 let _span_sub = tracing::info_span!("demand_subgraph_build").entered();
4665 let (precedents_to_eval, vdeps) = self.build_demand_subgraph(&target_vertex_ids);
4666 #[cfg(feature = "tracing")]
4667 drop(_span_sub);
4668
4669 if precedents_to_eval.is_empty() {
4670 return Ok(EvalResult {
4671 computed_vertices: 0,
4672 cycle_errors: 0,
4673 elapsed: start.elapsed(),
4674 });
4675 }
4676
4677 let scheduler = Scheduler::new(&self.graph);
4679 #[cfg(feature = "tracing")]
4680 let _span_sched =
4681 tracing::info_span!("schedule_build", vertices = precedents_to_eval.len()).entered();
4682 let schedule = scheduler.create_schedule_with_virtual(&precedents_to_eval, &vdeps)?;
4683 #[cfg(feature = "tracing")]
4684 drop(_span_sched);
4685
4686 let mut cycle_errors = 0;
4688 for cycle in &schedule.cycles {
4689 cycle_errors += 1;
4690 let circ_error = LiteralValue::Error(
4691 ExcelError::new(ExcelErrorKind::Circ)
4692 .with_message("Circular dependency detected".to_string()),
4693 );
4694 for &vertex_id in cycle {
4695 self.graph
4696 .update_vertex_value(vertex_id, circ_error.clone());
4697 self.mirror_vertex_value_to_overlay(vertex_id, &circ_error);
4698 }
4699 }
4700
4701 let mut computed_vertices = 0;
4703 for layer in &schedule.layers {
4704 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
4705 computed_vertices += self.evaluate_layer_parallel(layer)?;
4706 } else {
4707 computed_vertices += self.evaluate_layer_sequential(layer)?;
4708 }
4709 }
4710
4711 self.graph.clear_dirty_flags(&precedents_to_eval);
4715
4716 self.graph.redirty_volatiles();
4718
4719 Ok(EvalResult {
4720 computed_vertices,
4721 cycle_errors,
4722 elapsed: start.elapsed(),
4723 })
4724 }
4725
4726 fn evaluate_until_with_delta_collector(
4727 &mut self,
4728 targets: &[(&str, u32, u32)],
4729 delta: &mut DeltaCollector,
4730 ) -> Result<EvalResult, ExcelError> {
4731 #[cfg(feature = "tracing")]
4732 let _span_eval =
4733 tracing::info_span!("evaluate_until_with_delta", targets = targets.len()).entered();
4734 let start = crate::instant::FzInstant::now();
4735 let _source_cache = self.source_cache_session();
4736
4737 let mut target_addrs = Vec::new();
4738 for (sheet, row, col) in targets {
4739 let sheet_id = self.graph.sheet_id_mut(sheet);
4740 let coord = Coord::from_excel(*row, *col, true, true);
4741 target_addrs.push(CellRef::new(sheet_id, coord));
4742 }
4743
4744 let mut target_vertex_ids = Vec::new();
4745 for addr in &target_addrs {
4746 if let Some(vertex_id) = self.graph.get_vertex_id_for_address(addr) {
4747 target_vertex_ids.push(*vertex_id);
4748 }
4749 }
4750
4751 if target_vertex_ids.is_empty() {
4752 return Ok(EvalResult {
4753 computed_vertices: 0,
4754 cycle_errors: 0,
4755 elapsed: start.elapsed(),
4756 });
4757 }
4758
4759 let (precedents_to_eval, vdeps) = self.build_demand_subgraph(&target_vertex_ids);
4760
4761 if precedents_to_eval.is_empty() {
4762 return Ok(EvalResult {
4763 computed_vertices: 0,
4764 cycle_errors: 0,
4765 elapsed: start.elapsed(),
4766 });
4767 }
4768
4769 let scheduler = Scheduler::new(&self.graph);
4770 let schedule = scheduler.create_schedule_with_virtual(&precedents_to_eval, &vdeps)?;
4771
4772 let mut cycle_errors = 0;
4773 let circ_error = LiteralValue::Error(
4774 ExcelError::new(ExcelErrorKind::Circ)
4775 .with_message("Circular dependency detected".to_string()),
4776 );
4777 for cycle in &schedule.cycles {
4778 cycle_errors += 1;
4779 for &vertex_id in cycle {
4780 if delta.mode != DeltaMode::Off
4781 && let Some(cell) = self.graph.get_cell_ref_for_vertex(vertex_id)
4782 {
4783 let sheet_name = self.graph.sheet_name(cell.sheet_id);
4784 let old = self
4785 .read_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
4786 .unwrap_or(LiteralValue::Empty);
4787 if old != circ_error {
4788 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
4789 }
4790 }
4791 self.graph
4792 .update_vertex_value(vertex_id, circ_error.clone());
4793 self.mirror_vertex_value_to_overlay(vertex_id, &circ_error);
4794 }
4795 }
4796
4797 let mut computed_vertices = 0;
4798 for layer in &schedule.layers {
4799 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
4800 computed_vertices += self.evaluate_layer_parallel_with_delta(layer, delta)?;
4801 } else {
4802 computed_vertices += self.evaluate_layer_sequential_with_delta(layer, delta)?;
4803 }
4804 }
4805
4806 self.graph.clear_dirty_flags(&precedents_to_eval);
4807 self.graph.redirty_volatiles();
4808
4809 Ok(EvalResult {
4810 computed_vertices,
4811 cycle_errors,
4812 elapsed: start.elapsed(),
4813 })
4814 }
4815
4816 pub fn build_recalc_plan(&self) -> Result<RecalcPlan, ExcelError> {
4818 let mut vertices: Vec<VertexId> = self.graph.vertices_with_formulas().collect();
4819 vertices.sort_unstable();
4820 if vertices.is_empty() {
4821 return Ok(RecalcPlan {
4822 schedule: crate::engine::Schedule {
4823 layers: Vec::new(),
4824 cycles: Vec::new(),
4825 },
4826 has_dynamic_refs: false,
4827 });
4828 }
4829
4830 let has_dynamic_refs = vertices.iter().copied().any(|v| self.graph.is_dynamic(v));
4831 let (schedule, _, _) = self.create_evaluation_schedule_uncached(&vertices)?;
4832 Ok(RecalcPlan {
4833 schedule,
4834 has_dynamic_refs,
4835 })
4836 }
4837
4838 pub fn evaluate_recalc_plan(&mut self, plan: &RecalcPlan) -> Result<EvalResult, ExcelError> {
4840 let _source_cache = self.source_cache_session();
4841 self.validate_deterministic_mode()?;
4842 if self.config.defer_graph_building {
4843 self.build_graph_all()?;
4844 }
4845
4846 let start = crate::instant::FzInstant::now();
4847 let dirty_vertices = self.graph.get_evaluation_vertices();
4848 if dirty_vertices.is_empty() {
4849 return Ok(EvalResult {
4850 computed_vertices: 0,
4851 cycle_errors: 0,
4852 elapsed: start.elapsed(),
4853 });
4854 }
4855
4856 if plan.has_dynamic_refs {
4859 self.virtual_dep_fallback_activations =
4860 self.virtual_dep_fallback_activations.saturating_add(1);
4861 return self.evaluate_all();
4862 }
4863
4864 let dirty_set: FxHashSet<VertexId> = dirty_vertices.iter().copied().collect();
4865 let mut computed_vertices = 0;
4866 let mut cycle_errors = 0;
4867
4868 if !plan.schedule.cycles.is_empty() {
4869 let circ_error = LiteralValue::Error(
4870 ExcelError::new(ExcelErrorKind::Circ)
4871 .with_message("Circular dependency detected".to_string()),
4872 );
4873 for cycle in &plan.schedule.cycles {
4874 if !cycle.iter().any(|v| dirty_set.contains(v)) {
4875 continue;
4876 }
4877 cycle_errors += 1;
4878 for &vertex_id in cycle {
4879 if dirty_set.contains(&vertex_id) {
4880 self.graph
4881 .update_vertex_value(vertex_id, circ_error.clone());
4882 self.mirror_vertex_value_to_overlay(vertex_id, &circ_error);
4883 }
4884 }
4885 }
4886 }
4887
4888 for layer in &plan.schedule.layers {
4889 let work: Vec<VertexId> = layer
4890 .vertices
4891 .iter()
4892 .copied()
4893 .filter(|v| dirty_set.contains(v))
4894 .collect();
4895 if work.is_empty() {
4896 continue;
4897 }
4898 let temp_layer = crate::engine::scheduler::Layer { vertices: work };
4899 if self.thread_pool.is_some() && temp_layer.vertices.len() > 1 {
4900 computed_vertices += self.evaluate_layer_parallel(&temp_layer)?;
4901 } else {
4902 computed_vertices += self.evaluate_layer_sequential(&temp_layer)?;
4903 }
4904 }
4905
4906 self.graph.clear_dirty_flags(&dirty_vertices);
4907 self.graph.redirty_volatiles();
4908
4909 Ok(EvalResult {
4910 computed_vertices,
4911 cycle_errors,
4912 elapsed: start.elapsed(),
4913 })
4914 }
4915 pub fn evaluate_all(&mut self) -> Result<EvalResult, ExcelError> {
4917 let _source_cache = self.source_cache_session();
4918 self.validate_deterministic_mode()?;
4919 if self.config.defer_graph_building {
4920 self.build_graph_all()?;
4922 }
4923 self.reset_virtual_dep_telemetry_if_disabled();
4924 #[cfg(feature = "tracing")]
4925 let _span_eval = tracing::info_span!("evaluate_all").entered();
4926 let start = crate::instant::FzInstant::now();
4927 let mut computed_vertices = 0;
4928 let mut cycle_errors = 0;
4929 let mut replan_iterations = 0;
4930 const MAX_REPLAN: usize = 5;
4931 let mut telemetry = self
4932 .config
4933 .enable_virtual_dep_telemetry
4934 .then(|| self.start_virtual_dep_telemetry());
4935
4936 loop {
4937 let to_evaluate = self.graph.get_evaluation_vertices();
4938 if to_evaluate.is_empty() {
4939 if let Some(t) = telemetry.as_mut()
4940 && t.bailout_reason.is_none()
4941 {
4942 t.bailout_reason = Some("no_work");
4943 }
4944 break;
4945 }
4946
4947 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
4948 if let Some(t) = telemetry.as_mut() {
4949 Self::accumulate_schedule_meta(t, &meta);
4950 }
4951
4952 for cycle in &schedule.cycles {
4954 cycle_errors += 1;
4955 let circ_error = LiteralValue::Error(
4956 ExcelError::new(ExcelErrorKind::Circ)
4957 .with_message("Circular dependency detected".to_string()),
4958 );
4959 for &vertex_id in cycle {
4960 self.graph
4961 .update_vertex_value(vertex_id, circ_error.clone());
4962 self.mirror_vertex_value_to_overlay(vertex_id, &circ_error);
4963 }
4964 }
4965
4966 for layer in &schedule.layers {
4968 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
4969 computed_vertices += self.evaluate_layer_parallel(layer)?;
4970 } else {
4971 computed_vertices += self.evaluate_layer_sequential(layer)?;
4972 }
4973 }
4974
4975 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
4977 if let Some(t) = telemetry.as_mut() {
4978 t.changed_vdeps_total += changed_vertices.len();
4979 }
4980
4981 self.graph.clear_dirty_flags(&to_evaluate);
4982 for v in &changed_vertices {
4983 self.graph.set_dirty(*v, true);
4984 }
4985
4986 if changed_vertices.is_empty() {
4987 if let Some(t) = telemetry.as_mut() {
4988 t.bailout_reason = Some("converged");
4989 }
4990 break;
4991 }
4992 if replan_iterations >= MAX_REPLAN {
4993 if let Some(t) = telemetry.as_mut() {
4994 t.bailout_reason = Some("max_replan");
4995 }
4996 break;
4997 }
4998
4999 replan_iterations += 1;
5000 }
5001
5002 if let Some(mut t) = telemetry {
5003 t.replan_iterations = replan_iterations;
5004 self.last_virtual_dep_telemetry = t;
5005 }
5006
5007 self.graph.redirty_volatiles();
5009
5010 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
5012
5013 Ok(EvalResult {
5014 computed_vertices,
5015 cycle_errors,
5016 elapsed: start.elapsed(),
5017 })
5018 }
5019
5020 pub fn evaluate_all_with_delta(&mut self) -> Result<(EvalResult, EvalDelta), ExcelError> {
5021 let mut collector = DeltaCollector::new(DeltaMode::Cells);
5022 let result = self.evaluate_all_with_delta_collector(&mut collector)?;
5023 Ok((result, collector.finish()))
5024 }
5025
5026 fn evaluate_all_with_delta_collector(
5027 &mut self,
5028 delta: &mut DeltaCollector,
5029 ) -> Result<EvalResult, ExcelError> {
5030 let _source_cache = self.source_cache_session();
5031 if self.config.defer_graph_building {
5032 self.build_graph_all()?;
5033 }
5034 self.reset_virtual_dep_telemetry_if_disabled();
5035 #[cfg(feature = "tracing")]
5036 let _span_eval = tracing::info_span!("evaluate_all_with_delta").entered();
5037 let start = crate::instant::FzInstant::now();
5038 let mut computed_vertices = 0;
5039 let mut cycle_errors = 0;
5040
5041 let mut replan_iterations = 0;
5042 const MAX_REPLAN: usize = 5;
5043 let mut telemetry = self
5044 .config
5045 .enable_virtual_dep_telemetry
5046 .then(|| self.start_virtual_dep_telemetry());
5047
5048 loop {
5049 let to_evaluate = self.graph.get_evaluation_vertices();
5050 if to_evaluate.is_empty() {
5051 if let Some(t) = telemetry.as_mut()
5052 && t.bailout_reason.is_none()
5053 {
5054 t.bailout_reason = Some("no_work");
5055 }
5056 break;
5057 }
5058
5059 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
5060 if let Some(t) = telemetry.as_mut() {
5061 Self::accumulate_schedule_meta(t, &meta);
5062 }
5063
5064 let circ_error = LiteralValue::Error(
5065 ExcelError::new(ExcelErrorKind::Circ)
5066 .with_message("Circular dependency detected".to_string()),
5067 );
5068 for cycle in &schedule.cycles {
5069 cycle_errors += 1;
5070 for &vertex_id in cycle {
5071 if delta.mode != DeltaMode::Off
5072 && let Some(cell) = self.graph.get_cell_ref_for_vertex(vertex_id)
5073 {
5074 let sheet_name = self.graph.sheet_name(cell.sheet_id);
5075 let old = self
5076 .read_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
5077 .unwrap_or(LiteralValue::Empty);
5078 if old != circ_error {
5079 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
5080 }
5081 }
5082 self.graph
5083 .update_vertex_value(vertex_id, circ_error.clone());
5084 self.mirror_vertex_value_to_overlay(vertex_id, &circ_error);
5085 }
5086 }
5087
5088 for layer in &schedule.layers {
5089 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
5090 computed_vertices += self.evaluate_layer_parallel_with_delta(layer, delta)?;
5091 } else {
5092 computed_vertices += self.evaluate_layer_sequential_with_delta(layer, delta)?;
5093 }
5094 }
5095
5096 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
5097 if let Some(t) = telemetry.as_mut() {
5098 t.changed_vdeps_total += changed_vertices.len();
5099 }
5100 self.graph.clear_dirty_flags(&to_evaluate);
5101 for v in &changed_vertices {
5102 self.graph.set_dirty(*v, true);
5103 }
5104
5105 if changed_vertices.is_empty() {
5106 if let Some(t) = telemetry.as_mut() {
5107 t.bailout_reason = Some("converged");
5108 }
5109 break;
5110 }
5111 if replan_iterations >= MAX_REPLAN {
5112 if let Some(t) = telemetry.as_mut() {
5113 t.bailout_reason = Some("max_replan");
5114 }
5115 break;
5116 }
5117 replan_iterations += 1;
5118 }
5119
5120 if let Some(mut t) = telemetry {
5121 t.replan_iterations = replan_iterations;
5122 self.last_virtual_dep_telemetry = t;
5123 }
5124
5125 self.graph.redirty_volatiles();
5126 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
5127
5128 Ok(EvalResult {
5129 computed_vertices,
5130 cycle_errors,
5131 elapsed: start.elapsed(),
5132 })
5133 }
5134
5135 pub fn evaluate_cell(
5145 &mut self,
5146 sheet: &str,
5147 row: u32,
5148 col: u32,
5149 ) -> Result<Option<LiteralValue>, ExcelError> {
5150 if row == 0 || col == 0 {
5151 return Err(ExcelError::new(ExcelErrorKind::Ref)
5152 .with_message("Row and column must be >= 1".to_string()));
5153 }
5154
5155 if self.config.defer_graph_building {
5156 self.build_graph_for_sheets(std::iter::once(sheet))?;
5157 }
5158
5159 let result = self.evaluate_cells(&[(sheet, row, col)])?;
5160
5161 match result.len() {
5162 0 => Ok(None),
5163 1 => {
5164 let v = result.into_iter().next().unwrap();
5165 Ok(v)
5166 }
5167 _ => unreachable!("evaluate_cells returned unexpected length"),
5168 }
5169 }
5170
5171 pub fn evaluate_cells(
5178 &mut self,
5179 targets: &[(&str, u32, u32)],
5180 ) -> Result<Vec<Option<LiteralValue>>, ExcelError> {
5181 self.validate_deterministic_mode()?;
5182 if targets.is_empty() {
5183 return Ok(Vec::new());
5184 }
5185 if self.config.defer_graph_building {
5186 let mut sheets: rustc_hash::FxHashSet<&str> = rustc_hash::FxHashSet::default();
5187 for (s, _, _) in targets.iter() {
5188 sheets.insert(*s);
5189 }
5190 self.build_graph_for_sheets(sheets.iter().cloned())?;
5191 }
5192 self.evaluate_until(targets)?;
5193 Ok(targets
5194 .iter()
5195 .map(|(s, r, c)| self.get_cell_value(s, *r, *c))
5196 .collect())
5197 }
5198
5199 pub fn evaluate_cells_cancellable(
5200 &mut self,
5201 targets: &[(&str, u32, u32)],
5202 cancel_flag: Arc<AtomicBool>,
5203 ) -> Result<Vec<Option<LiteralValue>>, ExcelError> {
5204 self.active_cancel_flag = Some(cancel_flag.clone());
5205 let res = self.evaluate_cells_cancellable_impl(targets, &cancel_flag);
5206 self.active_cancel_flag = None;
5207 res
5208 }
5209
5210 fn evaluate_cells_cancellable_impl(
5211 &mut self,
5212 targets: &[(&str, u32, u32)],
5213 cancel_flag: &AtomicBool,
5214 ) -> Result<Vec<Option<LiteralValue>>, ExcelError> {
5215 self.validate_deterministic_mode()?;
5216 if targets.is_empty() {
5217 return Ok(Vec::new());
5218 }
5219 if self.config.defer_graph_building {
5220 let mut sheets: rustc_hash::FxHashSet<&str> = rustc_hash::FxHashSet::default();
5221 for (s, _, _) in targets.iter() {
5222 sheets.insert(*s);
5223 }
5224 self.build_graph_for_sheets(sheets.iter().cloned())?;
5225 }
5226
5227 let a1_targets: Vec<String> = targets
5230 .iter()
5231 .map(|(s, r, c)| {
5232 format!("{}!{}", s, col_letters_from_1based(*c).unwrap()) + &r.to_string()
5233 })
5234 .collect();
5235 let a1_refs: Vec<&str> = a1_targets.iter().map(|s| s.as_str()).collect();
5236
5237 self.evaluate_until_cancellable_impl(&a1_refs, cancel_flag)?;
5238
5239 Ok(targets
5240 .iter()
5241 .map(|(s, r, c)| self.get_cell_value(s, *r, *c))
5242 .collect())
5243 }
5244
5245 pub fn evaluate_cells_with_delta(
5246 &mut self,
5247 targets: &[(&str, u32, u32)],
5248 ) -> Result<(Vec<Option<LiteralValue>>, EvalDelta), ExcelError> {
5249 self.validate_deterministic_mode()?;
5250 if targets.is_empty() {
5251 return Ok((Vec::new(), EvalDelta::default()));
5252 }
5253 if self.config.defer_graph_building {
5254 let mut sheets: rustc_hash::FxHashSet<&str> = rustc_hash::FxHashSet::default();
5255 for (s, _, _) in targets.iter() {
5256 sheets.insert(*s);
5257 }
5258 self.build_graph_for_sheets(sheets.iter().cloned())?;
5259 }
5260 let mut collector = DeltaCollector::new(DeltaMode::Cells);
5261 self.evaluate_until_with_delta_collector(targets, &mut collector)?;
5262 let values = targets
5263 .iter()
5264 .map(|(s, r, c)| self.get_cell_value(s, *r, *c))
5265 .collect();
5266 Ok((values, collector.finish()))
5267 }
5268
5269 pub fn get_eval_plan(&self, targets: &[(&str, u32, u32)]) -> Result<EvalPlan, ExcelError> {
5271 if targets.is_empty() {
5272 return Ok(EvalPlan {
5273 total_vertices_to_evaluate: 0,
5274 layers: Vec::new(),
5275 cycles_detected: 0,
5276 dirty_count: 0,
5277 volatile_count: 0,
5278 parallel_enabled: self.config.enable_parallel && self.thread_pool.is_some(),
5279 estimated_parallel_layers: 0,
5280 target_cells: Vec::new(),
5281 });
5282 }
5283 if self.config.defer_graph_building {
5284 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(
5285 "Evaluation plan requested with deferred graph; build first or call evaluate_*",
5286 ));
5287 }
5288
5289 let addresses: Vec<String> = targets
5291 .iter()
5292 .map(|(s, r, c)| format!("{}!{}{}", s, Self::col_to_letters(*c), r))
5293 .collect();
5294
5295 let mut target_addrs = Vec::new();
5297 for (sheet, row, col) in targets {
5298 if let Some(sheet_id) = self.graph.sheet_id(sheet) {
5299 let coord = Coord::from_excel(*row, *col, true, true);
5300 target_addrs.push(CellRef::new(sheet_id, coord));
5301 }
5302 }
5303
5304 let mut target_vertex_ids = Vec::new();
5306 for addr in &target_addrs {
5307 if let Some(vertex_id) = self.graph.get_vertex_id_for_address(addr) {
5308 target_vertex_ids.push(*vertex_id);
5309 }
5310 }
5311
5312 if target_vertex_ids.is_empty() {
5313 return Ok(EvalPlan {
5314 total_vertices_to_evaluate: 0,
5315 layers: Vec::new(),
5316 cycles_detected: 0,
5317 dirty_count: 0,
5318 volatile_count: 0,
5319 parallel_enabled: self.config.enable_parallel && self.thread_pool.is_some(),
5320 estimated_parallel_layers: 0,
5321 target_cells: addresses,
5322 });
5323 }
5324
5325 let (precedents_to_eval, vdeps) = self.build_demand_subgraph(&target_vertex_ids);
5327
5328 if precedents_to_eval.is_empty() {
5329 return Ok(EvalPlan {
5330 total_vertices_to_evaluate: 0,
5331 layers: Vec::new(),
5332 cycles_detected: 0,
5333 dirty_count: 0,
5334 volatile_count: 0,
5335 parallel_enabled: self.config.enable_parallel && self.thread_pool.is_some(),
5336 estimated_parallel_layers: 0,
5337 target_cells: addresses,
5338 });
5339 }
5340
5341 let mut dirty_count = 0;
5343 let mut volatile_count = 0;
5344 for &vertex_id in &precedents_to_eval {
5345 if self.graph.is_dirty(vertex_id) {
5346 dirty_count += 1;
5347 }
5348 if self.graph.is_volatile(vertex_id) {
5349 volatile_count += 1;
5350 }
5351 }
5352
5353 let scheduler = Scheduler::new(&self.graph);
5355 let schedule = scheduler.create_schedule_with_virtual(&precedents_to_eval, &vdeps)?;
5356
5357 let mut layers = Vec::new();
5359 let mut estimated_parallel_layers = 0;
5360 let parallel_enabled = self.config.enable_parallel && self.thread_pool.is_some();
5361
5362 for layer in &schedule.layers {
5363 let parallel_eligible = parallel_enabled && layer.vertices.len() > 1;
5364 if parallel_eligible {
5365 estimated_parallel_layers += 1;
5366 }
5367
5368 let sample_cells: Vec<String> = layer
5370 .vertices
5371 .iter()
5372 .take(5)
5373 .filter_map(|&vertex_id| {
5374 self.graph
5375 .get_cell_ref_for_vertex(vertex_id)
5376 .map(|cell_ref| {
5377 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
5378 format!(
5379 "{}!{}{}",
5380 sheet_name,
5381 Self::col_to_letters(cell_ref.coord.col()),
5382 cell_ref.coord.row() + 1
5383 )
5384 })
5385 })
5386 .collect();
5387
5388 layers.push(LayerInfo {
5389 vertex_count: layer.vertices.len(),
5390 parallel_eligible,
5391 sample_cells,
5392 });
5393 }
5394
5395 Ok(EvalPlan {
5396 total_vertices_to_evaluate: precedents_to_eval.len(),
5397 layers,
5398 cycles_detected: schedule.cycles.len(),
5399 dirty_count,
5400 volatile_count,
5401 parallel_enabled,
5402 estimated_parallel_layers,
5403 target_cells: addresses,
5404 })
5405 }
5406 fn create_evaluation_schedule(
5408 &mut self,
5409 to_evaluate: &[VertexId],
5410 ) -> Result<ScheduleBuildOutput, ExcelError> {
5411 if self.can_use_static_schedule_cache(to_evaluate) {
5412 if let Some(cached) = self.cached_static_schedule.as_ref()
5413 && cached.topology_epoch == self.topology_epoch
5414 && cached.candidate_vertices.as_slice() == to_evaluate
5415 {
5416 let meta = ScheduleBuildMeta {
5417 candidate_vertices: to_evaluate.len(),
5418 vdeps_vertices: 0,
5419 vdeps_edges: 0,
5420 builder_elapsed_ms: 0,
5421 used_virtual_schedule: false,
5422 schedule_cache_hit: true,
5423 schedule_cache_eligible: true,
5424 };
5425 return Ok((cached.schedule.clone(), FxHashMap::default(), meta));
5426 }
5427
5428 let (schedule, vdeps, mut meta) =
5429 self.create_evaluation_schedule_uncached(to_evaluate)?;
5430 meta.schedule_cache_hit = false;
5431 meta.schedule_cache_eligible = true;
5432 if vdeps.is_empty() {
5433 self.cached_static_schedule = Some(CachedScheduleEntry {
5434 topology_epoch: self.topology_epoch,
5435 candidate_vertices: to_evaluate.to_vec(),
5436 schedule: schedule.clone(),
5437 });
5438 }
5439 return Ok((schedule, vdeps, meta));
5440 }
5441
5442 let (schedule, vdeps, mut meta) = self.create_evaluation_schedule_uncached(to_evaluate)?;
5443 meta.schedule_cache_hit = false;
5444 meta.schedule_cache_eligible = false;
5445 Ok((schedule, vdeps, meta))
5446 }
5447
5448 fn create_evaluation_schedule_uncached(
5449 &self,
5450 to_evaluate: &[VertexId],
5451 ) -> Result<ScheduleBuildOutput, ExcelError> {
5452 let builder = VirtualDepBuilder::new(self);
5453 let (vdeps, augmented, builder_elapsed_ms, vdeps_edges) =
5454 if self.config.enable_virtual_dep_telemetry {
5455 let build_started = crate::instant::FzInstant::now();
5456 let (vdeps, augmented) = builder.build(to_evaluate);
5457 let builder_elapsed_ms = build_started.elapsed().as_millis();
5458 let vdeps_edges = vdeps.values().map(|deps| deps.len()).sum::<usize>();
5459 (vdeps, augmented, builder_elapsed_ms, vdeps_edges)
5460 } else {
5461 let (vdeps, augmented) = builder.build(to_evaluate);
5462 (vdeps, augmented, 0, 0)
5463 };
5464
5465 let mut final_evaluate = to_evaluate.to_vec();
5466 if !augmented.is_empty() {
5467 final_evaluate.extend(augmented);
5468 final_evaluate.sort_unstable();
5469 final_evaluate.dedup();
5470 }
5471
5472 let use_virtual = !vdeps.is_empty();
5473
5474 let scheduler = Scheduler::new(&self.graph);
5475 let schedule = if use_virtual {
5476 scheduler.create_schedule_with_virtual(&final_evaluate, &vdeps)?
5477 } else {
5478 scheduler.create_schedule(&final_evaluate)?
5479 };
5480
5481 let meta = ScheduleBuildMeta {
5482 candidate_vertices: to_evaluate.len(),
5483 vdeps_vertices: vdeps.len(),
5484 vdeps_edges,
5485 builder_elapsed_ms,
5486 used_virtual_schedule: use_virtual,
5487 schedule_cache_hit: false,
5488 schedule_cache_eligible: false,
5489 };
5490
5491 Ok((schedule, vdeps, meta))
5492 }
5493
5494 fn can_use_static_schedule_cache(&self, to_evaluate: &[VertexId]) -> bool {
5495 !to_evaluate.is_empty()
5496 && to_evaluate.iter().copied().all(|v| {
5497 !self.graph.is_dynamic(v) && self.graph.get_range_dependencies(v).is_none()
5498 })
5499 }
5500
5501 fn start_virtual_dep_telemetry(&self) -> VirtualDepTelemetry {
5502 VirtualDepTelemetry {
5503 fallback_mode_activations: self.virtual_dep_fallback_activations,
5504 ..VirtualDepTelemetry::default()
5505 }
5506 }
5507
5508 fn accumulate_schedule_meta(telemetry: &mut VirtualDepTelemetry, meta: &ScheduleBuildMeta) {
5509 telemetry.candidate_vertices_total += meta.candidate_vertices;
5510 telemetry.vdeps_vertices_total += meta.vdeps_vertices;
5511 telemetry.vdeps_edges_total += meta.vdeps_edges;
5512 telemetry.builder_elapsed_ms_total += meta.builder_elapsed_ms;
5513 if meta.schedule_cache_eligible {
5514 if meta.schedule_cache_hit {
5515 telemetry.schedule_cache_hits += 1;
5516 telemetry.reused_schedule_vertices_total += meta.candidate_vertices;
5517 } else {
5518 telemetry.schedule_cache_misses += 1;
5519 }
5520 }
5521 if meta.used_virtual_schedule {
5522 telemetry.schedule_virtual_passes += 1;
5523 } else {
5524 telemetry.schedule_static_passes += 1;
5525 }
5526 }
5527
5528 fn changed_virtual_dep_vertices(
5529 &self,
5530 to_evaluate: &[VertexId],
5531 old_vdeps: &FxHashMap<VertexId, Vec<VertexId>>,
5532 ) -> Vec<VertexId> {
5533 if !to_evaluate
5534 .iter()
5535 .copied()
5536 .any(|v| self.graph.is_dynamic(v))
5537 {
5538 return Vec::new();
5539 }
5540
5541 let builder = VirtualDepBuilder::new(self);
5542 let (new_vdeps, _) = builder.build(to_evaluate);
5543
5544 let mut candidates = FxHashSet::default();
5545 candidates.extend(old_vdeps.keys().copied());
5546 candidates.extend(new_vdeps.keys().copied());
5547
5548 let mut changed = Vec::new();
5549 for v in candidates {
5550 if old_vdeps.get(&v) != new_vdeps.get(&v) {
5551 changed.push(v);
5552 }
5553 }
5554 changed
5555 }
5556
5557 fn build_demand_subgraph(
5560 &self,
5561 target_vertices: &[VertexId],
5562 ) -> (
5563 Vec<VertexId>,
5564 rustc_hash::FxHashMap<VertexId, Vec<VertexId>>,
5565 ) {
5566 #[cfg(feature = "tracing")]
5567 let _span =
5568 tracing::info_span!("demand_subgraph", targets = target_vertices.len()).entered();
5569 use rustc_hash::{FxHashMap, FxHashSet};
5570
5571 let mut to_evaluate: FxHashSet<VertexId> = FxHashSet::default();
5572 let mut visited: FxHashSet<VertexId> = FxHashSet::default();
5573 let mut stack: Vec<VertexId> = Vec::new();
5574 let mut vdeps: FxHashMap<VertexId, Vec<VertexId>> = FxHashMap::default(); for &t in target_vertices {
5577 stack.push(t);
5578 }
5579
5580 while let Some(v) = stack.pop() {
5581 if !visited.insert(v) {
5582 continue;
5583 }
5584 if !self.graph.vertex_exists(v) {
5585 continue;
5586 }
5587 match self.graph.get_vertex_kind(v) {
5589 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
5590 if self.graph.is_dirty(v) || self.graph.is_volatile(v) {
5591 to_evaluate.insert(v);
5592 }
5593 }
5594 _ => {}
5595 }
5596
5597 if let Some(dependencies) = self.graph.dependencies_slice(v) {
5599 for &dep in dependencies {
5600 if self.graph.vertex_exists(dep) {
5601 match self.graph.get_vertex_kind(dep) {
5602 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
5603 if !visited.contains(&dep) {
5604 stack.push(dep);
5605 }
5606 }
5607 _ => {}
5608 }
5609 }
5610 }
5611 } else {
5612 for dep in self.graph.get_dependencies(v) {
5613 if self.graph.vertex_exists(dep) {
5614 match self.graph.get_vertex_kind(dep) {
5615 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
5616 if !visited.contains(&dep) {
5617 stack.push(dep);
5618 }
5619 }
5620 _ => {}
5621 }
5622 }
5623 }
5624 } let builder = VirtualDepBuilder::new(self);
5626 let (vdeps_map, _) = builder.build(&[v]);
5627 if let Some(deps) = vdeps_map.get(&v) {
5628 for &u in deps {
5629 vdeps.entry(v).or_default().push(u);
5630 if !visited.contains(&u) {
5631 stack.push(u);
5632 }
5633 }
5634 }
5635 }
5636
5637 let mut result: Vec<VertexId> = to_evaluate.into_iter().collect();
5638 result.sort_unstable();
5639 for deps in vdeps.values_mut() {
5641 deps.sort_unstable();
5642 deps.dedup();
5643 }
5644 (result, vdeps)
5645 }
5646
5647 fn col_to_letters(col: u32) -> String {
5649 col_letters_from_1based(col).expect("column index must be >= 1")
5650 }
5651
5652 pub fn evaluate_all_cancellable(
5654 &mut self,
5655 cancel_flag: Arc<AtomicBool>,
5656 ) -> Result<EvalResult, ExcelError> {
5657 self.active_cancel_flag = Some(cancel_flag.clone());
5658 let res = self.evaluate_all_cancellable_impl(&cancel_flag);
5659 self.active_cancel_flag = None;
5660 res
5661 }
5662
5663 fn evaluate_all_cancellable_impl(
5664 &mut self,
5665 cancel_flag: &AtomicBool,
5666 ) -> Result<EvalResult, ExcelError> {
5667 let _source_cache = self.source_cache_session();
5668 self.validate_deterministic_mode()?;
5669 if self.config.defer_graph_building {
5670 self.build_graph_all()?;
5671 }
5672 self.reset_virtual_dep_telemetry_if_disabled();
5673 let start = crate::instant::FzInstant::now();
5674 let mut computed_vertices = 0;
5675 let mut cycle_errors = 0;
5676
5677 let mut replan_iterations = 0;
5678 const MAX_REPLAN: usize = 5;
5679 let mut telemetry = self
5680 .config
5681 .enable_virtual_dep_telemetry
5682 .then(|| self.start_virtual_dep_telemetry());
5683
5684 loop {
5685 if cancel_flag.load(Ordering::Relaxed) {
5686 if let Some(mut t) = telemetry {
5687 t.bailout_reason = Some("cancelled");
5688 t.replan_iterations = replan_iterations;
5689 self.last_virtual_dep_telemetry = t;
5690 }
5691 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
5692 .with_message("Evaluation cancelled before scheduling".to_string()));
5693 }
5694
5695 let to_evaluate = self.graph.get_evaluation_vertices();
5696 if to_evaluate.is_empty() {
5697 if let Some(t) = telemetry.as_mut()
5698 && t.bailout_reason.is_none()
5699 {
5700 t.bailout_reason = Some("no_work");
5701 }
5702 break;
5703 }
5704
5705 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
5706 if let Some(t) = telemetry.as_mut() {
5707 Self::accumulate_schedule_meta(t, &meta);
5708 }
5709
5710 for cycle in &schedule.cycles {
5712 if cancel_flag.load(Ordering::Relaxed) {
5714 if let Some(mut t) = telemetry {
5715 t.bailout_reason = Some("cancelled");
5716 t.replan_iterations = replan_iterations;
5717 self.last_virtual_dep_telemetry = t;
5718 }
5719 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
5720 .with_message("Evaluation cancelled during cycle handling".to_string()));
5721 }
5722
5723 cycle_errors += 1;
5724 let circ_error = LiteralValue::Error(
5725 ExcelError::new(ExcelErrorKind::Circ)
5726 .with_message("Circular dependency detected".to_string()),
5727 );
5728 for &vertex_id in cycle {
5729 self.graph
5730 .update_vertex_value(vertex_id, circ_error.clone());
5731 self.mirror_vertex_value_to_overlay(vertex_id, &circ_error);
5732 }
5733 }
5734
5735 for layer in &schedule.layers {
5737 if cancel_flag.load(Ordering::Relaxed) {
5739 if let Some(mut t) = telemetry {
5740 t.bailout_reason = Some("cancelled");
5741 t.replan_iterations = replan_iterations;
5742 self.last_virtual_dep_telemetry = t;
5743 }
5744 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
5745 .with_message("Evaluation cancelled between layers".to_string()));
5746 }
5747
5748 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
5750 computed_vertices +=
5751 self.evaluate_layer_parallel_cancellable(layer, cancel_flag)?;
5752 } else {
5753 computed_vertices +=
5754 self.evaluate_layer_sequential_cancellable(layer, cancel_flag)?;
5755 }
5756 }
5757
5758 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
5759 if let Some(t) = telemetry.as_mut() {
5760 t.changed_vdeps_total += changed_vertices.len();
5761 }
5762 self.graph.clear_dirty_flags(&to_evaluate);
5763 for v in &changed_vertices {
5764 self.graph.set_dirty(*v, true);
5765 }
5766
5767 if changed_vertices.is_empty() {
5768 if let Some(t) = telemetry.as_mut() {
5769 t.bailout_reason = Some("converged");
5770 }
5771 break;
5772 }
5773 if replan_iterations >= MAX_REPLAN {
5774 if let Some(t) = telemetry.as_mut() {
5775 t.bailout_reason = Some("max_replan");
5776 }
5777 break;
5778 }
5779 replan_iterations += 1;
5780 }
5781
5782 if let Some(mut t) = telemetry {
5783 t.replan_iterations = replan_iterations;
5784 self.last_virtual_dep_telemetry = t;
5785 }
5786
5787 self.graph.redirty_volatiles();
5789 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
5790
5791 Ok(EvalResult {
5792 computed_vertices,
5793 cycle_errors,
5794 elapsed: start.elapsed(),
5795 })
5796 }
5797
5798 pub fn evaluate_until_cancellable(
5800 &mut self,
5801 targets: &[&str],
5802 cancel_flag: Arc<AtomicBool>,
5803 ) -> Result<EvalResult, ExcelError> {
5804 self.active_cancel_flag = Some(cancel_flag.clone());
5805 let res = self.evaluate_until_cancellable_impl(targets, &cancel_flag);
5806 self.active_cancel_flag = None;
5807 res
5808 }
5809
5810 fn evaluate_until_cancellable_impl(
5811 &mut self,
5812 targets: &[&str],
5813 cancel_flag: &AtomicBool,
5814 ) -> Result<EvalResult, ExcelError> {
5815 let start = crate::instant::FzInstant::now();
5816
5817 let mut target_addrs = Vec::new();
5819 for target in targets {
5820 let (sheet, row, col) = self.parse_a1_notation(target)?;
5821 let sheet_id = self.graph.sheet_id_mut(&sheet);
5822 let coord = Coord::from_excel(row, col, true, true);
5823 target_addrs.push(CellRef::new(sheet_id, coord));
5824 }
5825
5826 let mut target_vertex_ids = Vec::new();
5828 for addr in &target_addrs {
5829 if let Some(vertex_id) = self.graph.get_vertex_id_for_address(addr) {
5830 target_vertex_ids.push(*vertex_id);
5831 }
5832 }
5833
5834 if target_vertex_ids.is_empty() {
5835 return Ok(EvalResult {
5836 computed_vertices: 0,
5837 cycle_errors: 0,
5838 elapsed: start.elapsed(),
5839 });
5840 }
5841
5842 let (precedents_to_eval, vdeps) = self.build_demand_subgraph(&target_vertex_ids);
5844
5845 if precedents_to_eval.is_empty() {
5846 return Ok(EvalResult {
5847 computed_vertices: 0,
5848 cycle_errors: 0,
5849 elapsed: start.elapsed(),
5850 });
5851 }
5852
5853 let scheduler = Scheduler::new(&self.graph);
5855 let schedule = scheduler.create_schedule_with_virtual(&precedents_to_eval, &vdeps)?;
5856
5857 let mut cycle_errors = 0;
5859 for cycle in &schedule.cycles {
5860 if cancel_flag.load(Ordering::Relaxed) {
5862 return Err(ExcelError::new(ExcelErrorKind::Cancelled).with_message(
5863 "Demand-driven evaluation cancelled during cycle handling".to_string(),
5864 ));
5865 }
5866
5867 cycle_errors += 1;
5868 let circ_error = LiteralValue::Error(
5869 ExcelError::new(ExcelErrorKind::Circ)
5870 .with_message("Circular dependency detected".to_string()),
5871 );
5872 for &vertex_id in cycle {
5873 self.graph
5874 .update_vertex_value(vertex_id, circ_error.clone());
5875 self.mirror_vertex_value_to_overlay(vertex_id, &circ_error);
5876 }
5877 }
5878
5879 let mut computed_vertices = 0;
5881 for layer in &schedule.layers {
5882 if cancel_flag.load(Ordering::Relaxed) {
5884 return Err(ExcelError::new(ExcelErrorKind::Cancelled).with_message(
5885 "Demand-driven evaluation cancelled between layers".to_string(),
5886 ));
5887 }
5888
5889 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
5891 computed_vertices +=
5892 self.evaluate_layer_parallel_cancellable(layer, cancel_flag)?;
5893 } else {
5894 computed_vertices +=
5895 self.evaluate_layer_sequential_cancellable_demand_driven(layer, cancel_flag)?;
5896 }
5897 }
5898
5899 self.graph.clear_dirty_flags(&precedents_to_eval);
5901
5902 self.graph.redirty_volatiles();
5904
5905 Ok(EvalResult {
5906 computed_vertices,
5907 cycle_errors,
5908 elapsed: start.elapsed(),
5909 })
5910 }
5911
5912 fn parse_a1_notation(&self, address: &str) -> Result<(String, u32, u32), ExcelError> {
5913 let mut parts = address.splitn(2, '!');
5914 let first = parts.next().unwrap_or_default();
5915 let remainder = parts.next();
5916
5917 let (sheet, cell_part) = match remainder {
5918 Some(cell) => (first.to_string(), cell),
5919 None => (self.default_sheet_name().to_string(), first),
5920 };
5921
5922 let (row, col, _, _) = parse_a1_1based(cell_part).map_err(|err| {
5923 ExcelError::new(ExcelErrorKind::Ref)
5924 .with_message(format!("Invalid cell reference `{cell_part}`: {err}"))
5925 })?;
5926
5927 Ok((sheet, row, col))
5928 }
5929
5930 fn is_ast_volatile_with_provider(&self, ast: &ASTNode) -> bool {
5932 use formualizer_parse::parser::ASTNodeType;
5933 match &ast.node_type {
5934 ASTNodeType::Function { name, args, .. } => {
5935 if let Some(func) = self
5936 .get_function("", name)
5937 .or_else(|| crate::function_registry::get("", name))
5938 && func.caps().contains(crate::function::FnCaps::VOLATILE)
5939 {
5940 return true;
5941 }
5942 args.iter()
5943 .any(|arg| self.is_ast_volatile_with_provider(arg))
5944 }
5945 ASTNodeType::BinaryOp { left, right, .. } => {
5946 self.is_ast_volatile_with_provider(left)
5947 || self.is_ast_volatile_with_provider(right)
5948 }
5949 ASTNodeType::UnaryOp { expr, .. } => self.is_ast_volatile_with_provider(expr),
5950 ASTNodeType::Array(rows) => rows.iter().any(|row| {
5951 row.iter()
5952 .any(|cell| self.is_ast_volatile_with_provider(cell))
5953 }),
5954 _ => false,
5955 }
5956 }
5957
5958 fn find_dirty_precedents(&self, target_vertices: &[VertexId]) -> Vec<VertexId> {
5960 let mut to_evaluate = FxHashSet::default();
5961 let mut visited = FxHashSet::default();
5962 let mut stack = Vec::new();
5963
5964 for &target in target_vertices {
5966 stack.push(target);
5967 }
5968
5969 while let Some(vertex_id) = stack.pop() {
5970 if !visited.insert(vertex_id) {
5971 continue; }
5973
5974 if self.graph.vertex_exists(vertex_id) {
5975 let kind = self.graph.get_vertex_kind(vertex_id);
5977 let needs_eval = match kind {
5978 super::vertex::VertexKind::FormulaScalar
5979 | super::vertex::VertexKind::FormulaArray => {
5980 self.graph.is_dirty(vertex_id) || self.graph.is_volatile(vertex_id)
5981 }
5982 _ => false, };
5984
5985 if needs_eval {
5986 to_evaluate.insert(vertex_id);
5987 }
5988
5989 if let Some(dependencies) = self.graph.dependencies_slice(vertex_id) {
5991 for &dep_id in dependencies {
5992 if !visited.contains(&dep_id) {
5993 stack.push(dep_id);
5994 }
5995 }
5996 } else {
5997 let dependencies = self.graph.get_dependencies(vertex_id);
5998 for dep_id in dependencies {
5999 if !visited.contains(&dep_id) {
6000 stack.push(dep_id);
6001 }
6002 }
6003 }
6004 }
6005 }
6006
6007 let mut result: Vec<VertexId> = to_evaluate.into_iter().collect();
6008 result.sort_unstable();
6009 result
6010 }
6011
6012 fn evaluate_layer_sequential(
6014 &mut self,
6015 layer: &super::scheduler::Layer,
6016 ) -> Result<usize, ExcelError> {
6017 self.evaluate_layer_sequential_effects(layer)
6018 }
6019
6020 fn update_vertex_value_with_delta(
6021 &mut self,
6022 vertex_id: VertexId,
6023 new_value: LiteralValue,
6024 delta: &mut DeltaCollector,
6025 ) {
6026 if delta.mode != DeltaMode::Off
6027 && let Some(cell) = self.graph.get_cell_ref_for_vertex(vertex_id)
6028 {
6029 let sheet_name = self.graph.sheet_name(cell.sheet_id);
6030 let old = self
6031 .read_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
6032 .unwrap_or(LiteralValue::Empty);
6033 if old != new_value {
6034 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
6035 }
6036 }
6037 self.graph.update_vertex_value(vertex_id, new_value.clone());
6038 self.mirror_vertex_value_to_overlay(vertex_id, &new_value);
6039 }
6040
6041 fn evaluate_layer_sequential_with_delta(
6042 &mut self,
6043 layer: &super::scheduler::Layer,
6044 delta: &mut DeltaCollector,
6045 ) -> Result<usize, ExcelError> {
6046 self.evaluate_layer_sequential_with_delta_effects(layer, delta)
6047 }
6048
6049 fn evaluate_layer_sequential_cancellable(
6051 &mut self,
6052 layer: &super::scheduler::Layer,
6053 cancel_flag: &AtomicBool,
6054 ) -> Result<usize, ExcelError> {
6055 self.evaluate_layer_sequential_cancellable_effects(layer, cancel_flag)
6056 }
6057
6058 fn evaluate_layer_sequential_cancellable_demand_driven(
6060 &mut self,
6061 layer: &super::scheduler::Layer,
6062 cancel_flag: &AtomicBool,
6063 ) -> Result<usize, ExcelError> {
6064 self.evaluate_layer_sequential_cancellable_demand_driven_effects(layer, cancel_flag)
6065 }
6066
6067 fn evaluate_layer_parallel(
6069 &mut self,
6070 layer: &super::scheduler::Layer,
6071 ) -> Result<usize, ExcelError> {
6072 self.evaluate_layer_parallel_effects(layer)
6073 }
6074
6075 fn evaluate_layer_parallel_with_delta(
6076 &mut self,
6077 layer: &super::scheduler::Layer,
6078 delta: &mut DeltaCollector,
6079 ) -> Result<usize, ExcelError> {
6080 self.evaluate_layer_parallel_with_delta_effects(layer, delta)
6081 }
6082
6083 fn evaluate_layer_parallel_cancellable(
6085 &mut self,
6086 layer: &super::scheduler::Layer,
6087 cancel_flag: &AtomicBool,
6088 ) -> Result<usize, ExcelError> {
6089 self.evaluate_layer_parallel_cancellable_effects(layer, cancel_flag)
6090 }
6091
6092 fn apply_parallel_vertex_result(
6097 &mut self,
6098 vertex_id: VertexId,
6099 result: LiteralValue,
6100 mut delta: Option<&mut DeltaCollector>,
6101 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
6102 ) -> Result<(), ExcelError> {
6103 if let Some(cell) = self.graph.get_cell_ref(vertex_id)
6106 && let Some(owner) = self.graph.spill_registry_anchor_for_cell(cell)
6107 && owner != vertex_id
6108 {
6109 return Ok(());
6110 }
6111
6112 let kind = self.graph.get_vertex_kind(vertex_id);
6113
6114 let is_formula = matches!(kind, VertexKind::FormulaScalar | VertexKind::FormulaArray);
6116 if is_formula {
6117 match result {
6118 LiteralValue::Array(rows) => {
6119 self.apply_array_result_from_parallel(
6120 vertex_id,
6121 rows,
6122 delta.as_deref_mut(),
6123 overwritable_formulas,
6124 )?;
6125 }
6126 other => {
6127 self.apply_non_array_result_from_parallel(
6128 vertex_id,
6129 other,
6130 delta.as_deref_mut(),
6131 );
6132 }
6133 }
6134 return Ok(());
6135 }
6136
6137 if let Some(d) = delta {
6139 self.update_vertex_value_with_delta(vertex_id, result, d);
6140 } else {
6141 self.graph.update_vertex_value(vertex_id, result.clone());
6142 self.mirror_vertex_value_to_overlay(vertex_id, &result);
6143 }
6144 Ok(())
6145 }
6146
6147 fn apply_non_array_result_from_parallel(
6148 &mut self,
6149 vertex_id: VertexId,
6150 value: LiteralValue,
6151 delta: Option<&mut DeltaCollector>,
6152 ) {
6153 let spill_cells = self
6156 .graph
6157 .spill_cells_for_anchor(vertex_id)
6158 .map(|cells| cells.to_vec())
6159 .unwrap_or_default();
6160
6161 if let Some(d) = delta
6162 && d.mode != DeltaMode::Off
6163 && let Some(anchor) = self.graph.get_cell_ref_for_vertex(vertex_id)
6164 {
6165 if spill_cells.is_empty() {
6166 let old = self
6167 .read_cell_value(
6168 self.graph.sheet_name(anchor.sheet_id),
6169 anchor.coord.row() + 1,
6170 anchor.coord.col() + 1,
6171 )
6172 .unwrap_or(LiteralValue::Empty);
6173 if old != value {
6174 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
6175 }
6176 } else {
6177 for cell in spill_cells.iter() {
6178 let sheet_name = self.graph.sheet_name(cell.sheet_id);
6179 let old = self
6180 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
6181 .unwrap_or(LiteralValue::Empty);
6182 let new = if cell.sheet_id == anchor.sheet_id
6183 && cell.coord.row() == anchor.coord.row()
6184 && cell.coord.col() == anchor.coord.col()
6185 {
6186 value.clone()
6187 } else {
6188 LiteralValue::Empty
6189 };
6190 Self::record_cell_if_changed(d, cell, &old, &new);
6191 }
6192 }
6193 }
6194
6195 self.graph.clear_spill_region(vertex_id);
6196
6197 if self.config.arrow_storage_enabled
6198 && self.config.delta_overlay_enabled
6199 && self.config.write_formula_overlay_enabled
6200 {
6201 let empty = LiteralValue::Empty;
6202 for cell in spill_cells.iter() {
6203 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
6204 self.mirror_value_to_computed_overlay(
6205 &sheet_name,
6206 cell.coord.row() + 1,
6207 cell.coord.col() + 1,
6208 &empty,
6209 );
6210 }
6211 }
6212
6213 self.graph.update_vertex_value(vertex_id, value.clone());
6214 self.mirror_vertex_value_to_overlay(vertex_id, &value);
6215 }
6216
6217 fn apply_array_result_from_parallel(
6218 &mut self,
6219 vertex_id: VertexId,
6220 rows: Vec<Vec<LiteralValue>>,
6221 mut delta: Option<&mut DeltaCollector>,
6222 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
6223 ) -> Result<(), ExcelError> {
6224 self.graph
6226 .set_kind(vertex_id, crate::engine::vertex::VertexKind::FormulaArray);
6227
6228 let anchor = self
6229 .graph
6230 .get_cell_ref(vertex_id)
6231 .expect("cell ref for vertex");
6232 let sheet_id = anchor.sheet_id;
6233 let h = rows.len() as u32;
6234 let w = rows.first().map(|r| r.len()).unwrap_or(0) as u32;
6235
6236 let spill_cells = (h as u64).saturating_mul(w as u64);
6238 if spill_cells > self.config.spill.max_spill_cells as u64 {
6239 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
6240 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
6241 .with_message("SpillTooLarge")
6242 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
6243 expected_rows: h,
6244 expected_cols: w,
6245 });
6246 let spill_val = LiteralValue::Error(spill_err.clone());
6247 if let Some(d) = delta.as_deref_mut()
6248 && d.mode != DeltaMode::Off
6249 {
6250 let old = self
6251 .read_cell_value(
6252 self.graph.sheet_name(anchor.sheet_id),
6253 anchor.coord.row() + 1,
6254 anchor.coord.col() + 1,
6255 )
6256 .unwrap_or(LiteralValue::Empty);
6257 if old != spill_val {
6258 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
6259 }
6260 }
6261 self.graph.update_vertex_value(vertex_id, spill_val.clone());
6262 self.mirror_vertex_value_to_overlay(vertex_id, &spill_val);
6263 return Ok(());
6264 }
6265
6266 const PACKED_MAX_ROW: u32 = 1_048_575; const PACKED_MAX_COL: u32 = 16_383; let end_row = anchor.coord.row().saturating_add(h).saturating_sub(1);
6270 let end_col = anchor.coord.col().saturating_add(w).saturating_sub(1);
6271 if end_row > PACKED_MAX_ROW || end_col > PACKED_MAX_COL {
6272 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
6273 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
6274 .with_message("Spill exceeds sheet bounds")
6275 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
6276 expected_rows: h,
6277 expected_cols: w,
6278 });
6279 let spill_val = LiteralValue::Error(spill_err.clone());
6280 if let Some(d) = delta.as_deref_mut()
6281 && d.mode != DeltaMode::Off
6282 {
6283 let old = self
6284 .read_cell_value(
6285 self.graph.sheet_name(anchor.sheet_id),
6286 anchor.coord.row() + 1,
6287 anchor.coord.col() + 1,
6288 )
6289 .unwrap_or(LiteralValue::Empty);
6290 if old != spill_val {
6291 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
6292 }
6293 }
6294 self.graph.update_vertex_value(vertex_id, spill_val.clone());
6295 self.mirror_vertex_value_to_overlay(vertex_id, &spill_val);
6296 return Ok(());
6297 }
6298
6299 let mut targets = Vec::new();
6300 for r in 0..h {
6301 for c in 0..w {
6302 targets.push(self.graph.make_cell_ref_internal(
6303 sheet_id,
6304 anchor.coord.row() + r,
6305 anchor.coord.col() + c,
6306 ));
6307 }
6308 }
6309
6310 match self.spill_mgr.reserve(
6311 vertex_id,
6312 anchor,
6313 SpillShape { rows: h, cols: w },
6314 SpillMeta {
6315 epoch: self.recalc_epoch,
6316 config: self.config.spill,
6317 },
6318 ) {
6319 Ok(()) => {
6320 if let Err(e) = self.commit_spill_and_mirror(
6321 vertex_id,
6322 &targets,
6323 rows.clone(),
6324 delta.as_deref_mut(),
6325 overwritable_formulas,
6326 ) {
6327 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
6328 let err_val = LiteralValue::Error(e.clone());
6329 if let Some(d) = delta.as_deref_mut()
6330 && d.mode != DeltaMode::Off
6331 {
6332 let old = self
6333 .read_cell_value(
6334 self.graph.sheet_name(anchor.sheet_id),
6335 anchor.coord.row() + 1,
6336 anchor.coord.col() + 1,
6337 )
6338 .unwrap_or(LiteralValue::Empty);
6339 if old != err_val {
6340 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
6341 }
6342 }
6343 self.graph.update_vertex_value(vertex_id, err_val.clone());
6344 self.mirror_vertex_value_to_overlay(vertex_id, &err_val);
6345 return Ok(());
6346 }
6347
6348 let top_left = rows
6350 .first()
6351 .and_then(|r| r.first())
6352 .cloned()
6353 .unwrap_or(LiteralValue::Empty);
6354 self.graph.update_vertex_value(vertex_id, top_left.clone());
6355 self.mirror_vertex_value_to_overlay(vertex_id, &top_left);
6356 Ok(())
6357 }
6358 Err(e) => {
6359 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
6360 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
6361 .with_message(e.message.unwrap_or_else(|| "Spill blocked".to_string()))
6362 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
6363 expected_rows: h,
6364 expected_cols: w,
6365 });
6366 let spill_val = LiteralValue::Error(spill_err.clone());
6367 if let Some(d) = delta
6368 && d.mode != DeltaMode::Off
6369 {
6370 let old = self
6371 .read_cell_value(
6372 self.graph.sheet_name(anchor.sheet_id),
6373 anchor.coord.row() + 1,
6374 anchor.coord.col() + 1,
6375 )
6376 .unwrap_or(LiteralValue::Empty);
6377 if old != spill_val {
6378 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
6379 }
6380 }
6381 self.graph.update_vertex_value(vertex_id, spill_val.clone());
6382 self.mirror_vertex_value_to_overlay(vertex_id, &spill_val);
6383 Ok(())
6384 }
6385 }
6386 }
6387
6388 fn evaluate_vertex_immutable(&self, vertex_id: VertexId) -> Result<LiteralValue, ExcelError> {
6390 if !self.graph.vertex_exists(vertex_id) {
6392 return Err(ExcelError::new(formualizer_common::ExcelErrorKind::Ref)
6393 .with_message(format!("Vertex not found: {vertex_id:?}")));
6394 }
6395
6396 let kind = self.graph.get_vertex_kind(vertex_id);
6398 let sheet_id = self.graph.get_vertex_sheet_id(vertex_id);
6399
6400 let ast_id = match kind {
6401 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
6402 if let Some(ast_id) = self.graph.get_formula_id(vertex_id) {
6403 ast_id
6404 } else {
6405 return Ok(LiteralValue::Number(0.0));
6406 }
6407 }
6408 VertexKind::Empty | VertexKind::Cell => {
6409 if let Some(cell_ref) = self.graph.get_cell_ref(vertex_id) {
6410 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
6411 let row = cell_ref.coord.row() + 1;
6412 let col = cell_ref.coord.col() + 1;
6413 if let Some(v) = self.read_cell_value(sheet_name, row, col) {
6414 return Ok(v);
6415 }
6416 }
6417 return Ok(LiteralValue::Number(0.0));
6418 }
6419 VertexKind::NamedScalar => {
6420 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
6421 ExcelError::new(ExcelErrorKind::Name)
6422 .with_message("Named range metadata missing".to_string())
6423 })?;
6424
6425 return match &named_range.definition {
6426 NamedDefinition::Cell(cell_ref) => {
6427 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
6428 Ok(self
6429 .get_cell_value(
6430 sheet_name,
6431 cell_ref.coord.row() + 1,
6432 cell_ref.coord.col() + 1,
6433 )
6434 .unwrap_or(LiteralValue::Empty))
6435 }
6436 NamedDefinition::Literal(v) => Ok(v.clone()),
6437 NamedDefinition::Formula { ast, .. } => {
6438 let context_sheet = match named_range.scope {
6439 NameScope::Sheet(id) => id,
6440 NameScope::Workbook => sheet_id,
6441 };
6442 let sheet_name = self.graph.sheet_name(context_sheet);
6443 let cell_ref = self
6444 .graph
6445 .get_cell_ref(vertex_id)
6446 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
6447 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
6448 interpreter.evaluate_ast(ast).map(|cv| cv.into_literal())
6449 }
6450 NamedDefinition::Range(_) => Err(ExcelError::new(ExcelErrorKind::Value)
6451 .with_message("Range-valued name evaluated as scalar".to_string())),
6452 };
6453 }
6454 VertexKind::NamedArray => {
6455 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
6456 ExcelError::new(ExcelErrorKind::Name)
6457 .with_message("Named range metadata missing".to_string())
6458 })?;
6459
6460 return match &named_range.definition {
6461 NamedDefinition::Range(range_ref) => {
6462 if range_ref.start.sheet_id != range_ref.end.sheet_id {
6463 return Err(ExcelError::new(ExcelErrorKind::Ref)
6464 .with_message("Named range cannot span sheets".to_string()));
6465 }
6466 let sheet_name = self.graph.sheet_name(range_ref.start.sheet_id);
6467 let sr0 = range_ref.start.coord.row();
6468 let sc0 = range_ref.start.coord.col();
6469 let er0 = range_ref.end.coord.row();
6470 let ec0 = range_ref.end.coord.col();
6471 if sr0 > er0 || sc0 > ec0 {
6472 return Err(ExcelError::new(ExcelErrorKind::Ref)
6473 .with_message("Invalid named range bounds".to_string()));
6474 }
6475
6476 let h = (er0 - sr0 + 1) as usize;
6477 let w = (ec0 - sc0 + 1) as usize;
6478 let cell_count = (h as u64).saturating_mul(w as u64);
6479 if cell_count > self.config.spill.max_spill_cells as u64 {
6480 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
6481 "Named range too large to materialize as an array".to_string(),
6482 ));
6483 }
6484
6485 let mut rows = Vec::with_capacity(h);
6486 for r0 in sr0..=er0 {
6487 let mut row = Vec::with_capacity(w);
6488 for c0 in sc0..=ec0 {
6489 let v = self
6490 .get_cell_value(sheet_name, r0 + 1, c0 + 1)
6491 .unwrap_or(LiteralValue::Empty);
6492 row.push(v);
6493 }
6494 rows.push(row);
6495 }
6496 Ok(LiteralValue::Array(rows))
6497 }
6498 NamedDefinition::Cell(cell_ref) => {
6499 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
6500 let row = cell_ref.coord.row() + 1;
6501 let col = cell_ref.coord.col() + 1;
6502 let v = self
6503 .get_cell_value(sheet_name, row, col)
6504 .unwrap_or(LiteralValue::Empty);
6505 Ok(LiteralValue::Array(vec![vec![v]]))
6506 }
6507 NamedDefinition::Literal(v) => Ok(LiteralValue::Array(vec![vec![v.clone()]])),
6508 NamedDefinition::Formula { ast, .. } => {
6509 let context_sheet = match named_range.scope {
6510 NameScope::Sheet(id) => id,
6511 NameScope::Workbook => sheet_id,
6512 };
6513 let sheet_name = self.graph.sheet_name(context_sheet);
6514 let cell_ref = self
6515 .graph
6516 .get_cell_ref(vertex_id)
6517 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
6518 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
6519 match interpreter.evaluate_ast(ast) {
6520 Ok(cv) => {
6521 let v = cv.into_literal();
6522 match v {
6523 LiteralValue::Array(_) => Ok(v),
6524 other => Ok(LiteralValue::Array(vec![vec![other]])),
6525 }
6526 }
6527 Err(err) => Ok(LiteralValue::Error(err)),
6528 }
6529 }
6530 };
6531 }
6532 VertexKind::InfiniteRange
6533 | VertexKind::Range
6534 | VertexKind::External
6535 | VertexKind::Table => {
6536 return Ok(LiteralValue::Number(0.0));
6538 }
6539 };
6540
6541 let sheet_name = self.graph.sheet_name(sheet_id);
6543 let cell_ref = self
6544 .graph
6545 .get_cell_ref(vertex_id)
6546 .expect("cell ref for vertex");
6547 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
6548
6549 interpreter
6550 .evaluate_arena_ast(ast_id, self.graph.data_store(), self.graph.sheet_reg())
6551 .map(|cv| cv.into_literal())
6552 }
6553
6554 pub fn thread_pool(&self) -> Option<&Arc<rayon::ThreadPool>> {
6556 self.thread_pool.as_ref()
6557 }
6558}
6559
6560#[derive(Default)]
6561struct RowBoundsCache {
6562 snapshot: u64,
6563 map: rustc_hash::FxHashMap<(u32, usize), (Option<u32>, Option<u32>)>,
6565}
6566
6567impl RowBoundsCache {
6568 fn new(snapshot: u64) -> Self {
6569 Self {
6570 snapshot,
6571 map: Default::default(),
6572 }
6573 }
6574 fn get_row_bounds(
6575 &self,
6576 sheet_id: SheetId,
6577 col_idx: usize,
6578 snapshot: u64,
6579 ) -> Option<(Option<u32>, Option<u32>)> {
6580 if self.snapshot != snapshot {
6581 return None;
6582 }
6583 self.map.get(&(sheet_id as u32, col_idx)).copied()
6584 }
6585 fn put_row_bounds(
6586 &mut self,
6587 sheet_id: SheetId,
6588 col_idx: usize,
6589 snapshot: u64,
6590 bounds: (Option<u32>, Option<u32>),
6591 ) {
6592 if self.snapshot != snapshot {
6593 self.snapshot = snapshot;
6594 self.map.clear();
6595 }
6596 self.map.insert((sheet_id as u32, col_idx), bounds);
6597 }
6598}
6599
6600#[derive(Default)]
6602pub struct ShimSpillManager {
6603 region_locks: RegionLockManager,
6604 pub(crate) active_locks: rustc_hash::FxHashMap<VertexId, u64>,
6605}
6606
6607impl ShimSpillManager {
6608 pub(crate) fn reserve(
6609 &mut self,
6610 owner: VertexId,
6611 anchor_cell: CellRef,
6612 shape: SpillShape,
6613 _meta: SpillMeta,
6614 ) -> Result<(), ExcelError> {
6615 let region = crate::engine::spill::Region {
6617 sheet_id: anchor_cell.sheet_id as u32,
6618 row_start: anchor_cell.coord.row(),
6619 row_end: anchor_cell
6620 .coord
6621 .row()
6622 .saturating_add(shape.rows)
6623 .saturating_sub(1),
6624 col_start: anchor_cell.coord.col(),
6625 col_end: anchor_cell
6626 .coord
6627 .col()
6628 .saturating_add(shape.cols)
6629 .saturating_sub(1),
6630 };
6631 match self.region_locks.reserve(region, owner) {
6632 Ok(id) => {
6633 if id != 0 {
6634 self.active_locks.insert(owner, id);
6635 }
6636 Ok(())
6637 }
6638 Err(e) => Err(e),
6639 }
6640 }
6641
6642 pub(crate) fn commit_array_with_value_probe<F>(
6643 &mut self,
6644 graph: &mut DependencyGraph,
6645 anchor_vertex: VertexId,
6646 targets: &[CellRef],
6647 rows: Vec<Vec<LiteralValue>>,
6648 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
6649 mut value_probe: F,
6650 ) -> Result<(), ExcelError>
6651 where
6652 F: FnMut(&DependencyGraph, &CellRef) -> Option<LiteralValue>,
6653 {
6654 use formualizer_common::{ExcelErrorExtra, ExcelErrorKind};
6655
6656 let plan_res = graph.plan_spill_region_allowing_formula_overwrite(
6660 anchor_vertex,
6661 targets,
6662 overwritable_formulas,
6663 );
6664 if let Err(e) = plan_res {
6665 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
6666 self.region_locks.release(id);
6667 }
6668 return Err(e);
6669 }
6670
6671 if !graph.value_cache_enabled() {
6672 let (expected_rows, expected_cols) = if targets.is_empty() {
6674 (0u32, 0u32)
6675 } else {
6676 let mut min_r = u32::MAX;
6677 let mut max_r = 0u32;
6678 let mut min_c = u32::MAX;
6679 let mut max_c = 0u32;
6680 for cell in targets {
6681 let r = cell.coord.row();
6682 let c = cell.coord.col();
6683 min_r = min_r.min(r);
6684 max_r = max_r.max(r);
6685 min_c = min_c.min(c);
6686 max_c = max_c.max(c);
6687 }
6688 (
6689 max_r.saturating_sub(min_r).saturating_add(1),
6690 max_c.saturating_sub(min_c).saturating_add(1),
6691 )
6692 };
6693
6694 let anchor_cell = graph
6695 .get_cell_ref(anchor_vertex)
6696 .expect("anchor cell ref for spill commit");
6697
6698 for cell in targets {
6699 if *cell == anchor_cell {
6701 continue;
6702 }
6703 if graph.spill_registry_anchor_for_cell(*cell).is_some() {
6705 continue;
6706 }
6707 if let Some(&vid) = graph.get_vertex_id_for_address(cell)
6709 && vid != anchor_vertex
6710 {
6711 match graph.get_vertex_kind(vid) {
6712 crate::engine::vertex::VertexKind::FormulaScalar
6713 | crate::engine::vertex::VertexKind::FormulaArray => {
6714 continue;
6716 }
6717 _ => {}
6718 }
6719 }
6720
6721 if let Some(v) = value_probe(graph, cell)
6722 && !matches!(v, LiteralValue::Empty)
6723 {
6724 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
6725 self.region_locks.release(id);
6726 }
6727 return Err(ExcelError::new(ExcelErrorKind::Spill)
6728 .with_message("BlockedByValue")
6729 .with_extra(ExcelErrorExtra::Spill {
6730 expected_rows,
6731 expected_cols,
6732 }));
6733 }
6734 }
6735 }
6736
6737 let commit_res = graph.commit_spill_region_atomic_with_fault(
6738 anchor_vertex,
6739 targets.to_vec(),
6740 rows,
6741 None,
6742 );
6743 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
6744 self.region_locks.release(id);
6745 }
6746 commit_res.map(|_| ())
6747 }
6748
6749 pub(crate) fn commit_array_with_overlay<R: EvaluationContext>(
6751 &mut self,
6752 engine: &mut Engine<R>,
6753 anchor_vertex: VertexId,
6754 targets: &[CellRef],
6755 rows: Vec<Vec<LiteralValue>>,
6756 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
6757 ) -> Result<(), ExcelError> {
6758 let plan_res = engine.graph.plan_spill_region_allowing_formula_overwrite(
6760 anchor_vertex,
6761 targets,
6762 overwritable_formulas,
6763 );
6764 if let Err(e) = plan_res {
6765 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
6766 self.region_locks.release(id);
6767 }
6768 return Err(e);
6769 }
6770
6771 let commit_res = engine.graph.commit_spill_region_atomic_with_fault(
6772 anchor_vertex,
6773 targets.to_vec(),
6774 rows.clone(),
6775 None,
6776 );
6777 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
6778 self.region_locks.release(id);
6779 }
6780 commit_res.map(|_| ())?;
6781
6782 if engine.config.arrow_storage_enabled
6784 && engine.config.delta_overlay_enabled
6785 && engine.config.write_formula_overlay_enabled
6786 {
6787 for (idx, cell) in targets.iter().enumerate() {
6789 let (r_off, c_off) = {
6790 if rows.is_empty() || rows[0].is_empty() {
6791 (0usize, 0usize)
6792 } else {
6793 let width = rows[0].len();
6794 (idx / width, idx % width)
6795 }
6796 };
6797 let v = rows
6798 .get(r_off)
6799 .and_then(|r| r.get(c_off))
6800 .cloned()
6801 .unwrap_or(LiteralValue::Empty);
6802 let sheet_name = engine.graph.sheet_name(cell.sheet_id).to_string();
6803 engine.mirror_value_to_computed_overlay(
6804 &sheet_name,
6805 cell.coord.row() + 1,
6806 cell.coord.col() + 1,
6807 &v,
6808 );
6809 }
6810 }
6811 Ok(())
6812 }
6813}
6814
6815impl<R> Engine<R>
6816where
6817 R: EvaluationContext,
6818{
6819 fn resolve_shared_ref(
6820 &self,
6821 reference: &ReferenceType,
6822 current_sheet: &str,
6823 ) -> Result<formualizer_common::SheetRef<'static>, ExcelError> {
6824 use formualizer_common::{
6825 SheetCellRef as SharedCellRef, SheetLocator, SheetRangeRef as SharedRangeRef,
6826 SheetRef as SharedRef,
6827 };
6828
6829 let sr = match reference {
6831 ReferenceType::Cell {
6832 sheet,
6833 row,
6834 col,
6835 row_abs,
6836 col_abs,
6837 } => {
6838 let row0 = row
6839 .checked_sub(1)
6840 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
6841 let col0 = col
6842 .checked_sub(1)
6843 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
6844 let sheet_loc = match sheet.as_deref() {
6845 Some(name) => SheetLocator::from_name(name),
6846 None => SheetLocator::Current,
6847 };
6848 let coord = formualizer_common::RelativeCoord::new(row0, col0, *row_abs, *col_abs);
6849 SharedRef::Cell(SharedCellRef::new(sheet_loc, coord))
6850 }
6851 ReferenceType::Range {
6852 sheet,
6853 start_row,
6854 start_col,
6855 end_row,
6856 end_col,
6857 start_row_abs,
6858 start_col_abs,
6859 end_row_abs,
6860 end_col_abs,
6861 } => {
6862 let sheet_loc = match sheet.as_deref() {
6863 Some(name) => SheetLocator::from_name(name),
6864 None => SheetLocator::Current,
6865 };
6866 let sr = start_row
6867 .map(|r| {
6868 r.checked_sub(1)
6869 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
6870 })
6871 .transpose()?;
6872 let sc = start_col
6873 .map(|c| {
6874 c.checked_sub(1)
6875 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
6876 })
6877 .transpose()?;
6878 let er = end_row
6879 .map(|r| {
6880 r.checked_sub(1)
6881 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
6882 })
6883 .transpose()?;
6884 let ec = end_col
6885 .map(|c| {
6886 c.checked_sub(1)
6887 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
6888 })
6889 .transpose()?;
6890 let range = SharedRangeRef::from_parts(
6891 sheet_loc,
6892 sr.map(|idx| formualizer_common::AxisBound::new(idx, *start_row_abs)),
6893 sc.map(|idx| formualizer_common::AxisBound::new(idx, *start_col_abs)),
6894 er.map(|idx| formualizer_common::AxisBound::new(idx, *end_row_abs)),
6895 ec.map(|idx| formualizer_common::AxisBound::new(idx, *end_col_abs)),
6896 )
6897 .map_err(|_| ExcelError::new(ExcelErrorKind::Ref))?;
6898 SharedRef::Range(range)
6899 }
6900 _ => return Err(ExcelError::new(ExcelErrorKind::Ref)),
6901 };
6902
6903 let current_id = self
6904 .graph
6905 .sheet_id(current_sheet)
6906 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
6907
6908 let resolve_loc = |loc: SheetLocator<'_>| -> Result<SheetLocator<'static>, ExcelError> {
6909 match loc {
6910 SheetLocator::Current => Ok(SheetLocator::Id(current_id)),
6911 SheetLocator::Id(id) => Ok(SheetLocator::Id(id)),
6912 SheetLocator::Name(name) => {
6913 let n = name.as_ref();
6914 self.graph
6915 .sheet_id(n)
6916 .map(SheetLocator::Id)
6917 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
6918 }
6919 }
6920 };
6921
6922 match sr {
6923 SharedRef::Cell(cell) => {
6924 let owned = cell.into_owned();
6925 let sheet = resolve_loc(owned.sheet)?;
6926 Ok(SharedRef::Cell(SharedCellRef::new(sheet, owned.coord)))
6927 }
6928 SharedRef::Range(range) => {
6929 let owned = range.into_owned();
6930 let sheet = resolve_loc(owned.sheet)?;
6931 Ok(SharedRef::Range(SharedRangeRef {
6932 sheet,
6933 start_row: owned.start_row,
6934 start_col: owned.start_col,
6935 end_row: owned.end_row,
6936 end_col: owned.end_col,
6937 }))
6938 }
6939 }
6940 }
6941}
6942
6943impl<R> crate::traits::ReferenceResolver for Engine<R>
6946where
6947 R: EvaluationContext,
6948{
6949 fn resolve_cell_reference(
6950 &self,
6951 sheet: Option<&str>,
6952 row: u32,
6953 col: u32,
6954 ) -> Result<LiteralValue, ExcelError> {
6955 let sheet_name = sheet.unwrap_or_else(|| self.default_sheet_name()); if let Some(v) = self.get_cell_value(sheet_name, row, col) {
6959 Ok(v)
6960 } else {
6961 Ok(LiteralValue::Number(0.0))
6963 }
6964 }
6965}
6966
6967impl<R> crate::traits::RangeResolver for Engine<R>
6968where
6969 R: EvaluationContext,
6970{
6971 fn resolve_range_reference(
6972 &self,
6973 sheet: Option<&str>,
6974 sr: Option<u32>,
6975 sc: Option<u32>,
6976 er: Option<u32>,
6977 ec: Option<u32>,
6978 ) -> Result<Box<dyn crate::traits::Range>, ExcelError> {
6979 self.resolver.resolve_range_reference(sheet, sr, sc, er, ec)
6982 }
6983}
6984
6985impl<R> crate::traits::NamedRangeResolver for Engine<R>
6986where
6987 R: EvaluationContext,
6988{
6989 fn resolve_named_range_reference(
6990 &self,
6991 name: &str,
6992 ) -> Result<Vec<Vec<LiteralValue>>, ExcelError> {
6993 self.resolver.resolve_named_range_reference(name)
6994 }
6995}
6996
6997impl<R> crate::traits::TableResolver for Engine<R>
6998where
6999 R: EvaluationContext,
7000{
7001 fn resolve_table_reference(
7002 &self,
7003 tref: &formualizer_parse::parser::TableReference,
7004 ) -> Result<Box<dyn crate::traits::Table>, ExcelError> {
7005 self.resolver.resolve_table_reference(tref)
7006 }
7007}
7008
7009impl<R> crate::traits::SourceResolver for Engine<R>
7010where
7011 R: EvaluationContext,
7012{
7013 fn source_scalar_version(&self, name: &str) -> Option<u64> {
7014 self.resolver.source_scalar_version(name)
7015 }
7016
7017 fn resolve_source_scalar(&self, name: &str) -> Result<LiteralValue, ExcelError> {
7018 self.resolver.resolve_source_scalar(name)
7019 }
7020
7021 fn source_table_version(&self, name: &str) -> Option<u64> {
7022 self.resolver.source_table_version(name)
7023 }
7024
7025 fn resolve_source_table(
7026 &self,
7027 name: &str,
7028 ) -> Result<Box<dyn crate::traits::Table>, ExcelError> {
7029 self.resolver.resolve_source_table(name)
7030 }
7031}
7032
7033impl<R> crate::traits::Resolver for Engine<R> where R: EvaluationContext {}
7035
7036impl<R> crate::traits::FunctionProvider for Engine<R>
7038where
7039 R: EvaluationContext,
7040{
7041 fn get_function(
7042 &self,
7043 prefix: &str,
7044 name: &str,
7045 ) -> Option<std::sync::Arc<dyn crate::function::Function>> {
7046 self.resolver.get_function(prefix, name)
7047 }
7048}
7049
7050impl<R> crate::traits::EvaluationContext for Engine<R>
7052where
7053 R: EvaluationContext,
7054{
7055 fn clock(&self) -> &dyn crate::timezone::ClockProvider {
7056 self.clock.as_ref()
7057 }
7058
7059 fn thread_pool(&self) -> Option<&Arc<rayon::ThreadPool>> {
7060 self.thread_pool.as_ref()
7061 }
7062
7063 fn cancellation_token(&self) -> Option<Arc<std::sync::atomic::AtomicBool>> {
7064 self.active_cancel_flag.clone()
7065 }
7066
7067 fn chunk_hint(&self) -> Option<usize> {
7068 let hint =
7070 (self.config.stripe_height as usize).saturating_mul(self.config.stripe_width as usize);
7071 Some(hint.clamp(1024, 1 << 20)) }
7073
7074 fn volatile_level(&self) -> crate::traits::VolatileLevel {
7075 self.config.volatile_level
7076 }
7077
7078 fn workbook_seed(&self) -> u64 {
7079 self.config.workbook_seed
7080 }
7081
7082 fn recalc_epoch(&self) -> u64 {
7083 self.recalc_epoch
7084 }
7085
7086 fn used_rows_for_columns(
7087 &self,
7088 sheet: &str,
7089 start_col: u32,
7090 end_col: u32,
7091 ) -> Option<(u32, u32)> {
7092 let sheet_id = self.graph.sheet_id(sheet)?;
7094 let arrow_bounds = self
7095 .sheet_store()
7096 .sheet(sheet)
7097 .and_then(|_| self.arrow_used_row_bounds(sheet, start_col, end_col));
7098 let formula_bounds = self.formula_row_bounds_for_columns(sheet, start_col, end_col);
7099 if let Some(bounds) = Self::union_used_bounds(arrow_bounds, formula_bounds) {
7100 return Some(bounds);
7101 }
7102 let sc0 = start_col.saturating_sub(1);
7103 let ec0 = end_col.saturating_sub(1);
7104 self.graph
7105 .used_row_bounds_for_columns(sheet_id, sc0, ec0)
7106 .map(|(a0, b0)| (a0 + 1, b0 + 1))
7107 }
7108
7109 fn used_cols_for_rows(&self, sheet: &str, start_row: u32, end_row: u32) -> Option<(u32, u32)> {
7110 let sheet_id = self.graph.sheet_id(sheet)?;
7112 let arrow_bounds = self
7113 .sheet_store()
7114 .sheet(sheet)
7115 .and_then(|_| self.arrow_used_col_bounds(sheet, start_row, end_row));
7116 let formula_bounds = self.formula_col_bounds_for_rows(sheet, start_row, end_row);
7117 if let Some(bounds) = Self::union_used_bounds(arrow_bounds, formula_bounds) {
7118 return Some(bounds);
7119 }
7120 let sr0 = start_row.saturating_sub(1);
7121 let er0 = end_row.saturating_sub(1);
7122 self.graph
7123 .used_col_bounds_for_rows(sheet_id, sr0, er0)
7124 .map(|(a0, b0)| (a0 + 1, b0 + 1))
7125 }
7126
7127 fn sheet_bounds(&self, sheet: &str) -> Option<(u32, u32)> {
7128 let _ = self.graph.sheet_id(sheet)?;
7129 Some((1_048_576, 16_384)) }
7133
7134 fn data_snapshot_id(&self) -> u64 {
7135 self.snapshot_id.load(std::sync::atomic::Ordering::Relaxed)
7136 }
7137
7138 fn backend_caps(&self) -> crate::traits::BackendCaps {
7139 crate::traits::BackendCaps {
7140 streaming: true,
7141 used_region: true,
7142 write: false,
7143 tables: false,
7144 async_stream: false,
7145 }
7146 }
7147
7148 fn date_system(&self) -> crate::engine::DateSystem {
7151 self.config.date_system
7152 }
7153 fn resolve_range_view<'c>(
7155 &'c self,
7156 reference: &ReferenceType,
7157 current_sheet: &str,
7158 ) -> Result<RangeView<'c>, ExcelError> {
7159 match reference {
7160 ReferenceType::External(ext) => {
7161 let name = ext.raw.as_str();
7162 match ext.kind {
7163 formualizer_parse::parser::ExternalRefKind::Cell { .. } => {
7164 let Some(source) = self.graph.resolve_source_scalar_entry(name) else {
7165 return Err(ExcelError::new(ExcelErrorKind::Name)
7166 .with_message(format!("Undefined name: {name}")));
7167 };
7168 let version = source
7169 .version
7170 .or_else(|| self.resolver.source_scalar_version(name));
7171 let v = self.resolve_source_scalar_cached(name, version)?;
7172 Ok(RangeView::from_owned_rows(
7173 vec![vec![v]],
7174 self.config.date_system,
7175 ))
7176 }
7177 formualizer_parse::parser::ExternalRefKind::Range { .. } => {
7178 let Some(source) = self.graph.resolve_source_table_entry(name) else {
7179 return Err(ExcelError::new(ExcelErrorKind::Name)
7180 .with_message(format!("Undefined table: {name}")));
7181 };
7182 let version = source
7183 .version
7184 .or_else(|| self.resolver.source_table_version(name));
7185 let table = self.resolve_source_table_cached(name, version)?;
7186 let spec = Some(formualizer_parse::parser::TableSpecifier::Data);
7187 self.source_table_to_range_view(table.as_ref(), &spec)
7188 }
7189 }
7190 }
7191 ReferenceType::Range { .. } => {
7192 let shared = self.resolve_shared_ref(reference, current_sheet)?;
7193 let formualizer_common::SheetRef::Range(range) = shared else {
7194 return Err(ExcelError::new(ExcelErrorKind::Ref));
7195 };
7196 let sheet_id = match range.sheet {
7197 formualizer_common::SheetLocator::Id(id) => id,
7198 _ => return Err(ExcelError::new(ExcelErrorKind::Ref)),
7199 };
7200 let sheet_name = self.graph.sheet_name(sheet_id);
7201
7202 let bounded_range = if range.start_row.is_some()
7203 && range.start_col.is_some()
7204 && range.end_row.is_some()
7205 && range.end_col.is_some()
7206 {
7207 Some(RangeRef::try_from_shared(range.as_ref())?)
7208 } else {
7209 None
7210 };
7211
7212 let mut sr = bounded_range
7213 .as_ref()
7214 .map(|r| r.start.coord.row() + 1)
7215 .or_else(|| range.start_row.map(|b| b.index + 1));
7216 let mut sc = bounded_range
7217 .as_ref()
7218 .map(|r| r.start.coord.col() + 1)
7219 .or_else(|| range.start_col.map(|b| b.index + 1));
7220 let mut er = bounded_range
7221 .as_ref()
7222 .map(|r| r.end.coord.row() + 1)
7223 .or_else(|| range.end_row.map(|b| b.index + 1));
7224 let mut ec = bounded_range
7225 .as_ref()
7226 .map(|r| r.end.coord.col() + 1)
7227 .or_else(|| range.end_col.map(|b| b.index + 1));
7228
7229 if sr.is_none() && er.is_none() {
7230 let scv = sc.unwrap_or(1);
7232 let ecv = ec.unwrap_or(scv);
7233 sr = Some(1);
7234 if let Some((_, max_r)) = self.used_rows_for_columns(sheet_name, scv, ecv) {
7235 er = Some(max_r);
7236 } else if let Some((max_rows, _)) = self.sheet_bounds(sheet_name) {
7237 er = Some(self.config.max_open_ended_rows);
7238 }
7239 }
7240 if sc.is_none() && ec.is_none() {
7241 let srv = sr.unwrap_or(1);
7243 let erv = er.unwrap_or(srv);
7244 sc = Some(1);
7245 if let Some((_, max_c)) = self.used_cols_for_rows(sheet_name, srv, erv) {
7246 ec = Some(max_c);
7247 } else if let Some((_, max_cols)) = self.sheet_bounds(sheet_name) {
7248 ec = Some(self.config.max_open_ended_cols);
7249 }
7250 }
7251 if sr.is_some() && er.is_none() {
7252 let scv = sc.unwrap_or(1);
7253 let ecv = ec.unwrap_or(scv);
7254 if let Some((_, max_r)) = self.used_rows_for_columns(sheet_name, scv, ecv) {
7255 er = Some(max_r);
7256 } else if let Some((max_rows, _)) = self.sheet_bounds(sheet_name) {
7257 er = Some(self.config.max_open_ended_rows);
7258 }
7259 }
7260 if er.is_some() && sr.is_none() {
7261 sr = Some(1);
7263 }
7264 if sc.is_some() && ec.is_none() {
7265 let srv = sr.unwrap_or(1);
7266 let erv = er.unwrap_or(srv);
7267 if let Some((_, max_c)) = self.used_cols_for_rows(sheet_name, srv, erv) {
7268 ec = Some(max_c);
7269 } else if let Some((_, max_cols)) = self.sheet_bounds(sheet_name) {
7270 ec = Some(self.config.max_open_ended_cols);
7271 }
7272 }
7273 if ec.is_some() && sc.is_none() {
7274 sc = Some(1);
7276 }
7277
7278 let sr = sr.unwrap_or(1);
7279 let sc = sc.unwrap_or(1);
7280 let er = er.unwrap_or(sr.saturating_sub(1));
7281 let ec = ec.unwrap_or(sc.saturating_sub(1));
7282
7283 if self.force_materialize_range_views {
7284 if er < sr || ec < sc {
7285 return Ok(RangeView::from_owned_rows(
7286 Vec::new(),
7287 self.config.date_system,
7288 ));
7289 }
7290 let h = (er - sr + 1) as u64;
7291 let w = (ec - sc + 1) as u64;
7292 let cell_count = h.saturating_mul(w);
7293 if cell_count <= self.config.spill.max_spill_cells as u64 {
7294 let mut rows: Vec<Vec<LiteralValue>> = Vec::with_capacity(h as usize);
7295 for r in sr..=er {
7296 let mut rowv: Vec<LiteralValue> = Vec::with_capacity(w as usize);
7297 for c in sc..=ec {
7298 rowv.push(
7299 self.get_cell_value(sheet_name, r, c)
7300 .unwrap_or(LiteralValue::Empty),
7301 );
7302 }
7303 rows.push(rowv);
7304 }
7305 return Ok(RangeView::from_owned_rows(rows, self.config.date_system));
7306 }
7307 }
7308
7309 let Some(asheet) = self.sheet_store().sheet(sheet_name) else {
7310 return Ok(RangeView::from_owned_rows(
7311 Vec::new(),
7312 self.config.date_system,
7313 ));
7314 };
7315
7316 let rv = if er < sr || ec < sc {
7317 asheet.range_view(1, 1, 0, 0)
7318 } else {
7319 let sr0 = sr.saturating_sub(1) as usize;
7320 let sc0 = sc.saturating_sub(1) as usize;
7321 let er0 = er.saturating_sub(1) as usize;
7322 let ec0 = ec.saturating_sub(1) as usize;
7323 asheet.range_view(sr0, sc0, er0, ec0)
7324 };
7325
7326 Ok(rv)
7327 }
7328 ReferenceType::Cell { .. } => {
7329 let shared = self.resolve_shared_ref(reference, current_sheet)?;
7330 let formualizer_common::SheetRef::Cell(cell) = shared else {
7331 return Err(ExcelError::new(ExcelErrorKind::Ref));
7332 };
7333 let addr = CellRef::try_from_shared(cell)?;
7334 let sheet_id = addr.sheet_id;
7335 let sheet_name = self.graph.sheet_name(sheet_id);
7336 let row = addr.coord.row() + 1;
7337 let col = addr.coord.col() + 1;
7338
7339 if self.force_materialize_range_views {
7340 let v = self
7341 .get_cell_value(sheet_name, row, col)
7342 .unwrap_or(LiteralValue::Empty);
7343 return Ok(RangeView::from_owned_rows(
7344 vec![vec![v]],
7345 self.config.date_system,
7346 ));
7347 }
7348
7349 if let Some(asheet) = self.sheet_store().sheet(sheet_name) {
7350 let r0 = row.saturating_sub(1) as usize;
7351 let c0 = col.saturating_sub(1) as usize;
7352 let rv = asheet.range_view(r0, c0, r0, c0);
7353 Ok(rv)
7354 } else {
7355 let v = self
7356 .get_cell_value(sheet_name, row, col)
7357 .unwrap_or(LiteralValue::Empty);
7358 Ok(RangeView::from_owned_rows(
7359 vec![vec![v]],
7360 self.config.date_system,
7361 ))
7362 }
7363 }
7364 ReferenceType::NamedRange(name) => {
7365 if let Some(current_id) = self.graph.sheet_id(current_sheet)
7366 && let Some(named) = self.graph.resolve_name_entry(name, current_id)
7367 {
7368 match &named.definition {
7369 NamedDefinition::Cell(cell_ref) => {
7370 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
7371 if self.force_materialize_range_views {
7372 let v = self
7373 .get_cell_value(
7374 sheet_name,
7375 cell_ref.coord.row() + 1,
7376 cell_ref.coord.col() + 1,
7377 )
7378 .unwrap_or(LiteralValue::Empty);
7379 return Ok(RangeView::from_owned_rows(
7380 vec![vec![v]],
7381 self.config.date_system,
7382 ));
7383 } else {
7384 let asheet = self
7385 .sheet_store()
7386 .sheet(sheet_name)
7387 .expect("Arrow sheet missing for named cell");
7388 let r0 = cell_ref.coord.row() as usize;
7389 let c0 = cell_ref.coord.col() as usize;
7390 let rv = asheet.range_view(r0, c0, r0, c0);
7391 return Ok(rv);
7392 }
7393 }
7394 NamedDefinition::Range(range_ref) => {
7395 let sheet_name = self.graph.sheet_name(range_ref.start.sheet_id);
7396 let sr = range_ref.start.coord.row() + 1;
7397 let sc = range_ref.start.coord.col() + 1;
7398 let er = range_ref.end.coord.row() + 1;
7399 let ec = range_ref.end.coord.col() + 1;
7400 if self.force_materialize_range_views {
7401 let h = (er.saturating_sub(sr) + 1) as u64;
7402 let w = (ec.saturating_sub(sc) + 1) as u64;
7403 let cell_count = h.saturating_mul(w);
7404 if cell_count <= self.config.spill.max_spill_cells as u64 {
7405 let mut rows: Vec<Vec<LiteralValue>> =
7406 Vec::with_capacity(h as usize);
7407 for r in sr..=er {
7408 let mut rowv: Vec<LiteralValue> =
7409 Vec::with_capacity(w as usize);
7410 for c in sc..=ec {
7411 rowv.push(
7412 self.get_cell_value(sheet_name, r, c)
7413 .unwrap_or(LiteralValue::Empty),
7414 );
7415 }
7416 rows.push(rowv);
7417 }
7418 return Ok(RangeView::from_owned_rows(
7419 rows,
7420 self.config.date_system,
7421 ));
7422 }
7423 }
7424 let asheet = self
7425 .sheet_store()
7426 .sheet(sheet_name)
7427 .expect("Arrow sheet missing for named range");
7428 let sr0 = range_ref.start.coord.row() as usize;
7429 let sc0 = range_ref.start.coord.col() as usize;
7430 let er0 = range_ref.end.coord.row() as usize;
7431 let ec0 = range_ref.end.coord.col() as usize;
7432 let rv = asheet.range_view(sr0, sc0, er0, ec0);
7433 return Ok(rv);
7434 }
7435 NamedDefinition::Literal(v) => {
7436 return Ok(RangeView::from_owned_rows(
7437 vec![vec![v.clone()]],
7438 self.config.date_system,
7439 ));
7440 }
7441 NamedDefinition::Formula { .. } => {
7442 if let Some(value) = self.graph.get_value(named.vertex) {
7443 return Ok(RangeView::from_owned_rows(
7444 vec![vec![value]],
7445 self.config.date_system,
7446 ));
7447 }
7448 }
7449 }
7450 }
7451
7452 if let Some(source) = self.graph.resolve_source_scalar_entry(name) {
7453 let version = source
7454 .version
7455 .or_else(|| self.resolver.source_scalar_version(name));
7456 let v = self.resolve_source_scalar_cached(name, version)?;
7457 return Ok(RangeView::from_owned_rows(
7458 vec![vec![v]],
7459 self.config.date_system,
7460 ));
7461 }
7462
7463 let data = self.resolver.resolve_named_range_reference(name)?;
7464 Ok(RangeView::from_owned_rows(data, self.config.date_system))
7465 }
7466 ReferenceType::Table(tref) => {
7467 if let Some(table) = self.graph.resolve_table_entry(&tref.name) {
7468 let sheet_name = self.graph.sheet_name(table.range.start.sheet_id);
7469 let asheet = self
7470 .sheet_store()
7471 .sheet(sheet_name)
7472 .expect("Arrow sheet missing for table reference");
7473
7474 let sr0 = table.range.start.coord.row() as usize;
7475 let sc0 = table.range.start.coord.col() as usize;
7476 let er0 = table.range.end.coord.row() as usize;
7477 let ec0 = table.range.end.coord.col() as usize;
7478
7479 let has_totals = table.totals_row;
7480 let has_headers = table.header_row;
7481 let data_sr = if has_headers {
7482 sr0.saturating_add(1)
7483 } else {
7484 sr0
7485 };
7486 let data_er = if has_totals {
7487 er0.saturating_sub(1)
7488 } else {
7489 er0
7490 };
7491
7492 let select = |sr: usize, sc: usize, er: usize, ec: usize| {
7493 if sr > er || sc > ec {
7494 asheet.range_view(1, 1, 0, 0)
7495 } else {
7496 asheet.range_view(sr, sc, er, ec)
7497 }
7498 };
7499
7500 let av = match &tref.specifier {
7501 None => {
7502 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
7503 "Table reference without specifier is unsupported".to_string(),
7504 ));
7505 }
7506 Some(formualizer_parse::parser::TableSpecifier::Column(col)) => {
7507 let Some(idx) = table.col_index(col) else {
7508 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
7509 "Column refers to unknown table column".to_string(),
7510 ));
7511 };
7512 let c0 = sc0 + idx;
7513 select(data_sr, c0, data_er, c0)
7514 }
7515 Some(formualizer_parse::parser::TableSpecifier::ColumnRange(
7516 start,
7517 end,
7518 )) => {
7519 let Some(si) = table.col_index(start) else {
7520 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
7521 "Column range refers to unknown column(s)".to_string(),
7522 ));
7523 };
7524 let Some(ei) = table.col_index(end) else {
7525 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
7526 "Column range refers to unknown column(s)".to_string(),
7527 ));
7528 };
7529 let (mut a, mut b) = (si, ei);
7530 if a > b {
7531 std::mem::swap(&mut a, &mut b);
7532 }
7533 let c_start = sc0 + a;
7534 let c_end = sc0 + b;
7535 select(data_sr, c_start, data_er, c_end)
7536 }
7537 Some(formualizer_parse::parser::TableSpecifier::All)
7538 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
7539 formualizer_parse::parser::SpecialItem::All,
7540 )) => select(sr0, sc0, er0, ec0),
7541 Some(formualizer_parse::parser::TableSpecifier::Data)
7542 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
7543 formualizer_parse::parser::SpecialItem::Data,
7544 )) => select(data_sr, sc0, data_er, ec0),
7545 Some(formualizer_parse::parser::TableSpecifier::Headers)
7546 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
7547 formualizer_parse::parser::SpecialItem::Headers,
7548 )) => {
7549 if !has_headers {
7550 asheet.range_view(1, 1, 0, 0)
7551 } else {
7552 select(sr0, sc0, sr0, ec0)
7553 }
7554 }
7555 Some(formualizer_parse::parser::TableSpecifier::Totals)
7556 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
7557 formualizer_parse::parser::SpecialItem::Totals,
7558 )) => {
7559 if !has_totals {
7560 asheet.range_view(1, 1, 0, 0)
7561 } else {
7562 select(er0, sc0, er0, ec0)
7563 }
7564 }
7565 Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
7566 formualizer_parse::parser::SpecialItem::ThisRow,
7567 )) => {
7568 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
7569 "@ (This Row) requires table-aware context; not yet supported"
7570 .to_string(),
7571 ));
7572 }
7573 Some(formualizer_parse::parser::TableSpecifier::Row(_))
7574 | Some(formualizer_parse::parser::TableSpecifier::Combination(_)) => {
7575 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
7576 "Complex structured references not yet supported".to_string(),
7577 ));
7578 }
7579 };
7580
7581 return Ok(av);
7582 }
7583
7584 if let Some(source) = self.graph.resolve_source_table_entry(&tref.name) {
7585 let version = source
7586 .version
7587 .or_else(|| self.resolver.source_table_version(&tref.name));
7588 let table = self.resolve_source_table_cached(&tref.name, version)?;
7589 return self.source_table_to_range_view(table.as_ref(), &tref.specifier);
7590 }
7591
7592 let boxed = self.resolve_range_like(&ReferenceType::Table(tref.clone()))?;
7594 let owned = boxed.materialise().into_owned();
7595 Ok(RangeView::from_owned_rows(owned, self.config.date_system))
7596 }
7597 }
7598 }
7599
7600 fn resolve_cell_reference_value(
7601 &self,
7602 sheet: Option<&str>,
7603 row: u32,
7604 col: u32,
7605 current_sheet: &str,
7606 ) -> Result<LiteralValue, ExcelError> {
7607 let sheet_name = sheet.unwrap_or(current_sheet);
7608 if self.graph.sheet_id(sheet_name).is_none() {
7609 return Err(ExcelError::new(ExcelErrorKind::Ref));
7610 }
7611 Ok(self
7612 .get_cell_value(sheet_name, row, col)
7613 .unwrap_or(LiteralValue::Empty))
7614 }
7615
7616 fn build_criteria_mask(
7617 &self,
7618 view: &RangeView<'_>,
7619 col_in_view: usize,
7620 pred: &crate::args::CriteriaPredicate,
7621 ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
7622 if view.dims().1 == 0 {
7623 return None;
7624 }
7625 let sheet_rows = view.sheet().nrows as usize;
7628 if sheet_rows == 0 || view.start_row() >= sheet_rows {
7629 return Some(std::sync::Arc::new(arrow_array::BooleanArray::new_null(0)));
7630 }
7631 compute_criteria_mask(view, col_in_view, pred)
7632 }
7633
7634 fn build_row_visibility_mask(
7635 &self,
7636 view: &RangeView<'_>,
7637 mode: VisibilityMaskMode,
7638 ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
7639 self.build_row_visibility_mask_for_view(view, mode)
7640 }
7641}
7642
7643impl<R> Engine<R>
7644where
7645 R: EvaluationContext,
7646{
7647 fn clear_spill_projection_and_mirror(
7648 &mut self,
7649 anchor_vertex: VertexId,
7650 delta: Option<&mut DeltaCollector>,
7651 ) {
7652 let spill_cells = self
7653 .graph
7654 .spill_cells_for_anchor(anchor_vertex)
7655 .map(|cells| cells.to_vec())
7656 .unwrap_or_default();
7657 if spill_cells.is_empty() {
7658 return;
7659 }
7660
7661 if let Some(delta) = delta
7662 && delta.mode != DeltaMode::Off
7663 {
7664 let empty = LiteralValue::Empty;
7665 for cell in spill_cells.iter() {
7666 let sheet_name = self.graph.sheet_name(cell.sheet_id);
7667 let old = self
7668 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
7669 .unwrap_or(LiteralValue::Empty);
7670 if old != empty {
7671 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
7672 }
7673 }
7674 }
7675
7676 self.graph.clear_spill_region(anchor_vertex);
7677
7678 if self.config.arrow_storage_enabled
7679 && self.config.delta_overlay_enabled
7680 && self.config.write_formula_overlay_enabled
7681 {
7682 let empty = LiteralValue::Empty;
7683 for cell in spill_cells.iter() {
7684 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
7685 self.mirror_value_to_computed_overlay(
7686 &sheet_name,
7687 cell.coord.row() + 1,
7688 cell.coord.col() + 1,
7689 &empty,
7690 );
7691 }
7692 }
7693 }
7694
7695 fn commit_spill_and_mirror(
7697 &mut self,
7698 anchor_vertex: VertexId,
7699 targets: &[CellRef],
7700 rows: Vec<Vec<LiteralValue>>,
7701 delta: Option<&mut DeltaCollector>,
7702 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
7703 ) -> Result<(), ExcelError> {
7704 let prev_spill_cells = self
7705 .graph
7706 .spill_cells_for_anchor(anchor_vertex)
7707 .map(|cells| cells.to_vec())
7708 .unwrap_or_default();
7709
7710 if let Some(delta) = delta
7711 && delta.mode != DeltaMode::Off
7712 {
7713 let target_set: FxHashSet<CellRef> = targets.iter().copied().collect();
7714 let empty = LiteralValue::Empty;
7715
7716 for cell in prev_spill_cells.iter() {
7718 if target_set.contains(cell) {
7719 continue;
7720 }
7721 let sheet_name = self.graph.sheet_name(cell.sheet_id);
7722 let old = self
7723 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
7724 .unwrap_or(LiteralValue::Empty);
7725 if old != empty {
7726 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
7727 }
7728 }
7729
7730 if !targets.is_empty() && !rows.is_empty() && !rows[0].is_empty() {
7732 let width = rows[0].len();
7733 for (idx, cell) in targets.iter().enumerate() {
7734 let r_off = idx / width;
7735 let c_off = idx % width;
7736 let new = rows
7737 .get(r_off)
7738 .and_then(|r| r.get(c_off))
7739 .cloned()
7740 .unwrap_or(LiteralValue::Empty);
7741 let sheet_name = self.graph.sheet_name(cell.sheet_id);
7742 let old = self
7743 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
7744 .unwrap_or(LiteralValue::Empty);
7745 if old != new {
7746 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
7747 }
7748 }
7749 } else {
7750 for cell in targets.iter() {
7752 let sheet_name = self.graph.sheet_name(cell.sheet_id);
7753 let old = self
7754 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
7755 .unwrap_or(LiteralValue::Empty);
7756 if !matches!(old, LiteralValue::Empty) {
7757 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
7758 }
7759 }
7760 }
7761 }
7762
7763 let arrow_sheets = &self.arrow_sheets;
7766 self.spill_mgr.commit_array_with_value_probe(
7767 &mut self.graph,
7768 anchor_vertex,
7769 targets,
7770 rows.clone(),
7771 overwritable_formulas,
7772 |g, cell| {
7773 let sheet_name = g.sheet_name(cell.sheet_id);
7774 let asheet = arrow_sheets.sheet(sheet_name)?;
7775 let r0 = cell.coord.row() as usize;
7776 let c0 = cell.coord.col() as usize;
7777 let v = asheet.get_cell_value(r0, c0);
7778 if matches!(v, LiteralValue::Empty) {
7779 None
7780 } else {
7781 Some(v)
7782 }
7783 },
7784 )?;
7785
7786 if self.config.arrow_storage_enabled
7787 && self.config.delta_overlay_enabled
7788 && self.config.write_formula_overlay_enabled
7789 {
7790 if !prev_spill_cells.is_empty() {
7791 let target_set: FxHashSet<CellRef> = targets.iter().copied().collect();
7792 let empty = LiteralValue::Empty;
7793 for cell in prev_spill_cells.iter() {
7794 if !target_set.contains(cell) {
7795 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
7796 self.mirror_value_to_computed_overlay(
7797 &sheet_name,
7798 cell.coord.row() + 1,
7799 cell.coord.col() + 1,
7800 &empty,
7801 );
7802 }
7803 }
7804 }
7805
7806 for (idx, cell) in targets.iter().enumerate() {
7807 if rows.is_empty() || rows[0].is_empty() {
7808 break;
7809 }
7810 let width = rows[0].len();
7811 let r_off = idx / width;
7812 let c_off = idx % width;
7813 let v = rows[r_off][c_off].clone();
7814 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
7815 self.mirror_value_to_computed_overlay(
7816 &sheet_name,
7817 cell.coord.row() + 1,
7818 cell.coord.col() + 1,
7819 &v,
7820 );
7821 }
7822 }
7823 Ok(())
7824 }
7825}
7826
7827use crate::engine::effects::Effect;
7832use crate::engine::graph::editor::change_log::{ChangeEvent, ChangeLog, SpillSnapshot};
7833
7834impl<R> Engine<R>
7835where
7836 R: EvaluationContext,
7837{
7838 pub(crate) fn plan_vertex_effects(
7845 &mut self,
7846 vertex_id: VertexId,
7847 computed_value: LiteralValue,
7848 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
7849 ) -> Result<Vec<Effect>, ExcelError> {
7850 let kind = self.graph.get_vertex_kind(vertex_id);
7851 let is_formula = matches!(kind, VertexKind::FormulaScalar | VertexKind::FormulaArray);
7852
7853 if !is_formula {
7857 if let Some(cell) = self.graph.get_cell_ref(vertex_id)
7858 && let Some(owner) = self.graph.spill_registry_anchor_for_cell(cell)
7859 && owner != vertex_id
7860 {
7861 return Ok(Vec::new());
7862 }
7863 return Ok(vec![Effect::WriteCell {
7865 vertex_id,
7866 value: computed_value,
7867 }]);
7868 }
7869
7870 match computed_value {
7871 LiteralValue::Array(rows) => {
7872 self.plan_array_effects(vertex_id, rows, overwritable_formulas)
7873 }
7874 other => self.plan_scalar_effects(vertex_id, other),
7875 }
7876 }
7877
7878 fn plan_scalar_effects(
7880 &self,
7881 vertex_id: VertexId,
7882 value: LiteralValue,
7883 ) -> Result<Vec<Effect>, ExcelError> {
7884 let has_spill = self
7885 .graph
7886 .spill_cells_for_anchor(vertex_id)
7887 .is_some_and(|c| !c.is_empty());
7888
7889 let mut effects = Vec::new();
7890 if has_spill {
7891 effects.push(Effect::SpillClear {
7892 anchor_vertex: vertex_id,
7893 });
7894 }
7895 effects.push(Effect::WriteCell { vertex_id, value });
7896 Ok(effects)
7897 }
7898
7899 fn plan_array_effects(
7901 &mut self,
7902 vertex_id: VertexId,
7903 rows: Vec<Vec<LiteralValue>>,
7904 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
7905 ) -> Result<Vec<Effect>, ExcelError> {
7906 self.graph.set_kind(vertex_id, VertexKind::FormulaArray);
7908
7909 let anchor = self
7910 .graph
7911 .get_cell_ref(vertex_id)
7912 .expect("cell ref for vertex");
7913 let sheet_id = anchor.sheet_id;
7914 let h = rows.len() as u32;
7915 let w = rows.first().map(|r| r.len()).unwrap_or(0) as u32;
7916
7917 let spill_cells = (h as u64).saturating_mul(w as u64);
7919 if spill_cells > self.config.spill.max_spill_cells as u64 {
7920 return self.plan_spill_error_effects(vertex_id, "SpillTooLarge", h, w);
7921 }
7922
7923 const PACKED_MAX_ROW: u32 = 1_048_575;
7925 const PACKED_MAX_COL: u32 = 16_383;
7926 let end_row = anchor.coord.row().saturating_add(h).saturating_sub(1);
7927 let end_col = anchor.coord.col().saturating_add(w).saturating_sub(1);
7928 if end_row > PACKED_MAX_ROW || end_col > PACKED_MAX_COL {
7929 return self.plan_spill_error_effects(vertex_id, "Spill exceeds sheet bounds", h, w);
7930 }
7931
7932 let mut targets = Vec::new();
7933 for r in 0..h {
7934 for c in 0..w {
7935 targets.push(self.graph.make_cell_ref_internal(
7936 sheet_id,
7937 anchor.coord.row() + r,
7938 anchor.coord.col() + c,
7939 ));
7940 }
7941 }
7942
7943 match self.spill_mgr.reserve(
7945 vertex_id,
7946 anchor,
7947 SpillShape { rows: h, cols: w },
7948 SpillMeta {
7949 epoch: self.recalc_epoch,
7950 config: self.config.spill,
7951 },
7952 ) {
7953 Ok(()) => {
7954 if let Err(_e) = self.graph.plan_spill_region_allowing_formula_overwrite(
7956 vertex_id,
7957 &targets,
7958 overwritable_formulas,
7959 ) {
7960 return self.plan_spill_error_effects(vertex_id, "Spill blocked", h, w);
7961 }
7962
7963 if !self.graph.value_cache_enabled() {
7967 let sheet_name = self.graph.sheet_name(sheet_id);
7968 if let Some(asheet) = self.sheet_store().sheet(sheet_name) {
7969 for cell in targets.iter() {
7970 if *cell == anchor {
7972 continue;
7973 }
7974 if self.graph.spill_registry_anchor_for_cell(*cell).is_some() {
7976 continue;
7977 }
7978 if let Some(&vid) = self.graph.get_vertex_id_for_address(cell)
7980 && vid != vertex_id
7981 {
7982 match self.graph.get_vertex_kind(vid) {
7983 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
7984 continue;
7985 }
7986 _ => {}
7987 }
7988 }
7989
7990 let v = asheet.get_cell_value(
7991 cell.coord.row() as usize,
7992 cell.coord.col() as usize,
7993 );
7994 if !matches!(v, LiteralValue::Empty) {
7995 return self.plan_spill_error_effects(
7996 vertex_id,
7997 "BlockedByValue",
7998 h,
7999 w,
8000 );
8001 }
8002 }
8003 }
8004 }
8005
8006 let top_left = rows
8007 .first()
8008 .and_then(|r| r.first())
8009 .cloned()
8010 .unwrap_or(LiteralValue::Empty);
8011
8012 let mut effects = Vec::new();
8013 let has_prev = self
8015 .graph
8016 .spill_cells_for_anchor(vertex_id)
8017 .is_some_and(|c| !c.is_empty());
8018 if has_prev {
8019 effects.push(Effect::SpillClear {
8020 anchor_vertex: vertex_id,
8021 });
8022 }
8023 effects.push(Effect::SpillCommit {
8024 anchor_vertex: vertex_id,
8025 anchor_cell: anchor,
8026 target_cells: targets,
8027 values: rows,
8028 });
8029 effects.push(Effect::WriteCell {
8030 vertex_id,
8031 value: top_left,
8032 });
8033 Ok(effects)
8034 }
8035 Err(e) => {
8036 let msg = e.message.unwrap_or_else(|| "Spill blocked".to_string());
8037 self.plan_spill_error_effects(vertex_id, &msg, h, w)
8038 }
8039 }
8040 }
8041
8042 fn plan_spill_error_effects(
8044 &self,
8045 vertex_id: VertexId,
8046 message: &str,
8047 expected_rows: u32,
8048 expected_cols: u32,
8049 ) -> Result<Vec<Effect>, ExcelError> {
8050 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
8051 .with_message(message)
8052 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
8053 expected_rows,
8054 expected_cols,
8055 });
8056 let spill_val = LiteralValue::Error(spill_err);
8057
8058 let effects = vec![
8059 Effect::SpillClear {
8060 anchor_vertex: vertex_id,
8061 },
8062 Effect::WriteCell {
8063 vertex_id,
8064 value: spill_val,
8065 },
8066 ];
8067 Ok(effects)
8068 }
8069
8070 pub(crate) fn apply_effect(
8072 &mut self,
8073 effect: &Effect,
8074 delta: Option<&mut DeltaCollector>,
8075 log: Option<&mut ChangeLog>,
8076 ) -> Result<(), ExcelError> {
8077 match effect {
8078 Effect::WriteCell { vertex_id, value } => {
8079 self.apply_write_cell(*vertex_id, value, delta);
8080 }
8081 Effect::SpillClear { anchor_vertex } => {
8082 self.apply_spill_clear(*anchor_vertex, delta, log);
8083 }
8084 Effect::SpillCommit {
8085 anchor_vertex,
8086 anchor_cell: _,
8087 target_cells,
8088 values,
8089 } => {
8090 self.apply_spill_commit(*anchor_vertex, target_cells, values.clone(), delta, log)?;
8091 }
8092 }
8093 Ok(())
8094 }
8095
8096 fn apply_write_cell(
8098 &mut self,
8099 vertex_id: VertexId,
8100 value: &LiteralValue,
8101 delta: Option<&mut DeltaCollector>,
8102 ) {
8103 if let Some(d) = delta
8104 && d.mode != DeltaMode::Off
8105 && let Some(cell) = self.graph.get_cell_ref_for_vertex(vertex_id)
8106 {
8107 let sheet_name = self.graph.sheet_name(cell.sheet_id);
8108 let old = self
8109 .read_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
8110 .unwrap_or(LiteralValue::Empty);
8111 if old != *value {
8112 d.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
8113 }
8114 }
8115 self.graph.update_vertex_value(vertex_id, value.clone());
8116 self.mirror_vertex_value_to_overlay(vertex_id, value);
8117 }
8118
8119 fn apply_spill_clear(
8121 &mut self,
8122 anchor_vertex: VertexId,
8123 delta: Option<&mut DeltaCollector>,
8124 log: Option<&mut ChangeLog>,
8125 ) {
8126 let spill_cells = self
8127 .graph
8128 .spill_cells_for_anchor(anchor_vertex)
8129 .map(|cells| cells.to_vec())
8130 .unwrap_or_default();
8131 if spill_cells.is_empty() {
8132 return;
8133 }
8134
8135 let snapshot = if log.is_some() {
8137 self.snapshot_spill_for_anchor(anchor_vertex)
8138 } else {
8139 None
8140 };
8141
8142 if let Some(d) = delta
8144 && d.mode != DeltaMode::Off
8145 {
8146 let empty = LiteralValue::Empty;
8147 for cell in spill_cells.iter() {
8148 let sheet_name = self.graph.sheet_name(cell.sheet_id);
8149 let old = self
8150 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
8151 .unwrap_or(LiteralValue::Empty);
8152 if old != empty {
8153 d.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
8154 }
8155 }
8156 }
8157
8158 self.graph.clear_spill_region(anchor_vertex);
8159
8160 if self.config.arrow_storage_enabled
8162 && self.config.delta_overlay_enabled
8163 && self.config.write_formula_overlay_enabled
8164 {
8165 let empty = LiteralValue::Empty;
8166 for cell in spill_cells.iter() {
8167 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
8168 self.mirror_value_to_computed_overlay(
8169 &sheet_name,
8170 cell.coord.row() + 1,
8171 cell.coord.col() + 1,
8172 &empty,
8173 );
8174 }
8175 }
8176
8177 if let Some(log) = log
8179 && let Some(old) = snapshot
8180 {
8181 log.record(ChangeEvent::SpillCleared {
8182 anchor: anchor_vertex,
8183 old,
8184 });
8185 }
8186 }
8187
8188 fn apply_spill_commit(
8190 &mut self,
8191 anchor_vertex: VertexId,
8192 target_cells: &[CellRef],
8193 values: Vec<Vec<LiteralValue>>,
8194 delta: Option<&mut DeltaCollector>,
8195 log: Option<&mut ChangeLog>,
8196 ) -> Result<(), ExcelError> {
8197 let old_snapshot = if log.is_some() {
8199 self.snapshot_spill_for_anchor(anchor_vertex)
8200 } else {
8201 None
8202 };
8203
8204 self.commit_spill_and_mirror(
8206 anchor_vertex,
8207 target_cells,
8208 values.clone(),
8209 delta,
8210 None, )?;
8212
8213 if let Some(log) = log {
8215 log.record(ChangeEvent::SpillCommitted {
8216 anchor: anchor_vertex,
8217 old: old_snapshot,
8218 new: SpillSnapshot {
8219 target_cells: target_cells.to_vec(),
8220 values,
8221 },
8222 });
8223 }
8224 Ok(())
8225 }
8226
8227 fn snapshot_spill_for_anchor(&self, anchor: VertexId) -> Option<SpillSnapshot> {
8232 let cells = self.graph.spill_cells_for_anchor(anchor)?.to_vec();
8233 if cells.is_empty() {
8234 return None;
8235 }
8236
8237 let max = self.config.spill.max_spill_cells as usize;
8238 let mut cells = cells;
8239 if cells.len() > max {
8240 cells.truncate(max);
8241 }
8242
8243 let first = *cells.first().expect("non-empty spill cells");
8244 let sheet_name = self.graph.sheet_name(first.sheet_id).to_string();
8245 let row0 = first.coord.row();
8246 let col0 = first.coord.col();
8247
8248 let mut max_row = row0;
8249 let mut max_col = col0;
8250 let mut by_coord: FxHashMap<(u32, u32), LiteralValue> = FxHashMap::default();
8251 for cell in &cells {
8252 max_row = max_row.max(cell.coord.row());
8253 max_col = max_col.max(cell.coord.col());
8254 let v = self
8255 .get_cell_value(&sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
8256 .unwrap_or(LiteralValue::Empty);
8257 by_coord.insert((cell.coord.row(), cell.coord.col()), v);
8258 }
8259
8260 let rows = (max_row - row0 + 1) as usize;
8261 let cols = (max_col - col0 + 1) as usize;
8262 let mut values: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows);
8263 for r in 0..rows {
8264 let mut row: Vec<LiteralValue> = Vec::with_capacity(cols);
8265 for c in 0..cols {
8266 row.push(
8267 by_coord
8268 .get(&(row0 + r as u32, col0 + c as u32))
8269 .cloned()
8270 .unwrap_or(LiteralValue::Empty),
8271 );
8272 }
8273 values.push(row);
8274 }
8275
8276 Some(SpillSnapshot {
8277 target_cells: cells,
8278 values,
8279 })
8280 }
8281
8282 fn evaluate_layer_sequential_effects(
8286 &mut self,
8287 layer: &super::scheduler::Layer,
8288 ) -> Result<usize, ExcelError> {
8289 for &vertex_id in &layer.vertices {
8290 let value = match self.evaluate_vertex_immutable(vertex_id) {
8291 Ok(v) => v,
8292 Err(e) => LiteralValue::Error(e),
8293 };
8294 let effects = self.plan_vertex_effects(vertex_id, value, None)?;
8295 for effect in &effects {
8296 self.apply_effect(effect, None, None)?;
8297 }
8298 }
8299 Ok(layer.vertices.len())
8300 }
8301
8302 fn evaluate_layer_sequential_with_delta_effects(
8304 &mut self,
8305 layer: &super::scheduler::Layer,
8306 delta: &mut DeltaCollector,
8307 ) -> Result<usize, ExcelError> {
8308 for &vertex_id in &layer.vertices {
8309 let value = match self.evaluate_vertex_immutable(vertex_id) {
8310 Ok(v) => v,
8311 Err(e) => LiteralValue::Error(e),
8312 };
8313 let effects = self.plan_vertex_effects(vertex_id, value, None)?;
8314 for effect in &effects {
8315 self.apply_effect(effect, Some(delta), None)?;
8316 }
8317 }
8318 Ok(layer.vertices.len())
8319 }
8320
8321 fn evaluate_layer_sequential_cancellable_effects(
8323 &mut self,
8324 layer: &super::scheduler::Layer,
8325 cancel_flag: &AtomicBool,
8326 ) -> Result<usize, ExcelError> {
8327 for (i, &vertex_id) in layer.vertices.iter().enumerate() {
8328 if i % 256 == 0 && cancel_flag.load(Ordering::Relaxed) {
8329 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
8330 .with_message("Evaluation cancelled within layer".to_string()));
8331 }
8332 let value = match self.evaluate_vertex_immutable(vertex_id) {
8333 Ok(v) => v,
8334 Err(e) => LiteralValue::Error(e),
8335 };
8336 let effects = self.plan_vertex_effects(vertex_id, value, None)?;
8337 for effect in &effects {
8338 self.apply_effect(effect, None, None)?;
8339 }
8340 }
8341 Ok(layer.vertices.len())
8342 }
8343
8344 fn evaluate_layer_sequential_cancellable_demand_driven_effects(
8346 &mut self,
8347 layer: &super::scheduler::Layer,
8348 cancel_flag: &AtomicBool,
8349 ) -> Result<usize, ExcelError> {
8350 for (i, &vertex_id) in layer.vertices.iter().enumerate() {
8351 if i % 128 == 0 && cancel_flag.load(Ordering::Relaxed) {
8352 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
8353 .with_message("Demand-driven evaluation cancelled within layer".to_string()));
8354 }
8355 let value = match self.evaluate_vertex_immutable(vertex_id) {
8356 Ok(v) => v,
8357 Err(e) => LiteralValue::Error(e),
8358 };
8359 let effects = self.plan_vertex_effects(vertex_id, value, None)?;
8360 for effect in &effects {
8361 self.apply_effect(effect, None, None)?;
8362 }
8363 }
8364 Ok(layer.vertices.len())
8365 }
8366
8367 fn evaluate_layer_parallel_effects(
8369 &mut self,
8370 layer: &super::scheduler::Layer,
8371 ) -> Result<usize, ExcelError> {
8372 use rayon::prelude::*;
8373
8374 let thread_pool = self.thread_pool.as_ref().unwrap().clone();
8375
8376 let mut phase1: Vec<VertexId> = Vec::new();
8377 let mut phase2: Vec<VertexId> = Vec::new();
8378 for &vid in &layer.vertices {
8379 if self.graph.get_range_dependencies(vid).is_some() {
8380 phase2.push(vid);
8381 } else {
8382 phase1.push(vid);
8383 }
8384 }
8385
8386 let inflight: rustc_hash::FxHashSet<VertexId> = layer.vertices.iter().copied().collect();
8387 let mut applied = 0usize;
8388
8389 for group in [&phase1[..], &phase2[..]] {
8390 if group.is_empty() {
8391 continue;
8392 }
8393
8394 let results: Result<Vec<(VertexId, LiteralValue)>, ExcelError> =
8395 thread_pool.install(|| {
8396 group
8397 .par_iter()
8398 .map(
8399 |&vertex_id| match self.evaluate_vertex_immutable(vertex_id) {
8400 Ok(v) => Ok((vertex_id, v)),
8401 Err(e) => Ok((vertex_id, LiteralValue::Error(e))),
8402 },
8403 )
8404 .collect()
8405 });
8406
8407 match results {
8408 Ok(vertex_results) => {
8409 let mut arrays: Vec<(VertexId, LiteralValue)> = Vec::new();
8412 let mut others: Vec<(VertexId, LiteralValue)> = Vec::new();
8413 for (vertex_id, result) in vertex_results {
8414 if matches!(result, LiteralValue::Array(_)) {
8415 arrays.push((vertex_id, result));
8416 } else {
8417 others.push((vertex_id, result));
8418 }
8419 }
8420 for (vertex_id, result) in arrays {
8421 let effects =
8422 self.plan_vertex_effects(vertex_id, result, Some(&inflight))?;
8423 for effect in &effects {
8424 self.apply_effect(effect, None, None)?;
8425 }
8426 applied = applied.saturating_add(1);
8427 }
8428 for (vertex_id, result) in others {
8429 let effects =
8430 self.plan_vertex_effects(vertex_id, result, Some(&inflight))?;
8431 for effect in &effects {
8432 self.apply_effect(effect, None, None)?;
8433 }
8434 applied = applied.saturating_add(1);
8435 }
8436 }
8437 Err(e) => return Err(e),
8438 }
8439 }
8440
8441 Ok(applied)
8442 }
8443
8444 fn evaluate_layer_parallel_with_delta_effects(
8446 &mut self,
8447 layer: &super::scheduler::Layer,
8448 delta: &mut DeltaCollector,
8449 ) -> Result<usize, ExcelError> {
8450 use rayon::prelude::*;
8451
8452 let thread_pool = self.thread_pool.as_ref().unwrap().clone();
8453
8454 let mut phase1: Vec<VertexId> = Vec::new();
8455 let mut phase2: Vec<VertexId> = Vec::new();
8456 for &vid in &layer.vertices {
8457 if self.graph.get_range_dependencies(vid).is_some() {
8458 phase2.push(vid);
8459 } else {
8460 phase1.push(vid);
8461 }
8462 }
8463
8464 let inflight: rustc_hash::FxHashSet<VertexId> = layer.vertices.iter().copied().collect();
8465 let mut applied = 0usize;
8466
8467 for group in [&phase1[..], &phase2[..]] {
8468 if group.is_empty() {
8469 continue;
8470 }
8471 let results: Result<Vec<(VertexId, LiteralValue)>, ExcelError> =
8472 thread_pool.install(|| {
8473 group
8474 .par_iter()
8475 .map(
8476 |&vertex_id| match self.evaluate_vertex_immutable(vertex_id) {
8477 Ok(v) => Ok((vertex_id, v)),
8478 Err(e) => Ok((vertex_id, LiteralValue::Error(e))),
8479 },
8480 )
8481 .collect()
8482 });
8483
8484 match results {
8485 Ok(vertex_results) => {
8486 let mut arrays: Vec<(VertexId, LiteralValue)> = Vec::new();
8487 let mut others: Vec<(VertexId, LiteralValue)> = Vec::new();
8488 for (vertex_id, result) in vertex_results {
8489 if matches!(result, LiteralValue::Array(_)) {
8490 arrays.push((vertex_id, result));
8491 } else {
8492 others.push((vertex_id, result));
8493 }
8494 }
8495 for (vertex_id, result) in arrays {
8496 let effects =
8497 self.plan_vertex_effects(vertex_id, result, Some(&inflight))?;
8498 for effect in &effects {
8499 self.apply_effect(effect, Some(delta), None)?;
8500 }
8501 applied = applied.saturating_add(1);
8502 }
8503 for (vertex_id, result) in others {
8504 let effects =
8505 self.plan_vertex_effects(vertex_id, result, Some(&inflight))?;
8506 for effect in &effects {
8507 self.apply_effect(effect, Some(delta), None)?;
8508 }
8509 applied = applied.saturating_add(1);
8510 }
8511 }
8512 Err(e) => return Err(e),
8513 }
8514 }
8515
8516 Ok(applied)
8517 }
8518
8519 fn evaluate_layer_parallel_cancellable_effects(
8521 &mut self,
8522 layer: &super::scheduler::Layer,
8523 cancel_flag: &AtomicBool,
8524 ) -> Result<usize, ExcelError> {
8525 use rayon::prelude::*;
8526
8527 let thread_pool = self.thread_pool.as_ref().unwrap().clone();
8528
8529 if cancel_flag.load(Ordering::Relaxed) {
8530 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
8531 .with_message("Parallel evaluation cancelled before starting".to_string()));
8532 }
8533
8534 let mut phase1: Vec<VertexId> = Vec::new();
8535 let mut phase2: Vec<VertexId> = Vec::new();
8536 for &vid in &layer.vertices {
8537 if self.graph.get_range_dependencies(vid).is_some() {
8538 phase2.push(vid);
8539 } else {
8540 phase1.push(vid);
8541 }
8542 }
8543
8544 let inflight: rustc_hash::FxHashSet<VertexId> = layer.vertices.iter().copied().collect();
8545 let mut applied = 0usize;
8546
8547 for group in [&phase1[..], &phase2[..]] {
8548 if group.is_empty() {
8549 continue;
8550 }
8551
8552 let results: Result<Vec<(VertexId, LiteralValue)>, ExcelError> =
8553 thread_pool.install(|| {
8554 group
8555 .par_iter()
8556 .map(|&vertex_id| {
8557 if cancel_flag.load(Ordering::Relaxed) {
8558 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
8559 .with_message(
8560 "Parallel evaluation cancelled during execution"
8561 .to_string(),
8562 ));
8563 }
8564 match self.evaluate_vertex_immutable(vertex_id) {
8565 Ok(v) => Ok((vertex_id, v)),
8566 Err(e) => Ok((vertex_id, LiteralValue::Error(e))),
8567 }
8568 })
8569 .collect()
8570 });
8571
8572 match results {
8573 Ok(vertex_results) => {
8574 let mut arrays: Vec<(VertexId, LiteralValue)> = Vec::new();
8575 let mut others: Vec<(VertexId, LiteralValue)> = Vec::new();
8576 for (vertex_id, result) in vertex_results {
8577 if matches!(result, LiteralValue::Array(_)) {
8578 arrays.push((vertex_id, result));
8579 } else {
8580 others.push((vertex_id, result));
8581 }
8582 }
8583 for (vertex_id, result) in arrays {
8584 let effects =
8585 self.plan_vertex_effects(vertex_id, result, Some(&inflight))?;
8586 for effect in &effects {
8587 self.apply_effect(effect, None, None)?;
8588 }
8589 applied = applied.saturating_add(1);
8590 }
8591 for (vertex_id, result) in others {
8592 let effects =
8593 self.plan_vertex_effects(vertex_id, result, Some(&inflight))?;
8594 for effect in &effects {
8595 self.apply_effect(effect, None, None)?;
8596 }
8597 applied = applied.saturating_add(1);
8598 }
8599 }
8600 Err(e) => return Err(e),
8601 }
8602 }
8603
8604 Ok(applied)
8605 }
8606
8607 pub fn evaluate_all_logged(&mut self, log: &mut ChangeLog) -> Result<EvalResult, ExcelError> {
8614 let _source_cache = self.source_cache_session();
8615 self.validate_deterministic_mode()?;
8616 if self.config.defer_graph_building {
8617 self.build_graph_all()?;
8618 }
8619 self.reset_virtual_dep_telemetry_if_disabled();
8620 let start = crate::instant::FzInstant::now();
8621 let mut computed_vertices = 0;
8622 let mut cycle_errors = 0;
8623
8624 let mut replan_iterations = 0;
8625 const MAX_REPLAN: usize = 5;
8626 let mut telemetry = self
8627 .config
8628 .enable_virtual_dep_telemetry
8629 .then(|| self.start_virtual_dep_telemetry());
8630
8631 log.begin_compound(format!("evaluate_all(epoch={})", self.recalc_epoch));
8632
8633 loop {
8634 let to_evaluate = self.graph.get_evaluation_vertices();
8635 if to_evaluate.is_empty() {
8636 if let Some(t) = telemetry.as_mut()
8637 && t.bailout_reason.is_none()
8638 {
8639 t.bailout_reason = Some("no_work");
8640 }
8641 break;
8642 }
8643
8644 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
8645 if let Some(t) = telemetry.as_mut() {
8646 Self::accumulate_schedule_meta(t, &meta);
8647 }
8648
8649 let circ_error = LiteralValue::Error(
8651 ExcelError::new(ExcelErrorKind::Circ)
8652 .with_message("Circular dependency detected".to_string()),
8653 );
8654 for cycle in &schedule.cycles {
8655 cycle_errors += 1;
8656 for &vertex_id in cycle {
8657 self.graph
8658 .update_vertex_value(vertex_id, circ_error.clone());
8659 self.mirror_vertex_value_to_overlay(vertex_id, &circ_error);
8660 }
8661 }
8662
8663 for layer in &schedule.layers {
8665 computed_vertices += self.evaluate_layer_logged(layer, log)?;
8666 }
8667
8668 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
8669 if let Some(t) = telemetry.as_mut() {
8670 t.changed_vdeps_total += changed_vertices.len();
8671 }
8672 self.graph.clear_dirty_flags(&to_evaluate);
8673 for v in &changed_vertices {
8674 self.graph.set_dirty(*v, true);
8675 }
8676
8677 if changed_vertices.is_empty() {
8678 if let Some(t) = telemetry.as_mut() {
8679 t.bailout_reason = Some("converged");
8680 }
8681 break;
8682 }
8683 if replan_iterations >= MAX_REPLAN {
8684 if let Some(t) = telemetry.as_mut() {
8685 t.bailout_reason = Some("max_replan");
8686 }
8687 break;
8688 }
8689 replan_iterations += 1;
8690 }
8691
8692 if let Some(mut t) = telemetry {
8693 t.replan_iterations = replan_iterations;
8694 self.last_virtual_dep_telemetry = t;
8695 }
8696
8697 log.end_compound();
8698
8699 self.graph.redirty_volatiles();
8700 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
8701
8702 Ok(EvalResult {
8703 computed_vertices,
8704 cycle_errors,
8705 elapsed: start.elapsed(),
8706 })
8707 }
8708
8709 fn evaluate_layer_logged(
8711 &mut self,
8712 layer: &super::scheduler::Layer,
8713 log: &mut ChangeLog,
8714 ) -> Result<usize, ExcelError> {
8715 for &vertex_id in &layer.vertices {
8716 let value = match self.evaluate_vertex_immutable(vertex_id) {
8717 Ok(v) => v,
8718 Err(e) => LiteralValue::Error(e),
8719 };
8720 let effects = self.plan_vertex_effects(vertex_id, value, None)?;
8721 for effect in &effects {
8722 self.apply_effect(effect, None, Some(log))?;
8723 }
8724 }
8725 Ok(layer.vertices.len())
8726 }
8727}