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