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