1use crate::SheetId;
2use crate::arrow_store::{OverlayFragment, OverlayValue, SheetStore};
3use crate::engine::arena::AstNodeId;
4use crate::engine::eval_delta::{DeltaCollector, DeltaMode, EvalDelta};
5use crate::engine::ingest_pipeline::{DependencyPlanRow, FormulaAstInput};
6use crate::engine::lookup_index_cache::{
7 BuildOutcome, LookupAxis, LookupIndex, LookupIndexCache, LookupIndexCacheReport,
8 LookupIndexKey, estimate_bytes,
9};
10use crate::engine::named_range::{NameScope, NamedDefinition};
11use crate::engine::range_view::RangeView;
12use crate::engine::row_visibility::RowVisibilityState;
13use crate::engine::spill::{RegionLockManager, SpillMeta, SpillShape};
14use crate::engine::virtual_deps::VirtualDepBuilder;
15use crate::engine::{
16 DependencyGraph, EvalConfig, FormulaIngestBatch, FormulaIngestRecord, FormulaIngestReport,
17 FormulaParseDiagnostic, FormulaParsePolicy, FormulaPlaneMode, RowVisibilitySource, Scheduler,
18 VertexId, VertexKind, VisibilityMaskMode,
19};
20use crate::formula_plane::placement::{
21 CandidateAnalysis, FormulaPlacementCandidate, FormulaPlacementResult, PlacementFallbackReason,
22 place_candidate_family_with_analyses, split_candidate_affine_literal_runs,
23};
24use crate::formula_plane::producer::{
25 DirtyProjectionRule, FormulaConsumerReadIndex, FormulaProducerId, FormulaProducerResultIndex,
26 FormulaProducerWork, ProducerDirtyDomain, SpanReadSummary,
27};
28use crate::formula_plane::region_index::{DirtyDomain, Region};
29use crate::formula_plane::runtime::{
30 FormulaPlane, FormulaSpanRef, PlacementCoord, PlacementDomain, ResultRegion,
31};
32use crate::formula_plane::scheduler::{MixedSchedule, build_mixed_schedule};
33#[cfg(test)]
34use crate::formula_plane::span_eval::SpanEvalReport;
35use crate::formula_plane::span_eval::{SpanComputedWriteSink, SpanEvalTask, SpanEvaluator};
36use crate::formula_plane::structural::relocate_ast_for_template_placement;
37use crate::formula_plane::structural_shift::{SpanShiftPlan, StructuralOp, classify_span_for_op};
38use crate::interpreter::Interpreter;
39use crate::reference::{CellRef, Coord, RangeRef};
40use crate::traits::FunctionProvider;
41use crate::traits::{EvaluationContext, ReferenceInfo, Resolver};
42use chrono::Timelike;
43use formualizer_common::{
44 CoordBuildHasher, LiteralValue, col_letters_from_1based, parse_a1_1based,
45};
46use formualizer_parse::parser::ReferenceType;
47use formualizer_parse::{ASTNode, ASTNodeType, ExcelError, ExcelErrorKind};
48use rayon::ThreadPoolBuilder;
49use rustc_hash::{FxHashMap, FxHashSet};
50use std::collections::{BTreeMap, BTreeSet, VecDeque};
51use std::sync::Arc;
52use std::sync::atomic::{AtomicBool, Ordering};
53
54type StagedFormulaEntry = (u32, u32, String);
55type StagedFormulaMap = std::collections::HashMap<String, Vec<StagedFormulaEntry>>;
56
57fn producer_dirty_to_span_dirty(
58 dirty: ProducerDirtyDomain,
59 span_ref: FormulaSpanRef,
60) -> DirtyDomain {
61 match dirty {
62 ProducerDirtyDomain::Whole => DirtyDomain::WholeSpan(span_ref),
63 ProducerDirtyDomain::Cells(cells) => DirtyDomain::Cells(cells),
64 ProducerDirtyDomain::Regions(regions) => DirtyDomain::Regions(regions),
65 }
66}
67type PreparedFormulaBatches = Vec<FormulaIngestBatch>;
68type StagedFormulaBatches = Vec<(String, Vec<StagedFormulaEntry>)>;
69type FormulaPlaneMixedScheduleBuild = (
70 MixedSchedule,
71 BTreeMap<crate::formula_plane::runtime::FormulaSpanId, FormulaSpanRef>,
72 u64,
73 Vec<VertexId>,
74);
75
76type PlannedFormulaMaterialize = BTreeMap<String, Vec<(u32, u32, AstNodeId, DependencyPlanRow)>>;
77
78const COMPUTED_WRITE_COALESCING_MIN_LAYER_WIDTH: usize = 8;
82
83#[derive(Debug, Clone, PartialEq)]
84pub(crate) enum ComputedWrite {
85 Cell {
86 seq: u64,
87 sheet_id: SheetId,
88 row0: u32,
89 col0: u32,
90 value: OverlayValue,
91 },
92 Rect {
93 seq: u64,
94 sheet_id: SheetId,
95 sr0: u32,
96 sc0: u32,
97 values: Vec<Vec<OverlayValue>>,
98 },
99}
100
101impl ComputedWrite {
102 #[inline]
103 pub(crate) fn seq(&self) -> u64 {
104 match self {
105 ComputedWrite::Cell { seq, .. } | ComputedWrite::Rect { seq, .. } => *seq,
106 }
107 }
108}
109
110#[derive(Debug, Default)]
111pub(crate) struct ComputedWriteBuffer {
112 writes: Vec<ComputedWrite>,
113 next_seq: u64,
114 estimated_bytes: usize,
115}
116
117impl ComputedWriteBuffer {
118 const ENTRY_BASE_BYTES: usize = 32;
119
120 #[inline]
121 pub(crate) fn is_empty(&self) -> bool {
122 self.writes.is_empty()
123 }
124
125 #[inline]
126 pub(crate) fn len(&self) -> usize {
127 self.writes.len()
128 }
129
130 #[inline]
131 pub(crate) fn estimated_bytes(&self) -> usize {
132 self.estimated_bytes
133 }
134
135 #[inline]
136 pub(crate) fn writes(&self) -> &[ComputedWrite] {
137 &self.writes
138 }
139
140 pub(crate) fn push_cell(
141 &mut self,
142 sheet_id: SheetId,
143 row0: u32,
144 col0: u32,
145 value: OverlayValue,
146 ) {
147 let seq = self.next_sequence();
148 self.estimated_bytes = self
149 .estimated_bytes
150 .saturating_add(Self::estimate_value_bytes(&value));
151 self.writes.push(ComputedWrite::Cell {
152 seq,
153 sheet_id,
154 row0,
155 col0,
156 value,
157 });
158 }
159
160 pub(crate) fn push_rect(
161 &mut self,
162 sheet_id: SheetId,
163 sr0: u32,
164 sc0: u32,
165 values: Vec<Vec<OverlayValue>>,
166 ) {
167 let seq = self.next_sequence();
168 let added = values
169 .iter()
170 .flat_map(|row| row.iter())
171 .map(Self::estimate_value_bytes)
172 .fold(0usize, usize::saturating_add);
173 self.estimated_bytes = self.estimated_bytes.saturating_add(added);
174 self.writes.push(ComputedWrite::Rect {
175 seq,
176 sheet_id,
177 sr0,
178 sc0,
179 values,
180 });
181 }
182
183 pub(crate) fn clear(&mut self) {
184 self.writes.clear();
185 self.estimated_bytes = 0;
186 }
187
188 fn take_writes(&mut self) -> Vec<ComputedWrite> {
189 self.estimated_bytes = 0;
190 std::mem::take(&mut self.writes)
191 }
192
193 fn next_sequence(&mut self) -> u64 {
194 let seq = self.next_seq;
195 self.next_seq = self.next_seq.wrapping_add(1);
196 seq
197 }
198
199 #[inline]
200 fn estimate_value_bytes(value: &OverlayValue) -> usize {
201 Self::ENTRY_BASE_BYTES.saturating_add(value.estimated_payload_bytes())
202 }
203}
204
205#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
206struct ComputedWriteChunkKey {
207 sheet_id: SheetId,
208 col0: u32,
209 chunk_idx: usize,
210 chunk_start_row0: u32,
211}
212
213#[derive(Debug, Clone, PartialEq)]
214pub(crate) struct ComputedWriteChunkEntryPlan {
215 pub(crate) row_in_chunk: usize,
216 pub(crate) seq: u64,
217 pub(crate) value: OverlayValue,
218}
219
220#[derive(Debug, Clone, PartialEq, Eq)]
221pub(crate) enum ComputedWriteChunkPlanShape {
222 Point,
223 SparseOffsets {
224 entries: usize,
225 span_len: usize,
226 },
227 DenseRange {
228 start: usize,
229 len: usize,
230 },
231 RunRange {
232 start: usize,
233 len: usize,
234 runs: usize,
235 },
236}
237
238#[derive(Debug, Clone, PartialEq)]
239pub(crate) struct ComputedWriteChunkPlan {
240 pub(crate) sheet_id: SheetId,
241 pub(crate) col0: u32,
242 pub(crate) chunk_idx: usize,
243 pub(crate) chunk_start_row0: u32,
244 pub(crate) entries: Vec<ComputedWriteChunkEntryPlan>,
245 pub(crate) shape: ComputedWriteChunkPlanShape,
246}
247
248#[derive(Debug, Clone, Default, PartialEq)]
249pub(crate) struct ComputedWriteCoalescingPlan {
250 pub(crate) chunks: Vec<ComputedWriteChunkPlan>,
251 pub(crate) input_cells: usize,
252 pub(crate) coalesced_cells: usize,
253 pub(crate) overwritten_cells: usize,
254}
255
256impl ComputedWriteCoalescingPlan {
257 #[inline]
258 pub(crate) fn is_empty(&self) -> bool {
259 self.chunks.is_empty()
260 }
261}
262
263impl ComputedWriteChunkPlan {
264 fn from_group(
265 key: ComputedWriteChunkKey,
266 mut entries: Vec<ComputedWriteChunkEntryPlan>,
267 ) -> (Self, usize) {
268 entries.sort_by_key(|entry| (entry.row_in_chunk, entry.seq));
269 let input_len = entries.len();
270 let mut coalesced: Vec<ComputedWriteChunkEntryPlan> = Vec::with_capacity(input_len);
271 for entry in entries {
272 if let Some(prev) = coalesced.last_mut()
273 && prev.row_in_chunk == entry.row_in_chunk
274 {
275 *prev = entry;
276 continue;
277 }
278 coalesced.push(entry);
279 }
280 let overwritten = input_len.saturating_sub(coalesced.len());
281 let shape = Self::classify_shape(&coalesced);
282 (
283 Self {
284 sheet_id: key.sheet_id,
285 col0: key.col0,
286 chunk_idx: key.chunk_idx,
287 chunk_start_row0: key.chunk_start_row0,
288 entries: coalesced,
289 shape,
290 },
291 overwritten,
292 )
293 }
294
295 fn classify_shape(entries: &[ComputedWriteChunkEntryPlan]) -> ComputedWriteChunkPlanShape {
296 debug_assert!(!entries.is_empty());
297 if entries.len() == 1 {
298 return ComputedWriteChunkPlanShape::Point;
299 }
300
301 let start = entries[0].row_in_chunk;
302 let end = entries[entries.len() - 1].row_in_chunk;
303 let span_len = end.saturating_sub(start).saturating_add(1);
304 if span_len != entries.len() {
305 return ComputedWriteChunkPlanShape::SparseOffsets {
306 entries: entries.len(),
307 span_len,
308 };
309 }
310
311 let runs = Self::run_count(entries);
312 if runs < entries.len() {
313 ComputedWriteChunkPlanShape::RunRange {
314 start,
315 len: entries.len(),
316 runs,
317 }
318 } else {
319 ComputedWriteChunkPlanShape::DenseRange {
320 start,
321 len: entries.len(),
322 }
323 }
324 }
325
326 fn run_count(entries: &[ComputedWriteChunkEntryPlan]) -> usize {
327 let mut runs = 0usize;
328 let mut prev: Option<&OverlayValue> = None;
329 for entry in entries {
330 if prev != Some(&entry.value) {
331 runs = runs.saturating_add(1);
332 prev = Some(&entry.value);
333 }
334 }
335 runs
336 }
337}
338
339pub struct Engine<R> {
340 pub(crate) graph: DependencyGraph,
341 resolver: R,
342 pub config: EvalConfig,
343 workbook_load_limits: crate::engine::WorkbookLoadLimits,
344 clock: Arc<dyn crate::timezone::ClockProvider>,
345 thread_pool: Option<Arc<rayon::ThreadPool>>,
346 pub recalc_epoch: u64,
347 snapshot_id: std::sync::atomic::AtomicU64,
348 topology_epoch: u64,
349 cached_static_schedule: Option<CachedScheduleEntry>,
350 spill_mgr: ShimSpillManager,
351 arrow_sheets: SheetStore,
353 has_edited: bool,
355 overlay_compactions: u64,
357
358 computed_overlay_bytes_estimate: usize,
360 computed_overlay_mirroring_disabled: bool,
361 pub(crate) force_materialize_range_views: bool,
364 row_bounds_cache: std::sync::RwLock<Option<RowBoundsCache>>,
366 used_axis_bounds_cache: std::sync::RwLock<Option<UsedAxisBoundsCache>>,
368 lookup_index_cache: LookupIndexCache,
369 source_cache: Arc<std::sync::RwLock<SourceCache>>,
370 staged_formulas: StagedFormulaMap,
372 row_visibility: FxHashMap<SheetId, RowVisibilityState>,
374 row_visibility_mask_cache: std::sync::RwLock<
376 FxHashMap<VisibilityMaskCacheKey, std::sync::Arc<arrow_array::BooleanArray>>,
377 >,
378 formula_parse_diagnostics: Vec<FormulaParseDiagnostic>,
380 last_formula_ingest_report: Option<FormulaIngestReport>,
382 formula_ingest_report_total: FormulaIngestReport,
384 active_cancel_flag: Option<Arc<AtomicBool>>,
386
387 action_depth: u32,
392
393 last_virtual_dep_telemetry: VirtualDepTelemetry,
395 virtual_dep_fallback_activations: u64,
396
397 formula_plane_indexes_epoch_seen: u64,
402
403 #[cfg(test)]
404 last_formula_plane_span_eval_report: Option<SpanEvalReport>,
405}
406
407impl<R: EvaluationContext> Engine<R> {
412 pub(crate) fn ingest_pipeline(&mut self) -> crate::engine::ingest_pipeline::IngestPipeline<'_> {
413 self.graph.ingest_pipeline(&self.resolver)
414 }
415}
416
417pub struct EngineAction<'a, R>
418where
419 R: EvaluationContext,
420{
421 engine: &'a mut Engine<R>,
422 name: String,
423 log: Option<*mut crate::engine::ChangeLog>,
426 arrow_undo: Option<*mut crate::engine::ArrowUndoBatch>,
429 atomic_policy: bool,
431}
432
433impl<'a, R> EngineAction<'a, R>
434where
435 R: EvaluationContext,
436{
437 #[inline]
438 fn addr_for(&mut self, sheet: &str, row: u32, col: u32) -> crate::reference::CellRef {
439 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
440 let coord = crate::reference::Coord::from_excel(row, col, true, true);
441 crate::reference::CellRef::new(sheet_id, coord)
442 }
443
444 #[inline]
445 pub fn name(&self) -> &str {
446 &self.name
447 }
448
449 #[inline]
450 pub fn set_cell_value(
451 &mut self,
452 sheet: &str,
453 row: u32,
454 col: u32,
455 value: LiteralValue,
456 ) -> Result<(), crate::engine::EditorError> {
457 if self.log.is_some() {
458 let old_value = self.engine.read_cell_value(sheet, row, col);
459 let mut old_formula = self.engine.read_cell_formula_ast(sheet, row, col);
460 let addr = self.addr_for(sheet, row, col);
461 let Some(log_ptr) = self.log else {
462 return Err(crate::engine::EditorError::TransactionFailed {
463 reason: "action_with_logger: missing ChangeLog".to_string(),
464 });
465 };
466
467 let old_comp = if self.arrow_undo.is_some() {
470 self.engine.read_computed_overlay_cell(sheet, row, col)
471 } else {
472 None
473 };
474
475 self.engine.demote_span_containing_cell_for_write(
476 addr.sheet_id,
477 addr.coord.row(),
478 addr.coord.col(),
479 )?;
480 if old_formula.is_none() {
481 old_formula = self.engine.read_cell_formula_ast(sheet, row, col);
482 }
483
484 let delta_old_sem = if old_formula.is_some() {
485 None
486 } else {
487 Some(old_value.clone().unwrap_or(LiteralValue::Empty))
488 };
489
490 let start_len = unsafe { (&*log_ptr).len() };
491
492 let log = unsafe { &mut *log_ptr };
494 self.engine.edit_with_logger(log, |editor| {
495 editor.set_cell_value(addr, value.clone());
496 });
497 log.patch_last_cell_event_old_state(addr, old_value.clone(), old_formula.clone());
498 self.engine
499 .record_formula_plane_structural_change(StructuralScope::Cell {
500 sheet: addr.sheet_id,
501 row: addr.coord.row(),
502 col: addr.coord.col(),
503 });
504
505 if let Some(undo_ptr) = self.arrow_undo {
506 let new_events = &unsafe { (&*log_ptr).events() }[start_len..];
508 let undo = unsafe { &mut *undo_ptr };
509 self.engine
510 .record_spill_ops_into_arrow_undo(undo, new_events);
511
512 let new_comp = self.engine.read_computed_overlay_cell(sheet, row, col);
514 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
515 let row0 = row.saturating_sub(1);
516 let col0 = col.saturating_sub(1);
517 let delta_new_sem = Some(value.clone());
518 undo.record_delta_cell(sheet_id, row0, col0, delta_old_sem, delta_new_sem);
519 undo.record_computed_cell(sheet_id, row0, col0, old_comp, new_comp);
520 }
521 Ok(())
522 } else {
523 self.engine
524 .set_cell_value(sheet, row, col, value)
525 .map_err(crate::engine::EditorError::from)
526 }
527 }
528
529 #[inline]
530 pub fn set_cell_formula(
531 &mut self,
532 sheet: &str,
533 row: u32,
534 col: u32,
535 ast: ASTNode,
536 ) -> Result<(), crate::engine::EditorError> {
537 if self.log.is_some() {
538 let old_value = self.engine.read_cell_value(sheet, row, col);
539 let mut old_formula = self.engine.read_cell_formula_ast(sheet, row, col);
540 let addr = self.addr_for(sheet, row, col);
541 let Some(log_ptr) = self.log else {
542 return Err(crate::engine::EditorError::TransactionFailed {
543 reason: "action_with_logger: missing ChangeLog".to_string(),
544 });
545 };
546
547 self.engine.demote_span_containing_cell_for_write(
548 addr.sheet_id,
549 addr.coord.row(),
550 addr.coord.col(),
551 )?;
552 if old_formula.is_none() {
553 old_formula = self.engine.read_cell_formula_ast(sheet, row, col);
554 }
555 let delta_old = if self.arrow_undo.is_some() {
556 if old_formula.is_some() {
557 None
558 } else {
559 Some(old_value.clone().unwrap_or(LiteralValue::Empty))
560 }
561 } else {
562 None
563 };
564 let start_len = unsafe { (&*log_ptr).len() };
565
566 let log = unsafe { &mut *log_ptr };
568 self.engine.edit_with_logger(log, |editor| {
569 editor.set_cell_formula(addr, ast.clone());
570 });
571 log.patch_last_cell_event_old_state(addr, old_value, old_formula);
572 self.engine
573 .record_formula_plane_structural_change(StructuralScope::Cell {
574 sheet: addr.sheet_id,
575 row: addr.coord.row(),
576 col: addr.coord.col(),
577 });
578
579 if let Some(undo_ptr) = self.arrow_undo {
580 let new_events = &unsafe { (&*log_ptr).events() }[start_len..];
581 let undo = unsafe { &mut *undo_ptr };
582 self.engine
583 .record_spill_ops_into_arrow_undo(undo, new_events);
584 let delta_new: Option<LiteralValue> = None;
585 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
586 let row0 = row.saturating_sub(1);
587 let col0 = col.saturating_sub(1);
588 undo.record_delta_cell(sheet_id, row0, col0, delta_old, delta_new);
589 }
590 Ok(())
591 } else {
592 self.engine
593 .set_cell_formula(sheet, row, col, ast)
594 .map_err(crate::engine::EditorError::from)
595 }
596 }
597
598 #[inline]
599 pub fn set_row_hidden(
600 &mut self,
601 sheet: &str,
602 row_1based: u32,
603 hidden: bool,
604 source: RowVisibilitySource,
605 ) -> Result<(), crate::engine::EditorError> {
606 if self.log.is_some() {
607 let sheet_id = self.engine.ensure_known_sheet_id(sheet)?;
608 let row0 = Engine::<R>::normalize_row_1based(row_1based)?;
609 let old_hidden = self
610 .engine
611 .row_visibility
612 .get(&sheet_id)
613 .map(|state| state.is_row_hidden(row0, Some(source)))
614 .unwrap_or(false);
615 if old_hidden == hidden {
616 return Ok(());
617 }
618
619 let _ = self
620 .engine
621 .set_row_hidden_by_sheet_id(sheet_id, row0, hidden, source);
622
623 let Some(log_ptr) = self.log else {
624 return Err(crate::engine::EditorError::TransactionFailed {
625 reason: "action_with_logger: missing ChangeLog".to_string(),
626 });
627 };
628 unsafe { &mut *log_ptr }.record(crate::engine::ChangeEvent::SetRowVisibility {
629 sheet_id,
630 row0,
631 source,
632 old_hidden,
633 new_hidden: hidden,
634 });
635
636 Ok(())
637 } else {
638 self.engine
639 .set_row_hidden(sheet, row_1based, hidden, source)
640 }
641 }
642
643 #[inline]
644 pub fn set_rows_hidden(
645 &mut self,
646 sheet: &str,
647 start_row_1based: u32,
648 end_row_1based: u32,
649 hidden: bool,
650 source: RowVisibilitySource,
651 ) -> Result<(), crate::engine::EditorError> {
652 if self.log.is_some() {
653 let sheet_id = self.engine.ensure_known_sheet_id(sheet)?;
654 let (start_row0, end_row0) =
655 Engine::<R>::normalize_row_range_1based(start_row_1based, end_row_1based)?;
656
657 let Some(log_ptr) = self.log else {
658 return Err(crate::engine::EditorError::TransactionFailed {
659 reason: "action_with_logger: missing ChangeLog".to_string(),
660 });
661 };
662 let log = unsafe { &mut *log_ptr };
663
664 for row0 in start_row0..=end_row0 {
665 let old_hidden = self
666 .engine
667 .row_visibility
668 .get(&sheet_id)
669 .map(|state| state.is_row_hidden(row0, Some(source)))
670 .unwrap_or(false);
671 if old_hidden == hidden {
672 continue;
673 }
674
675 let _ = self
676 .engine
677 .set_row_hidden_by_sheet_id(sheet_id, row0, hidden, source);
678
679 log.record(crate::engine::ChangeEvent::SetRowVisibility {
680 sheet_id,
681 row0,
682 source,
683 old_hidden,
684 new_hidden: hidden,
685 });
686 }
687
688 Ok(())
689 } else {
690 self.engine
691 .set_rows_hidden(sheet, start_row_1based, end_row_1based, hidden, source)
692 }
693 }
694
695 #[inline]
696 pub fn insert_rows(
697 &mut self,
698 sheet: &str,
699 before: u32,
700 count: u32,
701 ) -> Result<crate::engine::ShiftSummary, crate::engine::EditorError> {
702 if self.log.is_some() {
703 let Some(log_ptr) = self.log else {
704 return Err(crate::engine::EditorError::TransactionFailed {
705 reason: "action_atomic: missing ChangeLog".to_string(),
706 });
707 };
708
709 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
710 let before0 = before.saturating_sub(1);
711 let op = StructuralOp::InsertRows {
712 sheet_id,
713 before: before0,
714 count,
715 };
716 self.engine.demote_spans_for_structural_op(
717 op,
718 Engine::<R>::structural_row_region(sheet_id, before0),
719 )?;
720
721 let summary = {
723 let log = unsafe { &mut *log_ptr };
724 let mut out: Result<crate::engine::ShiftSummary, crate::engine::EditorError> =
725 Ok(crate::engine::ShiftSummary::default());
726 self.engine.edit_with_logger(log, |editor| {
727 out = editor.insert_rows(sheet_id, before0, count);
728 });
729 out?
730 };
731
732 self.engine.ensure_arrow_sheet(sheet);
734 if let Some(asheet) = self.engine.arrow_sheets.sheet_mut(sheet) {
735 asheet.insert_rows(before0 as usize, count as usize);
736 }
737 self.engine
738 .shift_row_visibility_insert(sheet_id, before0, count);
739 if let Some(undo_ptr) = self.arrow_undo {
740 unsafe { &mut *undo_ptr }.record_insert_rows(sheet_id, before0, count);
741 }
742 Ok(summary)
743 } else {
744 self.engine.insert_rows(sheet, before, count)
745 }
746 }
747
748 #[inline]
749 pub fn delete_rows(
750 &mut self,
751 sheet: &str,
752 start: u32,
753 count: u32,
754 ) -> Result<crate::engine::ShiftSummary, crate::engine::EditorError> {
755 if self.atomic_policy {
756 return Err(crate::engine::EditorError::TransactionUnsupported {
757 reason:
758 "delete_rows is not supported inside atomic actions (conservative rollback policy)"
759 .to_string(),
760 });
761 }
762 self.engine.delete_rows(sheet, start, count)
763 }
764
765 #[inline]
766 pub fn insert_columns(
767 &mut self,
768 sheet: &str,
769 before: u32,
770 count: u32,
771 ) -> Result<crate::engine::ShiftSummary, crate::engine::EditorError> {
772 if self.log.is_some() {
773 let Some(log_ptr) = self.log else {
774 return Err(crate::engine::EditorError::TransactionFailed {
775 reason: "action_atomic: missing ChangeLog".to_string(),
776 });
777 };
778
779 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
780 let before0 = before.saturating_sub(1);
781 let op = StructuralOp::InsertColumns {
782 sheet_id,
783 before: before0,
784 count,
785 };
786 self.engine.demote_spans_for_structural_op(
787 op,
788 Engine::<R>::structural_col_region(sheet_id, before0),
789 )?;
790
791 let summary = {
792 let log = unsafe { &mut *log_ptr };
793 let mut out: Result<crate::engine::ShiftSummary, crate::engine::EditorError> =
794 Ok(crate::engine::ShiftSummary::default());
795 self.engine.edit_with_logger(log, |editor| {
796 out = editor.insert_columns(sheet_id, before0, count);
797 });
798 out?
799 };
800
801 self.engine.ensure_arrow_sheet(sheet);
802 if let Some(asheet) = self.engine.arrow_sheets.sheet_mut(sheet) {
803 asheet.insert_columns(before0 as usize, count as usize);
804 }
805 if let Some(undo_ptr) = self.arrow_undo {
806 unsafe { &mut *undo_ptr }.record_insert_cols(sheet_id, before0, count);
807 }
808 Ok(summary)
809 } else {
810 self.engine.insert_columns(sheet, before, count)
811 }
812 }
813
814 #[inline]
815 pub fn delete_columns(
816 &mut self,
817 sheet: &str,
818 start: u32,
819 count: u32,
820 ) -> Result<crate::engine::ShiftSummary, crate::engine::EditorError> {
821 if self.atomic_policy {
822 return Err(crate::engine::EditorError::TransactionUnsupported {
823 reason:
824 "delete_columns is not supported inside atomic actions (conservative rollback policy)"
825 .to_string(),
826 });
827 }
828 self.engine.delete_columns(sheet, start, count)
829 }
830
831 #[inline]
836 pub fn action<T>(
837 &mut self,
838 name: impl AsRef<str>,
839 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
840 ) -> Result<T, crate::engine::EditorError> {
841 self.engine.action(name, f)
842 }
843}
844
845struct ActionDepthGuard<'a, R> {
846 engine: *mut Engine<R>,
847 _marker: std::marker::PhantomData<&'a mut Engine<R>>,
848}
849
850impl<'a, R> Drop for ActionDepthGuard<'a, R> {
851 fn drop(&mut self) {
852 unsafe {
855 let e = &mut *self.engine;
856 e.action_depth = e.action_depth.saturating_sub(1);
857 }
858 }
859}
860
861#[derive(Default)]
862struct SourceCache {
863 scalars: FxHashMap<(String, Option<u64>), LiteralValue>,
864 tables: FxHashMap<(String, Option<u64>), Arc<dyn crate::traits::Table>>,
865}
866
867#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
868struct VisibilityMaskCacheKey {
869 sheet_id: SheetId,
870 start_row0: u32,
871 end_row0: u32,
872 mode: VisibilityMaskMode,
873 version: u64,
874}
875
876#[derive(Debug, Clone, Copy, PartialEq, Eq)]
877enum StructuralScope {
878 Cell { sheet: SheetId, row: u32, col: u32 },
879 Region(Region),
880 Sheet(SheetId),
881 RemovedSheet(SheetId),
882 AllSheets,
883}
884
885struct SourceCacheSession {
886 cache: Arc<std::sync::RwLock<SourceCache>>,
887}
888
889impl Drop for SourceCacheSession {
890 fn drop(&mut self) {
891 if let Ok(mut g) = self.cache.write() {
892 *g = SourceCache::default();
893 }
894 }
895}
896
897#[derive(Debug)]
898pub struct EvalResult {
899 pub computed_vertices: usize,
900 pub cycle_errors: usize,
901 pub elapsed: std::time::Duration,
902}
903
904#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
909pub struct EngineBaselineStats {
910 pub graph_vertex_count: usize,
911 pub graph_formula_vertex_count: usize,
912 pub graph_edge_count: usize,
913 pub dirty_vertex_count: usize,
914 pub evaluation_vertex_count: usize,
915 pub formula_ast_root_count: usize,
916 pub formula_ast_node_count: usize,
917 pub staged_formula_count: usize,
918 pub formula_plane_active_span_count: usize,
919 pub formula_plane_producer_result_entries: usize,
920 pub formula_plane_consumer_read_entries: usize,
921}
922
923#[derive(Debug, Clone, Default)]
924pub struct VirtualDepTelemetry {
925 pub candidate_vertices_total: usize,
926 pub vdeps_vertices_total: usize,
927 pub vdeps_edges_total: usize,
928 pub builder_elapsed_ms_total: u128,
929 pub schedule_virtual_passes: usize,
930 pub schedule_static_passes: usize,
931 pub schedule_cache_hits: usize,
932 pub schedule_cache_misses: usize,
933 pub reused_schedule_vertices_total: usize,
934 pub replan_iterations: usize,
935 pub changed_vdeps_total: usize,
936 pub bailout_reason: Option<&'static str>,
937 pub fallback_mode_activations: u64,
938}
939
940#[derive(Debug, Clone, Copy)]
941struct ScheduleBuildMeta {
942 candidate_vertices: usize,
943 vdeps_vertices: usize,
944 vdeps_edges: usize,
945 builder_elapsed_ms: u128,
946 used_virtual_schedule: bool,
947 schedule_cache_hit: bool,
948 schedule_cache_eligible: bool,
949}
950
951#[derive(Debug, Clone)]
952struct CachedScheduleEntry {
953 topology_epoch: u64,
954 candidate_vertices: Vec<VertexId>,
955 schedule: crate::engine::scheduler::Schedule,
956}
957
958type ScheduleBuildOutput = (
959 crate::engine::scheduler::Schedule,
960 FxHashMap<VertexId, Vec<VertexId>>,
961 ScheduleBuildMeta,
962);
963
964#[derive(Debug)]
966pub struct RecalcPlan {
967 schedule: crate::engine::Schedule,
968 has_dynamic_refs: bool,
969}
970
971impl RecalcPlan {
972 pub fn layer_count(&self) -> usize {
973 self.schedule.layers.len()
974 }
975
976 pub fn has_dynamic_refs(&self) -> bool {
977 self.has_dynamic_refs
978 }
979}
980
981#[cfg(test)]
982pub(crate) mod criteria_mask_test_hooks {
983 use std::cell::Cell;
984
985 thread_local! {
986 static TEXT_SEGMENTS_TOTAL: Cell<usize> = const { Cell::new(0) };
987 static TEXT_SEGMENTS_ALL_NULL: Cell<usize> = const { Cell::new(0) };
988 }
989
990 pub fn reset_text_segment_counters() {
991 TEXT_SEGMENTS_TOTAL.with(|c| c.set(0));
992 TEXT_SEGMENTS_ALL_NULL.with(|c| c.set(0));
993 }
994
995 pub fn text_segment_counters() -> (usize, usize) {
996 let a = TEXT_SEGMENTS_TOTAL.with(|c| c.get());
997 let b = TEXT_SEGMENTS_ALL_NULL.with(|c| c.get());
998 (a, b)
999 }
1000
1001 pub(crate) fn inc_total() {
1002 TEXT_SEGMENTS_TOTAL.with(|c| c.set(c.get() + 1));
1003 }
1004 pub(crate) fn inc_all_null() {
1005 TEXT_SEGMENTS_ALL_NULL.with(|c| c.set(c.get() + 1));
1006 }
1007}
1008
1009#[cfg(test)]
1010pub(crate) mod visibility_mask_test_hooks {
1011 use std::cell::Cell;
1012
1013 thread_local! {
1014 static HITS: Cell<usize> = const { Cell::new(0) };
1015 static MISSES: Cell<usize> = const { Cell::new(0) };
1016 static EVICTIONS: Cell<usize> = const { Cell::new(0) };
1017 }
1018
1019 pub fn reset() {
1020 HITS.with(|c| c.set(0));
1021 MISSES.with(|c| c.set(0));
1022 EVICTIONS.with(|c| c.set(0));
1023 }
1024
1025 pub fn counters() -> (usize, usize, usize) {
1026 let hits = HITS.with(|c| c.get());
1027 let misses = MISSES.with(|c| c.get());
1028 let evictions = EVICTIONS.with(|c| c.get());
1029 (hits, misses, evictions)
1030 }
1031
1032 pub(crate) fn inc_hit() {
1033 HITS.with(|c| c.set(c.get() + 1));
1034 }
1035
1036 pub(crate) fn inc_miss() {
1037 MISSES.with(|c| c.set(c.get() + 1));
1038 }
1039
1040 pub(crate) fn inc_eviction() {
1041 EVICTIONS.with(|c| c.set(c.get() + 1));
1042 }
1043}
1044
1045fn compute_criteria_mask(
1046 view: &RangeView<'_>,
1047 col_in_view: usize,
1048 pred: &crate::args::CriteriaPredicate,
1049) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
1050 use crate::compute_prelude::{boolean, cmp, concat_arrays};
1051 use arrow::compute::kernels::comparison::{ilike, nilike};
1052 use arrow_array::{
1053 Array as _, ArrayRef, BooleanArray, Float64Array, StringArray, builder::BooleanBuilder,
1054 };
1055
1056 fn apply_numeric_pred(
1058 chunk: &Float64Array,
1059 pred: &crate::args::CriteriaPredicate,
1060 ) -> Option<BooleanArray> {
1061 match pred {
1062 crate::args::CriteriaPredicate::Gt(n) => {
1063 cmp::gt(chunk, &Float64Array::new_scalar(*n)).ok()
1064 }
1065 crate::args::CriteriaPredicate::Ge(n) => {
1066 cmp::gt_eq(chunk, &Float64Array::new_scalar(*n)).ok()
1067 }
1068 crate::args::CriteriaPredicate::Lt(n) => {
1069 cmp::lt(chunk, &Float64Array::new_scalar(*n)).ok()
1070 }
1071 crate::args::CriteriaPredicate::Le(n) => {
1072 cmp::lt_eq(chunk, &Float64Array::new_scalar(*n)).ok()
1073 }
1074 crate::args::CriteriaPredicate::Eq(v) => match v {
1075 formualizer_common::LiteralValue::Number(x) => {
1076 cmp::eq(chunk, &Float64Array::new_scalar(*x)).ok()
1077 }
1078 formualizer_common::LiteralValue::Int(i) => {
1079 cmp::eq(chunk, &Float64Array::new_scalar(*i as f64)).ok()
1080 }
1081 _ => None,
1082 },
1083 crate::args::CriteriaPredicate::Ne(v) => match v {
1084 formualizer_common::LiteralValue::Number(x) => {
1085 cmp::neq(chunk, &Float64Array::new_scalar(*x)).ok()
1086 }
1087 formualizer_common::LiteralValue::Int(i) => {
1088 cmp::neq(chunk, &Float64Array::new_scalar(*i as f64)).ok()
1089 }
1090 _ => None,
1091 },
1092 _ => None,
1093 }
1094 }
1095
1096 let is_numeric_pred = matches!(
1098 pred,
1099 crate::args::CriteriaPredicate::Gt(_)
1100 | crate::args::CriteriaPredicate::Ge(_)
1101 | crate::args::CriteriaPredicate::Lt(_)
1102 | crate::args::CriteriaPredicate::Le(_)
1103 | crate::args::CriteriaPredicate::Eq(formualizer_common::LiteralValue::Number(_))
1104 | crate::args::CriteriaPredicate::Eq(formualizer_common::LiteralValue::Int(_))
1105 | crate::args::CriteriaPredicate::Ne(formualizer_common::LiteralValue::Number(_))
1106 | crate::args::CriteriaPredicate::Ne(formualizer_common::LiteralValue::Int(_))
1107 );
1108
1109 if is_numeric_pred {
1113 let mut bool_parts: Vec<BooleanArray> = Vec::new();
1114 for res in view.numbers_slices() {
1115 let (_rs, _rl, cols_seg) = res.ok()?;
1116 if col_in_view < cols_seg.len() {
1117 let chunk = cols_seg[col_in_view].as_ref();
1118 let mask = apply_numeric_pred(chunk, pred)?;
1119 bool_parts.push(mask);
1120 }
1121 }
1122
1123 if bool_parts.is_empty() {
1124 return None;
1125 } else if bool_parts.len() == 1 {
1126 return Some(std::sync::Arc::new(bool_parts.remove(0)));
1127 } else {
1128 let anys: Vec<&dyn arrow_array::Array> = bool_parts
1130 .iter()
1131 .map(|a| a as &dyn arrow_array::Array)
1132 .collect();
1133 let conc: ArrayRef = concat_arrays(&anys).ok()?;
1134 let ba = conc.as_any().downcast_ref::<BooleanArray>()?.clone();
1135 return Some(std::sync::Arc::new(ba));
1136 }
1137 }
1138
1139 let (text_kind, text_pat, empty_special) = match pred {
1142 crate::args::CriteriaPredicate::Eq(formualizer_common::LiteralValue::Text(t)) => {
1143 (0u8, t.to_lowercase(), t.is_empty())
1144 }
1145 crate::args::CriteriaPredicate::Ne(formualizer_common::LiteralValue::Text(t)) => {
1146 (1u8, t.to_lowercase(), false)
1147 }
1148 crate::args::CriteriaPredicate::TextLike {
1149 pattern,
1150 case_insensitive,
1151 } => {
1152 let p = if *case_insensitive {
1153 pattern.to_lowercase()
1154 } else {
1155 pattern.clone()
1156 };
1157 (2u8, p.replace('*', "%").replace('?', "_"), false)
1158 }
1159 _ => return None,
1160 };
1161
1162 let pat = StringArray::new_scalar(text_pat);
1163 let mut bool_parts: Vec<BooleanArray> = Vec::new();
1164
1165 for res in view.iter_row_chunks() {
1166 let cs = res.ok()?;
1167 if cs.row_len == 0 {
1168 continue;
1169 }
1170 #[cfg(test)]
1171 criteria_mask_test_hooks::inc_total();
1172
1173 let slices = view.slice_lowered_text(cs.row_start, cs.row_len);
1174 if col_in_view >= slices.len() {
1175 return None;
1176 }
1177
1178 let seg_opt = slices[col_in_view].as_ref().map(|a| a.as_ref());
1179 let seg = match seg_opt {
1180 Some(s) => s,
1181 None => {
1182 #[cfg(test)]
1183 criteria_mask_test_hooks::inc_all_null();
1184 if text_kind == 0 && empty_special {
1185 let mut bb = BooleanBuilder::with_capacity(cs.row_len);
1187 bb.append_n(cs.row_len, true);
1188 bool_parts.push(bb.finish());
1189 } else {
1190 bool_parts.push(BooleanArray::new_null(cs.row_len));
1192 }
1193 continue;
1194 }
1195 };
1196
1197 let seg_sa = seg.as_any().downcast_ref::<StringArray>()?;
1198 let mut m = match text_kind {
1199 0 => ilike(seg_sa, &pat).ok()?,
1200 1 => nilike(seg_sa, &pat).ok()?,
1201 2 => ilike(seg_sa, &pat).ok()?,
1202 _ => return None,
1203 };
1204
1205 if text_kind == 0 && empty_special {
1206 let mut bb = BooleanBuilder::with_capacity(seg_sa.len());
1208 for i in 0..seg_sa.len() {
1209 bb.append_value(seg_sa.is_null(i));
1210 }
1211 let nulls = bb.finish();
1212 m = boolean::or_kleene(&m, &nulls).ok()?;
1213 }
1214
1215 bool_parts.push(m);
1216 }
1217
1218 if bool_parts.is_empty() {
1219 None
1220 } else if bool_parts.len() == 1 {
1221 Some(std::sync::Arc::new(bool_parts.remove(0)))
1222 } else {
1223 let anys: Vec<&dyn arrow_array::Array> = bool_parts
1224 .iter()
1225 .map(|a| a as &dyn arrow_array::Array)
1226 .collect();
1227 let conc: ArrayRef = concat_arrays(&anys).ok()?;
1228 let ba = conc.as_any().downcast_ref::<BooleanArray>()?.clone();
1229 Some(std::sync::Arc::new(ba))
1230 }
1231}
1232
1233#[derive(Debug, Clone)]
1234pub struct LayerInfo {
1235 pub vertex_count: usize,
1236 pub parallel_eligible: bool,
1237 pub sample_cells: Vec<String>, }
1239
1240#[derive(Debug, Clone)]
1241pub struct EvalPlan {
1242 pub total_vertices_to_evaluate: usize,
1243 pub layers: Vec<LayerInfo>,
1244 pub cycles_detected: usize,
1245 pub dirty_count: usize,
1246 pub volatile_count: usize,
1247 pub parallel_enabled: bool,
1248 pub estimated_parallel_layers: usize,
1249 pub target_cells: Vec<String>,
1250}
1251
1252impl<R> Engine<R>
1253where
1254 R: EvaluationContext,
1255{
1256 pub fn new(resolver: R, config: EvalConfig) -> Self {
1257 crate::builtins::load_builtins();
1258
1259 let clock = config.deterministic_mode.build_clock().unwrap_or_else(|_| {
1260 #[cfg(feature = "system-clock")]
1261 {
1262 Arc::new(crate::timezone::SystemClock::new(
1263 crate::timezone::TimeZoneSpec::default(),
1264 ))
1265 }
1266 #[cfg(not(feature = "system-clock"))]
1267 {
1268 Arc::new(crate::timezone::FixedClock::new(
1269 chrono::DateTime::UNIX_EPOCH,
1270 crate::timezone::TimeZoneSpec::Utc,
1271 ))
1272 }
1273 });
1274
1275 let thread_pool = if config.enable_parallel {
1277 let mut builder = ThreadPoolBuilder::new();
1278 if let Some(max_threads) = config.max_threads {
1279 builder = builder.num_threads(max_threads);
1280 }
1281
1282 match builder.build() {
1283 Ok(pool) => Some(Arc::new(pool)),
1284 Err(_) => {
1285 None
1287 }
1288 }
1289 } else {
1290 None
1291 };
1292
1293 let lookup_cache_max_bytes = config.lookup_index_cache_max_bytes;
1294 let mut engine = Self {
1295 graph: DependencyGraph::new_with_config(config.clone()),
1296 resolver,
1297 config,
1298 workbook_load_limits: crate::engine::WorkbookLoadLimits::default(),
1299 clock,
1300 thread_pool,
1301 recalc_epoch: 0,
1302 snapshot_id: std::sync::atomic::AtomicU64::new(1),
1303 topology_epoch: 0,
1304 cached_static_schedule: None,
1305 spill_mgr: ShimSpillManager::default(),
1306 arrow_sheets: SheetStore::default(),
1307 has_edited: false,
1308 overlay_compactions: 0,
1309 computed_overlay_bytes_estimate: 0,
1310 computed_overlay_mirroring_disabled: false,
1311 force_materialize_range_views: false,
1312 row_bounds_cache: std::sync::RwLock::new(None),
1313 used_axis_bounds_cache: std::sync::RwLock::new(None),
1314 lookup_index_cache: LookupIndexCache::new(lookup_cache_max_bytes),
1315 source_cache: Arc::new(std::sync::RwLock::new(SourceCache::default())),
1316 staged_formulas: std::collections::HashMap::new(),
1317 row_visibility: FxHashMap::default(),
1318 row_visibility_mask_cache: std::sync::RwLock::new(FxHashMap::default()),
1319 formula_parse_diagnostics: Vec::new(),
1320 last_formula_ingest_report: None,
1321 formula_ingest_report_total: FormulaIngestReport::default(),
1322 active_cancel_flag: None,
1323 action_depth: 0,
1324 last_virtual_dep_telemetry: VirtualDepTelemetry::default(),
1325 virtual_dep_fallback_activations: 0,
1326 formula_plane_indexes_epoch_seen: 0,
1327 #[cfg(test)]
1328 last_formula_plane_span_eval_report: None,
1329 };
1330 engine.config.arrow_storage_enabled = true;
1332 engine.config.delta_overlay_enabled = true;
1333 engine.config.write_formula_overlay_enabled = true;
1334 let default_sheet = engine.graph.default_sheet_name().to_string();
1335 engine.ensure_arrow_sheet(&default_sheet);
1336 engine
1337 }
1338
1339 pub fn with_thread_pool(
1341 resolver: R,
1342 config: EvalConfig,
1343 thread_pool: Arc<rayon::ThreadPool>,
1344 ) -> Self {
1345 crate::builtins::load_builtins();
1346 let clock = config.deterministic_mode.build_clock().unwrap_or_else(|_| {
1347 #[cfg(feature = "system-clock")]
1348 {
1349 Arc::new(crate::timezone::SystemClock::new(
1350 crate::timezone::TimeZoneSpec::default(),
1351 ))
1352 }
1353 #[cfg(not(feature = "system-clock"))]
1354 {
1355 Arc::new(crate::timezone::FixedClock::new(
1356 chrono::DateTime::UNIX_EPOCH,
1357 crate::timezone::TimeZoneSpec::Utc,
1358 ))
1359 }
1360 });
1361 let lookup_cache_max_bytes = config.lookup_index_cache_max_bytes;
1362 let mut engine = Self {
1363 graph: DependencyGraph::new_with_config(config.clone()),
1364 resolver,
1365 config,
1366 workbook_load_limits: crate::engine::WorkbookLoadLimits::default(),
1367 clock,
1368 thread_pool: Some(thread_pool),
1369 recalc_epoch: 0,
1370 snapshot_id: std::sync::atomic::AtomicU64::new(1),
1371 topology_epoch: 0,
1372 cached_static_schedule: None,
1373 spill_mgr: ShimSpillManager::default(),
1374 arrow_sheets: SheetStore::default(),
1375 has_edited: false,
1376 overlay_compactions: 0,
1377 computed_overlay_bytes_estimate: 0,
1378 computed_overlay_mirroring_disabled: false,
1379 force_materialize_range_views: false,
1380 row_bounds_cache: std::sync::RwLock::new(None),
1381 used_axis_bounds_cache: std::sync::RwLock::new(None),
1382 lookup_index_cache: LookupIndexCache::new(lookup_cache_max_bytes),
1383 source_cache: Arc::new(std::sync::RwLock::new(SourceCache::default())),
1384 staged_formulas: std::collections::HashMap::new(),
1385 row_visibility: FxHashMap::default(),
1386 row_visibility_mask_cache: std::sync::RwLock::new(FxHashMap::default()),
1387 formula_parse_diagnostics: Vec::new(),
1388 last_formula_ingest_report: None,
1389 formula_ingest_report_total: FormulaIngestReport::default(),
1390 active_cancel_flag: None,
1391 action_depth: 0,
1392 last_virtual_dep_telemetry: VirtualDepTelemetry::default(),
1393 virtual_dep_fallback_activations: 0,
1394 formula_plane_indexes_epoch_seen: 0,
1395 #[cfg(test)]
1396 last_formula_plane_span_eval_report: None,
1397 };
1398 engine.config.arrow_storage_enabled = true;
1400 engine.config.delta_overlay_enabled = true;
1401 engine.config.write_formula_overlay_enabled = true;
1402 let default_sheet = engine.graph.default_sheet_name().to_string();
1403 engine.ensure_arrow_sheet(&default_sheet);
1404 engine
1405 }
1406
1407 pub fn workbook_load_limits(&self) -> &crate::engine::WorkbookLoadLimits {
1408 &self.workbook_load_limits
1409 }
1410
1411 pub fn set_workbook_load_limits(&mut self, limits: crate::engine::WorkbookLoadLimits) {
1412 self.workbook_load_limits = limits;
1413 }
1414
1415 fn clear_source_cache(&self) {
1416 if let Ok(mut g) = self.source_cache.write() {
1417 *g = SourceCache::default();
1418 }
1419 }
1420
1421 pub fn last_virtual_dep_telemetry(&self) -> &VirtualDepTelemetry {
1422 &self.last_virtual_dep_telemetry
1423 }
1424
1425 pub fn virtual_dep_fallback_activations(&self) -> u64 {
1426 self.virtual_dep_fallback_activations
1427 }
1428
1429 pub(crate) fn last_lookup_index_cache_report(&self) -> LookupIndexCacheReport {
1430 self.lookup_index_cache.report()
1431 }
1432
1433 fn lookup_view_contains_volatile(&self, view: &RangeView<'_>, sheet_id: SheetId) -> bool {
1434 let start_row = view.start_row();
1435 let end_row = view.end_row();
1436 let start_col = view.start_col();
1437 let end_col = view.end_col();
1438 for row in start_row..=end_row {
1439 let Ok(row_u32) = u32::try_from(row) else {
1440 return true;
1441 };
1442 for col in start_col..=end_col {
1443 let Ok(col_u32) = u32::try_from(col) else {
1444 return true;
1445 };
1446 let cell_ref = self
1447 .graph
1448 .make_cell_ref_internal(sheet_id, row_u32, col_u32);
1449 if let Some(vertex_id) = self.graph.get_vertex_id_for_address(&cell_ref)
1450 && self.graph.is_volatile(*vertex_id)
1451 {
1452 return true;
1453 }
1454 }
1455 }
1456 false
1457 }
1458
1459 fn build_lookup_index_impl(
1460 &self,
1461 view: &RangeView<'_>,
1462 axis: LookupAxis,
1463 ) -> Option<Arc<LookupIndex>> {
1464 let (rows, cols) = view.dims();
1465 if rows == 0 || cols == 0 {
1466 self.lookup_index_cache.note_skipped_tiny();
1467 return None;
1468 }
1469 let len = match axis {
1470 LookupAxis::ColumnInView(col) => {
1471 if col >= cols {
1472 self.lookup_index_cache.note_skipped_tiny();
1473 return None;
1474 }
1475 rows
1476 }
1477 LookupAxis::RowInView(row) => {
1478 if row >= rows {
1479 self.lookup_index_cache.note_skipped_tiny();
1480 return None;
1481 }
1482 cols
1483 }
1484 };
1485 if len < 64 {
1486 self.lookup_index_cache.note_skipped_tiny();
1487 return None;
1488 }
1489
1490 let sheet_id = self.graph.sheet_id(view.sheet_name())?;
1491 let key = LookupIndexKey {
1492 sheet_id,
1493 start_row: u32::try_from(view.start_row()).ok()?,
1494 start_col: u32::try_from(view.start_col()).ok()?,
1495 end_row: u32::try_from(view.end_row()).ok()?,
1496 end_col: u32::try_from(view.end_col()).ok()?,
1497 axis,
1498 snapshot_id: self.data_snapshot_id(),
1499 };
1500 if let Some(index) = self.lookup_index_cache.get(&key) {
1501 return Some(index);
1502 }
1503 if self
1504 .lookup_index_cache
1505 .would_exceed_cap(estimate_bytes(len, 0))
1506 {
1507 self.lookup_index_cache.note_skipped_cap();
1508 return None;
1509 }
1510 if !self.lookup_index_cache.should_build(key) {
1511 return None;
1512 }
1513 if self.lookup_index_cache.is_known_volatile(&key) {
1514 self.lookup_index_cache.note_skipped_volatile();
1515 return None;
1516 }
1517 if self.lookup_view_contains_volatile(view, sheet_id) {
1518 self.lookup_index_cache.note_volatile_key(key);
1519 self.lookup_index_cache.note_skipped_volatile();
1520 return None;
1521 }
1522 match LookupIndex::build(view, axis).ok()? {
1523 BuildOutcome::Built(index) => self.lookup_index_cache.insert_if_room(key, index),
1524 BuildOutcome::ErrorInLookupAxis => {
1525 self.lookup_index_cache.note_skipped_error();
1526 None
1527 }
1528 BuildOutcome::Degenerate => {
1529 self.lookup_index_cache.note_skipped_tiny();
1530 None
1531 }
1532 }
1533 }
1534
1535 fn reset_virtual_dep_telemetry_if_disabled(&mut self) {
1536 if !self.config.enable_virtual_dep_telemetry {
1537 self.last_virtual_dep_telemetry = VirtualDepTelemetry {
1538 fallback_mode_activations: self.virtual_dep_fallback_activations,
1539 ..VirtualDepTelemetry::default()
1540 };
1541 }
1542 }
1543
1544 fn source_cache_session(&self) -> SourceCacheSession {
1545 self.clear_source_cache();
1546 SourceCacheSession {
1547 cache: self.source_cache.clone(),
1548 }
1549 }
1550
1551 fn resolve_source_scalar_cached(
1552 &self,
1553 name: &str,
1554 version: Option<u64>,
1555 ) -> Result<LiteralValue, ExcelError> {
1556 let key = (name.to_string(), version);
1557 if let Ok(mut g) = self.source_cache.write() {
1558 if let Some(v) = g.scalars.get(&key) {
1559 return Ok(v.clone());
1560 }
1561
1562 let v = self.resolver.resolve_source_scalar(name).map_err(|err| {
1563 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
1564 ExcelError::new(ExcelErrorKind::Ref)
1565 .with_message(format!("Unresolved source scalar: {name}"))
1566 } else {
1567 err
1568 }
1569 })?;
1570 g.scalars.insert(key, v.clone());
1571 Ok(v)
1572 } else {
1573 self.resolver.resolve_source_scalar(name).map_err(|err| {
1574 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
1575 ExcelError::new(ExcelErrorKind::Ref)
1576 .with_message(format!("Unresolved source scalar: {name}"))
1577 } else {
1578 err
1579 }
1580 })
1581 }
1582 }
1583
1584 fn resolve_source_table_cached(
1585 &self,
1586 name: &str,
1587 version: Option<u64>,
1588 ) -> Result<Arc<dyn crate::traits::Table>, ExcelError> {
1589 let key = (name.to_string(), version);
1590 if let Ok(mut g) = self.source_cache.write() {
1591 if let Some(t) = g.tables.get(&key) {
1592 return Ok(t.clone());
1593 }
1594
1595 let t = self.resolver.resolve_source_table(name).map_err(|err| {
1596 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
1597 ExcelError::new(ExcelErrorKind::Ref)
1598 .with_message(format!("Unresolved source table: {name}"))
1599 } else {
1600 err
1601 }
1602 })?;
1603 let t: Arc<dyn crate::traits::Table> = Arc::from(t);
1604 g.tables.insert(key, t.clone());
1605 Ok(t)
1606 } else {
1607 self.resolver
1608 .resolve_source_table(name)
1609 .map_err(|err| {
1610 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
1611 ExcelError::new(ExcelErrorKind::Ref)
1612 .with_message(format!("Unresolved source table: {name}"))
1613 } else {
1614 err
1615 }
1616 })
1617 .map(Arc::from)
1618 }
1619 }
1620
1621 fn source_table_to_range_view(
1622 &self,
1623 table: &dyn crate::traits::Table,
1624 spec: &Option<formualizer_parse::parser::TableSpecifier>,
1625 ) -> Result<RangeView<'static>, ExcelError> {
1626 use formualizer_parse::parser::{SpecialItem, TableSpecifier};
1627
1628 let owned = match spec {
1629 Some(TableSpecifier::Column(c)) => {
1630 let c = c.trim();
1631 if c == "@" || c.contains('[') || c.contains(']') || c.contains(',') {
1632 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
1633 "Complex structured references not yet supported".to_string(),
1634 ));
1635 }
1636 table.get_column(c)?.materialise().into_owned()
1637 }
1638 Some(TableSpecifier::ColumnRange(start, end)) => {
1639 let cols = table.columns();
1640 let start = start.trim();
1641 let end = end.trim();
1642 let start_key = start.to_lowercase();
1643 let end_key = end.to_lowercase();
1644 let start_idx = cols.iter().position(|n| n.to_lowercase() == start_key);
1645 let end_idx = cols.iter().position(|n| n.to_lowercase() == end_key);
1646 if let (Some(mut si), Some(mut ei)) = (start_idx, end_idx) {
1647 if si > ei {
1648 std::mem::swap(&mut si, &mut ei);
1649 }
1650 let h = table.data_height();
1651 let w = ei - si + 1;
1652 let mut rows = vec![vec![LiteralValue::Empty; w]; h];
1653 for (offset, ci) in (si..=ei).enumerate() {
1654 let cname = &cols[ci];
1655 let col_range = table.get_column(cname)?;
1656 let (rh, _) = col_range.dimensions();
1657 for (r, row) in rows.iter_mut().enumerate().take(h.min(rh)) {
1658 row[offset] = col_range.get(r, 0)?;
1659 }
1660 }
1661 rows
1662 } else {
1663 return Err(ExcelError::new(ExcelErrorKind::Ref)
1664 .with_message("Column range refers to unknown column(s)".to_string()));
1665 }
1666 }
1667 Some(TableSpecifier::SpecialItem(SpecialItem::Headers))
1668 | Some(TableSpecifier::Headers) => table
1669 .headers_row()
1670 .map(|r| r.materialise().into_owned())
1671 .unwrap_or_default(),
1672 Some(TableSpecifier::SpecialItem(SpecialItem::Totals))
1673 | Some(TableSpecifier::Totals) => table
1674 .totals_row()
1675 .map(|r| r.materialise().into_owned())
1676 .unwrap_or_default(),
1677 Some(TableSpecifier::SpecialItem(SpecialItem::Data)) | Some(TableSpecifier::Data) => {
1678 table
1679 .data_body()
1680 .map(|r| r.materialise().into_owned())
1681 .unwrap_or_default()
1682 }
1683 Some(TableSpecifier::SpecialItem(SpecialItem::All)) | Some(TableSpecifier::All) => {
1684 let mut out: Vec<Vec<LiteralValue>> = Vec::new();
1685 if let Some(h) = table.headers_row() {
1686 out.extend(h.iter_rows());
1687 }
1688 if let Some(body) = table.data_body() {
1689 out.extend(body.iter_rows());
1690 }
1691 if let Some(tr) = table.totals_row() {
1692 out.extend(tr.iter_rows());
1693 }
1694 out
1695 }
1696 Some(TableSpecifier::SpecialItem(SpecialItem::ThisRow)) => {
1697 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
1698 "@ (This Row) requires table-aware context; not yet supported".to_string(),
1699 ));
1700 }
1701 Some(TableSpecifier::Row(_)) | Some(TableSpecifier::Combination(_)) => {
1702 return Err(ExcelError::new(ExcelErrorKind::NImpl)
1703 .with_message("Complex structured references not yet supported".to_string()));
1704 }
1705 None => {
1706 return Err(ExcelError::new(ExcelErrorKind::NImpl)
1707 .with_message("Table reference without specifier is unsupported".to_string()));
1708 }
1709 };
1710
1711 Ok(RangeView::from_owned_rows(owned, self.config.date_system))
1712 }
1713
1714 pub fn default_sheet_id(&self) -> SheetId {
1715 self.graph.default_sheet_id()
1716 }
1717
1718 pub fn default_sheet_name(&self) -> &str {
1719 self.graph.default_sheet_name()
1720 }
1721
1722 pub fn set_workbook_seed(&mut self, seed: u64) {
1724 self.config.workbook_seed = seed;
1725 }
1726
1727 pub fn set_volatile_level(&mut self, level: crate::traits::VolatileLevel) {
1729 self.config.volatile_level = level;
1730 }
1731
1732 pub fn set_deterministic_mode(
1734 &mut self,
1735 mode: crate::engine::DeterministicMode,
1736 ) -> Result<(), ExcelError> {
1737 let clock = mode.build_clock()?;
1738 self.config.deterministic_mode = mode;
1739 self.clock = clock;
1740 Ok(())
1741 }
1742
1743 fn validate_deterministic_mode(&self) -> Result<(), ExcelError> {
1744 self.config.deterministic_mode.validate()
1745 }
1746
1747 pub fn sheet_id(&self, name: &str) -> Option<SheetId> {
1748 self.graph.sheet_id(name)
1749 }
1750
1751 pub fn sheet_id_mut(&mut self, name: &str) -> SheetId {
1752 self.add_sheet(name)
1753 .unwrap_or_else(|_| self.graph.sheet_id_mut(name))
1754 }
1755
1756 pub fn sheet_name(&self, id: SheetId) -> &str {
1757 self.graph.sheet_name(id)
1758 }
1759
1760 pub fn add_sheet(&mut self, name: &str) -> Result<SheetId, ExcelError> {
1761 let id = self.graph.add_sheet(name)?;
1762 self.ensure_arrow_sheet(name);
1763 self.mark_topology_edited();
1768 Ok(id)
1769 }
1770
1771 pub fn duplicate_sheet(&mut self, source: &str, new_name: &str) -> Result<SheetId, ExcelError> {
1772 let source_id = self.graph.sheet_id(source).ok_or_else(|| {
1773 ExcelError::new(ExcelErrorKind::Value).with_message("Source sheet does not exist")
1774 })?;
1775 self.demote_spans_preserving_computed_overlays(source_id, Region::whole_sheet(source_id))
1778 .map_err(Self::editor_error_to_excel)?;
1779 let new_id = self.graph.duplicate_sheet(source_id, new_name)?;
1780
1781 if let Some(source_sheet) = self.arrow_sheets.sheet(source).cloned() {
1782 let mut copied_sheet = source_sheet;
1783 copied_sheet.name = Arc::<str>::from(new_name);
1784 self.arrow_sheets.sheets.push(copied_sheet);
1785 } else {
1786 self.ensure_arrow_sheet(new_name);
1787 }
1788
1789 self.clear_all_computed_overlays();
1790 self.mark_all_formula_vertices_dirty();
1791 self.mark_topology_edited();
1792 Ok(new_id)
1793 }
1794
1795 fn ensure_arrow_sheet(&mut self, name: &str) {
1796 if self.arrow_sheets.sheet(name).is_some() {
1797 return;
1798 }
1799 self.arrow_sheets
1800 .sheets
1801 .push(crate::arrow_store::ArrowSheet {
1802 name: std::sync::Arc::<str>::from(name),
1803 columns: Vec::new(),
1804 nrows: 0,
1805 chunk_starts: Vec::new(),
1806 chunk_rows: 32 * 1024,
1807 });
1808 }
1809
1810 pub fn remove_sheet(&mut self, sheet_id: SheetId) -> Result<(), ExcelError> {
1811 let name = self.graph.sheet_name(sheet_id).to_string();
1812 self.demote_spans_preserving_computed_overlays(sheet_id, Region::whole_sheet(sheet_id))
1816 .map_err(Self::editor_error_to_excel)?;
1817 self.graph.remove_sheet(sheet_id)?;
1818 self.arrow_sheets.sheets.retain(|s| s.name.as_ref() != name);
1819 self.clear_all_computed_overlays();
1820 self.mark_all_formula_vertices_dirty();
1821 self.staged_formulas.remove(&name);
1822 if self.row_visibility.remove(&sheet_id).is_some() {
1823 self.invalidate_row_visibility_mask_cache();
1824 }
1825 self.record_formula_plane_structural_change(StructuralScope::RemovedSheet(sheet_id));
1826 self.mark_topology_edited();
1827 Ok(())
1828 }
1829
1830 fn rename_sheet_in_arrow_store(&mut self, target_name: &str, new_name: &str) -> bool {
1832 if let Some(asheet) = self
1833 .arrow_sheets
1834 .sheets
1835 .iter_mut()
1836 .find(|s| s.name.as_ref() == target_name)
1837 {
1838 asheet.name = std::sync::Arc::<str>::from(new_name);
1839 return true;
1840 }
1841 false
1842 }
1843
1844 pub fn rename_sheet(&mut self, sheet_id: SheetId, new_name: &str) -> Result<(), ExcelError> {
1845 let old_name = self.graph.sheet_name(sheet_id).to_string();
1846
1847 self.rename_sheet_in_arrow_store(&old_name, new_name);
1850
1851 match self.graph.rename_sheet(sheet_id, new_name) {
1853 Ok(_) => {
1854 self.rename_staged_formula_sheet(&old_name, new_name);
1855 let sheet_vertices: Vec<VertexId> =
1857 self.graph.vertices_in_sheet(sheet_id).collect();
1858 for v_id in sheet_vertices {
1859 self.graph.mark_vertex_dirty(v_id);
1860 }
1861 self.mark_topology_edited();
1865 Ok(())
1866 }
1867 Err(e) => {
1868 self.rename_sheet_in_arrow_store(new_name, &old_name);
1870 Err(e)
1871 }
1872 }
1873 }
1874
1875 pub fn named_ranges_iter(
1876 &self,
1877 ) -> impl Iterator<Item = (&String, &crate::engine::named_range::NamedRange)> {
1878 self.graph.named_ranges_iter()
1879 }
1880
1881 pub fn sheet_named_ranges_iter(
1882 &self,
1883 ) -> impl Iterator<Item = (&(SheetId, String), &crate::engine::named_range::NamedRange)> {
1884 self.graph.sheet_named_ranges_iter()
1885 }
1886
1887 pub fn resolve_name_entry(
1888 &self,
1889 name: &str,
1890 current_sheet: SheetId,
1891 ) -> Option<&crate::engine::named_range::NamedRange> {
1892 self.graph.resolve_name_entry(name, current_sheet)
1893 }
1894
1895 pub fn named_ranges_snapshot(&self) -> Vec<crate::engine::named_range::NamedRangeSnapshot> {
1896 let mut out: Vec<crate::engine::named_range::NamedRangeSnapshot> = Vec::new();
1897
1898 for (name, named) in self.graph.named_ranges_iter() {
1899 out.push(crate::engine::named_range::NamedRangeSnapshot {
1900 name: name.clone(),
1901 scope: NameScope::Workbook,
1902 definition: named.definition.clone(),
1903 });
1904 }
1905
1906 for ((sheet_id, name), named) in self.graph.sheet_named_ranges_iter() {
1907 out.push(crate::engine::named_range::NamedRangeSnapshot {
1908 name: name.clone(),
1909 scope: NameScope::Sheet(*sheet_id),
1910 definition: named.definition.clone(),
1911 });
1912 }
1913
1914 out.sort_by(|a, b| {
1915 let a_scope = match a.scope {
1916 NameScope::Workbook => (0u8, 0u32),
1917 NameScope::Sheet(id) => (1u8, u32::from(id)),
1918 };
1919 let b_scope = match b.scope {
1920 NameScope::Workbook => (0u8, 0u32),
1921 NameScope::Sheet(id) => (1u8, u32::from(id)),
1922 };
1923 a_scope.cmp(&b_scope).then_with(|| a.name.cmp(&b.name))
1924 });
1925
1926 out
1927 }
1928
1929 pub fn named_ranges_snapshot_for_sheet(
1930 &self,
1931 sheet_id: SheetId,
1932 ) -> Vec<crate::engine::named_range::NamedRangeSnapshot> {
1933 self.named_ranges_snapshot()
1934 .into_iter()
1935 .filter(|entry| match entry.scope {
1936 NameScope::Workbook => true,
1937 NameScope::Sheet(id) => id == sheet_id,
1938 })
1939 .collect()
1940 }
1941
1942 pub fn define_name(
1943 &mut self,
1944 name: &str,
1945 definition: NamedDefinition,
1946 scope: NameScope,
1947 ) -> Result<(), ExcelError> {
1948 self.graph.define_name(name, definition, scope)?;
1949 self.record_formula_plane_structural_change(StructuralScope::AllSheets);
1950 self.mark_topology_edited();
1951 Ok(())
1952 }
1953
1954 pub fn update_name(
1955 &mut self,
1956 name: &str,
1957 definition: NamedDefinition,
1958 scope: NameScope,
1959 ) -> Result<(), ExcelError> {
1960 self.graph.update_name(name, definition, scope)?;
1961 self.record_formula_plane_structural_change(StructuralScope::AllSheets);
1962 self.mark_topology_edited();
1963 Ok(())
1964 }
1965
1966 pub fn delete_name(&mut self, name: &str, scope: NameScope) -> Result<(), ExcelError> {
1967 self.graph.delete_name(name, scope)?;
1968 self.record_formula_plane_structural_change(StructuralScope::AllSheets);
1969 self.mark_topology_edited();
1970 Ok(())
1971 }
1972
1973 pub fn define_table(
1974 &mut self,
1975 name: &str,
1976 range: crate::reference::RangeRef,
1977 header_row: bool,
1978 headers: Vec<String>,
1979 totals_row: bool,
1980 ) -> Result<(), ExcelError> {
1981 self.graph
1982 .define_table(name, range, header_row, headers, totals_row)?;
1983 self.record_formula_plane_structural_change(StructuralScope::AllSheets);
1984 self.mark_topology_edited();
1985 Ok(())
1986 }
1987
1988 pub fn define_source_scalar(
1989 &mut self,
1990 name: &str,
1991 version: Option<u64>,
1992 ) -> Result<(), ExcelError> {
1993 self.graph.define_source_scalar(name, version)?;
1994 self.record_formula_plane_structural_change(StructuralScope::AllSheets);
1995 self.mark_topology_edited();
1996 Ok(())
1997 }
1998
1999 pub fn define_source_table(
2000 &mut self,
2001 name: &str,
2002 version: Option<u64>,
2003 ) -> Result<(), ExcelError> {
2004 self.graph.define_source_table(name, version)?;
2005 self.record_formula_plane_structural_change(StructuralScope::AllSheets);
2006 self.mark_topology_edited();
2007 Ok(())
2008 }
2009
2010 pub fn set_source_scalar_version(
2011 &mut self,
2012 name: &str,
2013 version: Option<u64>,
2014 ) -> Result<(), ExcelError> {
2015 self.graph.set_source_scalar_version(name, version)?;
2016 self.record_formula_plane_structural_change(StructuralScope::AllSheets);
2017 Ok(())
2018 }
2019
2020 pub fn set_source_table_version(
2021 &mut self,
2022 name: &str,
2023 version: Option<u64>,
2024 ) -> Result<(), ExcelError> {
2025 self.graph.set_source_table_version(name, version)?;
2026 self.record_formula_plane_structural_change(StructuralScope::AllSheets);
2027 Ok(())
2028 }
2029
2030 pub fn invalidate_source(&mut self, name: &str) -> Result<(), ExcelError> {
2031 self.graph.invalidate_source(name)?;
2032 self.record_formula_plane_structural_change(StructuralScope::AllSheets);
2033 Ok(())
2034 }
2035
2036 pub fn vertex_value(&self, vertex: VertexId) -> Option<LiteralValue> {
2037 self.graph.get_value(vertex)
2038 }
2039
2040 pub fn graph_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
2041 self.graph.get_cell_value(sheet, row, col)
2042 }
2043
2044 pub fn vertex_for_cell(&self, cell: &CellRef) -> Option<VertexId> {
2045 self.graph.get_vertex_for_cell(cell)
2046 }
2047
2048 pub fn evaluation_vertices(&self) -> Vec<VertexId> {
2049 self.graph.get_evaluation_vertices()
2050 }
2051
2052 pub fn baseline_stats(&self) -> EngineBaselineStats {
2054 let graph = self.graph.baseline_stats();
2055 let formula_authority = self.graph.formula_authority();
2056 EngineBaselineStats {
2057 graph_vertex_count: graph.graph_vertex_count,
2058 graph_formula_vertex_count: graph.graph_formula_vertex_count,
2059 graph_edge_count: graph.graph_edge_count,
2060 dirty_vertex_count: graph.dirty_vertex_count,
2061 evaluation_vertex_count: graph.evaluation_vertex_count,
2062 formula_ast_root_count: graph.formula_ast_root_count,
2063 formula_ast_node_count: graph.formula_ast_node_count,
2064 staged_formula_count: self.staged_formula_count(),
2065 formula_plane_active_span_count: formula_authority.active_span_count(),
2066 formula_plane_producer_result_entries: formula_authority.producer_results.len(),
2067 formula_plane_consumer_read_entries: formula_authority.consumer_reads.len(),
2068 }
2069 }
2070
2071 #[cfg(test)]
2072 pub(crate) fn used_axis_bounds_cache_stats(&self) -> (usize, usize, usize, usize) {
2073 self.used_axis_bounds_cache
2074 .read()
2075 .ok()
2076 .and_then(|guard| {
2077 guard.as_ref().map(|cache| {
2078 (
2079 cache.row_hits.load(Ordering::Relaxed),
2080 cache.row_misses.load(Ordering::Relaxed),
2081 cache.col_hits.load(Ordering::Relaxed),
2082 cache.col_misses.load(Ordering::Relaxed),
2083 )
2084 })
2085 })
2086 .unwrap_or((0, 0, 0, 0))
2087 }
2088
2089 pub fn set_first_load_assume_new(&mut self, enabled: bool) {
2090 self.graph.set_first_load_assume_new(enabled);
2091 }
2092
2093 pub fn reset_ensure_touched(&mut self) {
2094 self.graph.reset_ensure_touched();
2095 }
2096
2097 pub fn finalize_sheet_index(&mut self, sheet: &str) {
2098 self.graph.finalize_sheet_index(sheet);
2099 }
2100
2101 pub fn action<T>(
2110 &mut self,
2111 name: impl AsRef<str>,
2112 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
2113 ) -> Result<T, crate::engine::EditorError> {
2114 if self.action_depth != 0 {
2115 return Err(crate::engine::EditorError::TransactionFailed {
2116 reason: "Nested Engine::action calls are not supported (ticket 614: commit-only surface)"
2117 .to_string(),
2118 });
2119 }
2120
2121 self.action_depth = 1;
2122 let engine_ptr: *mut Engine<R> = self;
2123 let _guard = ActionDepthGuard {
2124 engine: engine_ptr,
2125 _marker: std::marker::PhantomData,
2126 };
2127
2128 let mut tx = EngineAction {
2129 engine: self,
2130 name: name.as_ref().to_string(),
2131 log: None,
2132 arrow_undo: None,
2133 atomic_policy: false,
2134 };
2135 f(&mut tx)
2136 }
2137
2138 pub fn action_atomic<T>(
2142 &mut self,
2143 name: impl Into<String>,
2144 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
2145 ) -> Result<T, crate::engine::EditorError> {
2146 let (v, _j) = self.action_atomic_journal(name, f)?;
2147 Ok(v)
2148 }
2149
2150 pub fn action_atomic_journal<T>(
2152 &mut self,
2153 name: impl Into<String>,
2154 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
2155 ) -> Result<(T, crate::engine::ActionJournal), crate::engine::EditorError> {
2156 if self.action_depth != 0 {
2157 return Err(crate::engine::EditorError::TransactionFailed {
2158 reason: "Nested Engine::action calls are not supported (deterministic rule)"
2159 .to_string(),
2160 });
2161 }
2162
2163 self.action_depth = 1;
2164 let engine_ptr: *mut Engine<R> = self;
2165 let _guard = ActionDepthGuard {
2166 engine: engine_ptr,
2167 _marker: std::marker::PhantomData,
2168 };
2169
2170 let name_str = name.into();
2171 let mut log = crate::engine::ChangeLog::new();
2172 let start_len = log.len();
2173 self.action_atomic_impl(&mut log, start_len, name_str, f)
2174 }
2175
2176 fn action_atomic_impl<T>(
2177 &mut self,
2178 log: &mut crate::engine::ChangeLog,
2179 start_len: usize,
2180 name: String,
2181 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
2182 ) -> Result<(T, crate::engine::ActionJournal), crate::engine::EditorError> {
2183 let mut arrow_undo = crate::engine::ArrowUndoBatch::default();
2184 let arrow_ptr: *mut crate::engine::ArrowUndoBatch = &mut arrow_undo;
2185
2186 let log_ptr: *mut crate::engine::ChangeLog = log;
2187 let mut tx = EngineAction {
2188 engine: self,
2189 name: name.clone(),
2190 log: Some(log_ptr),
2191 arrow_undo: Some(arrow_ptr),
2192 atomic_policy: true,
2193 };
2194
2195 let res = f(&mut tx);
2196
2197 let graph_events: Vec<crate::engine::ChangeEvent> =
2199 unsafe { (&*log_ptr).events() }[start_len..].to_vec();
2200 let graph_batch = crate::engine::GraphUndoBatch {
2201 events: graph_events,
2202 };
2203 let affected_cells = arrow_undo.ops.len();
2204 let journal = crate::engine::ActionJournal {
2205 name,
2206 graph: graph_batch,
2207 arrow: arrow_undo,
2208 affected_cells,
2209 };
2210
2211 match res {
2212 Ok(v) => {
2213 if !journal.graph.is_empty() || !journal.arrow.is_empty() {
2214 for event in &journal.graph.events {
2215 self.record_formula_plane_change_for_event(event);
2216 }
2217 self.mark_data_edited();
2218 }
2219 Ok((v, journal))
2220 }
2221 Err(e) => {
2222 if let Err(rb) = self.rollback_from_action_journal(&journal) {
2223 return Err(crate::engine::EditorError::TransactionFailed {
2224 reason: format!(
2225 "Engine::action_atomic rollback failed after error '{e}': {rb}"
2226 ),
2227 });
2228 }
2229 if !journal.graph.is_empty() || !journal.arrow.is_empty() {
2230 for event in &journal.graph.events {
2231 self.record_formula_plane_change_for_event(event);
2232 }
2233 }
2234 Err(e)
2235 }
2236 }
2237 }
2238
2239 pub fn action_with_logger<T>(
2246 &mut self,
2247 log: &mut crate::engine::ChangeLog,
2248 name: impl AsRef<str>,
2249 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
2250 ) -> Result<T, crate::engine::EditorError> {
2251 if self.action_depth != 0 {
2252 return Err(crate::engine::EditorError::TransactionFailed {
2253 reason: "Nested Engine::action calls are not supported (deterministic rule)"
2254 .to_string(),
2255 });
2256 }
2257
2258 self.action_depth = 1;
2259 let engine_ptr: *mut Engine<R> = self;
2260 let _guard = ActionDepthGuard {
2261 engine: engine_ptr,
2262 _marker: std::marker::PhantomData,
2263 };
2264
2265 let start_len = log.len();
2266 let name_str = name.as_ref().to_string();
2267 log.begin_compound(name_str.clone());
2268
2269 let res = self.action_atomic_impl(log, start_len, name_str, f);
2272
2273 match res {
2274 Ok((v, _journal)) => {
2275 log.end_compound();
2276 Ok(v)
2277 }
2278 Err(e) => {
2279 log.end_compound();
2281 log.truncate(start_len);
2282 Err(e)
2283 }
2284 }
2285 }
2286
2287 fn rollback_from_action_journal(
2288 &mut self,
2289 journal: &crate::engine::ActionJournal,
2290 ) -> Result<(), crate::engine::EditorError> {
2291 journal.graph.undo(&mut self.graph)?;
2293 self.apply_inverse_row_visibility_events(&journal.graph.events);
2295 self.apply_arrow_undo_batch(&journal.arrow, true);
2297 Ok(())
2298 }
2299
2300 fn rollback_from_change_events(
2301 &mut self,
2302 events: &[crate::engine::ChangeEvent],
2303 ) -> Result<(), crate::engine::EditorError> {
2304 use crate::engine::ChangeEvent;
2305
2306 {
2308 let mut editor = crate::engine::VertexEditor::new(&mut self.graph);
2309 let mut compound_stack: Vec<usize> = Vec::new();
2310 for ev in events.iter().rev() {
2311 match ev {
2312 ChangeEvent::CompoundEnd { depth } => compound_stack.push(*depth),
2313 ChangeEvent::CompoundStart { depth, .. } => {
2314 if compound_stack.last() == Some(depth) {
2315 compound_stack.pop();
2316 }
2317 }
2318 ChangeEvent::SetRowVisibility { .. } => {
2319 }
2321 _ => {
2322 editor.apply_inverse(ev.clone())?;
2323 }
2324 }
2325 }
2326 }
2327
2328 for ev in events.iter().rev() {
2330 self.apply_inverse_row_visibility_event(ev);
2331 }
2332
2333 for ev in events.iter().rev() {
2335 self.mirror_inverse_change_to_arrow(ev);
2336 }
2337
2338 Ok(())
2339 }
2340
2341 fn read_cell_formula_ast(&self, sheet: &str, row: u32, col: u32) -> Option<ASTNode> {
2342 let sheet_id = self.graph.sheet_id(sheet)?;
2343 let coord = Coord::from_excel(row, col, true, true);
2344 let cell = CellRef::new(sheet_id, coord);
2345 let vid = self.graph.get_vertex_for_cell(&cell)?;
2346 let ast_id = self.graph.get_formula_id(vid)?;
2347 self.graph
2348 .data_store()
2349 .retrieve_ast(ast_id, self.graph.sheet_reg())
2350 }
2351
2352 pub fn edit_with_logger<T>(
2353 &mut self,
2354 log: &mut crate::engine::ChangeLog,
2355 f: impl FnOnce(&mut crate::engine::VertexEditor) -> T,
2356 ) -> T {
2357 let start_len = log.len();
2359
2360 struct ArrowSpillReader<'a> {
2363 sheets: &'a crate::arrow_store::SheetStore,
2364 }
2365 impl crate::engine::graph::editor::vertex_editor::SpillValueReader for ArrowSpillReader<'_> {
2366 fn read_cell_value(
2367 &self,
2368 sheet: &str,
2369 row: u32,
2370 col: u32,
2371 ) -> Option<formualizer_common::LiteralValue> {
2372 use formualizer_common::LiteralValue;
2373 let asheet = self.sheets.sheet(sheet)?;
2374 let r0 = row.saturating_sub(1) as usize;
2375 let c0 = col.saturating_sub(1) as usize;
2376 let v = asheet.get_cell_value(r0, c0);
2377 if matches!(v, LiteralValue::Empty) {
2378 None
2379 } else {
2380 Some(v)
2381 }
2382 }
2383 }
2384
2385 let ret = {
2386 let spill_reader = ArrowSpillReader {
2387 sheets: &self.arrow_sheets,
2388 };
2389 let mut editor = crate::engine::VertexEditor::with_logger_and_spill_reader(
2390 &mut self.graph,
2391 log,
2392 &spill_reader,
2393 );
2394 f(&mut editor)
2395 };
2396
2397 for ev in &log.events()[start_len..] {
2400 self.mirror_forward_change_to_arrow(ev);
2401 }
2402 for ev in &log.events()[start_len..] {
2403 self.record_formula_plane_change_for_event(ev);
2404 }
2405
2406 ret
2407 }
2408
2409 pub fn undo_logged(
2410 &mut self,
2411 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
2412 log: &mut crate::engine::ChangeLog,
2413 ) -> Result<(), crate::engine::EditorError> {
2414 let batch = undo.undo(&mut self.graph, log)?;
2415 for item in batch.iter().rev() {
2416 self.apply_inverse_row_visibility_event(&item.event);
2417 self.apply_inverse_staged_formula_event(&item.event);
2418 }
2419 self.mirror_undo_batch_to_arrow(&batch);
2420 if !batch.is_empty() {
2421 for item in &batch {
2422 self.record_formula_plane_change_for_event(&item.event);
2423 }
2424 }
2425 Ok(())
2426 }
2427
2428 pub fn redo_logged(
2429 &mut self,
2430 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
2431 log: &mut crate::engine::ChangeLog,
2432 ) -> Result<(), crate::engine::EditorError> {
2433 let batch = undo.redo(&mut self.graph, log)?;
2434 for item in &batch {
2435 self.apply_forward_row_visibility_event(&item.event);
2436 self.apply_forward_staged_formula_event(&item.event);
2437 }
2438 self.mirror_redo_batch_to_arrow(&batch);
2439 if !batch.is_empty() {
2440 for item in &batch {
2441 self.record_formula_plane_change_for_event(&item.event);
2442 }
2443 }
2444 Ok(())
2445 }
2446
2447 pub fn undo_action(
2451 &mut self,
2452 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
2453 ) -> Result<(), crate::engine::EditorError> {
2454 let Some(journal) = undo.pop_undo_action() else {
2455 return Ok(());
2456 };
2457
2458 journal.graph.undo(&mut self.graph)?;
2459 self.apply_inverse_row_visibility_events(&journal.graph.events);
2460 self.apply_arrow_undo_batch(&journal.arrow, true);
2461 if !journal.graph.is_empty() || !journal.arrow.is_empty() {
2462 for event in &journal.graph.events {
2463 self.record_formula_plane_change_for_event(event);
2464 }
2465 self.mark_data_edited();
2466 }
2467
2468 undo.push_redo_action(journal);
2469 Ok(())
2470 }
2471
2472 pub fn redo_action(
2476 &mut self,
2477 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
2478 ) -> Result<(), crate::engine::EditorError> {
2479 let Some(journal) = undo.pop_redo_action() else {
2480 return Ok(());
2481 };
2482
2483 journal.graph.redo(&mut self.graph)?;
2484 self.apply_forward_row_visibility_events(&journal.graph.events);
2485 self.apply_arrow_undo_batch(&journal.arrow, false);
2486 if !journal.graph.is_empty() || !journal.arrow.is_empty() {
2487 for event in &journal.graph.events {
2488 self.record_formula_plane_change_for_event(event);
2489 }
2490 self.mark_data_edited();
2491 }
2492
2493 undo.push_done_action(journal);
2494 Ok(())
2495 }
2496
2497 fn cellref_to_sheet_row_col(&self, addr: &crate::reference::CellRef) -> (String, u32, u32) {
2498 let sheet = self.graph.sheet_name(addr.sheet_id).to_string();
2499 let row = addr.coord.row() + 1;
2501 let col = addr.coord.col() + 1;
2502 (sheet, row, col)
2503 }
2504
2505 fn mirror_undo_batch_to_arrow(
2506 &mut self,
2507 batch: &[crate::engine::graph::editor::undo_engine::UndoBatchItem],
2508 ) {
2509 for item in batch.iter().rev() {
2511 self.mirror_inverse_change_to_arrow(&item.event);
2512 }
2513 }
2514
2515 fn mirror_redo_batch_to_arrow(
2516 &mut self,
2517 batch: &[crate::engine::graph::editor::undo_engine::UndoBatchItem],
2518 ) {
2519 for item in batch.iter() {
2521 self.mirror_forward_change_to_arrow(&item.event);
2522 }
2523 }
2524
2525 fn mirror_inverse_change_to_arrow(&mut self, ev: &crate::engine::ChangeEvent) {
2526 use crate::engine::ChangeEvent;
2527 use formualizer_common::LiteralValue;
2528
2529 match ev {
2530 ChangeEvent::SetValue {
2531 addr,
2532 old_value,
2533 old_formula,
2534 ..
2535 } => {
2536 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
2537 if old_formula.is_some() {
2538 self.clear_delta_overlay_cell(&sheet, row, col);
2539 } else {
2540 let v = old_value.clone().unwrap_or(LiteralValue::Empty);
2541 self.mirror_value_to_overlay(&sheet, row, col, &v);
2542 }
2543 }
2544 ChangeEvent::SetFormula {
2545 addr,
2546 old_value,
2547 old_formula,
2548 ..
2549 } => {
2550 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
2551 if old_formula.is_some() {
2552 self.clear_delta_overlay_cell(&sheet, row, col);
2553 } else {
2554 let v = old_value.clone().unwrap_or(LiteralValue::Empty);
2555 self.mirror_value_to_overlay(&sheet, row, col, &v);
2556 }
2557 }
2558 ChangeEvent::SpillCommitted { old, new, .. } => {
2559 self.mirror_spill_snapshot(new, true);
2561 if let Some(snap) = old {
2562 self.mirror_spill_snapshot(snap, false);
2563 }
2564 }
2565 ChangeEvent::SpillCleared { old, .. } => {
2566 self.mirror_spill_snapshot(old, false);
2568 }
2569 ChangeEvent::SetRowVisibility { .. } => {
2570 }
2572 _ => {}
2573 }
2574 }
2575
2576 fn mirror_forward_change_to_arrow(&mut self, ev: &crate::engine::ChangeEvent) {
2577 use crate::engine::ChangeEvent;
2578
2579 match ev {
2580 ChangeEvent::SetValue { addr, new, .. } => {
2581 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
2582 self.mirror_value_to_overlay(&sheet, row, col, new);
2583 }
2584 ChangeEvent::SetFormula { addr, .. } => {
2585 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
2586 self.clear_delta_overlay_cell(&sheet, row, col);
2587 }
2589 ChangeEvent::SpillCommitted { old, new, .. } => {
2590 if let Some(snap) = old {
2591 self.mirror_spill_snapshot(snap, true);
2592 }
2593 self.mirror_spill_snapshot(new, false);
2594 }
2595 ChangeEvent::SpillCleared { old, .. } => {
2596 self.mirror_spill_snapshot(old, true);
2597 }
2598 ChangeEvent::SetRowVisibility { .. } => {
2599 }
2601 _ => {
2602 }
2604 }
2605 }
2606
2607 fn mirror_spill_snapshot(
2608 &mut self,
2609 snap: &crate::engine::graph::editor::change_log::SpillSnapshot,
2610 clear_only: bool,
2611 ) {
2612 use formualizer_common::LiteralValue;
2613
2614 let mut i = 0usize;
2615 for row in &snap.values {
2616 for v in row {
2617 if let Some(cell) = snap.target_cells.get(i) {
2618 let (sheet, r, c) = self.cellref_to_sheet_row_col(cell);
2619 let out = if clear_only {
2620 LiteralValue::Empty
2621 } else {
2622 v.clone()
2623 };
2624 self.mirror_value_to_computed_overlay(&sheet, r, c, &out);
2625 }
2626 i += 1;
2627 }
2628 }
2629 if clear_only {
2631 for cell in snap.target_cells.iter().skip(i) {
2632 let (sheet, r, c) = self.cellref_to_sheet_row_col(cell);
2633 self.mirror_value_to_computed_overlay(&sheet, r, c, &LiteralValue::Empty);
2634 }
2635 }
2636 }
2637
2638 pub fn set_default_sheet_by_name(&mut self, name: &str) {
2639 self.graph.set_default_sheet_by_name(name);
2640 }
2641
2642 pub fn set_default_sheet_by_id(&mut self, id: SheetId) {
2643 self.graph.set_default_sheet_by_id(id);
2644 }
2645
2646 pub fn set_sheet_index_mode(&mut self, mode: crate::engine::SheetIndexMode) {
2647 self.graph.set_sheet_index_mode(mode);
2648 }
2649
2650 fn clear_cached_static_schedule(&mut self) {
2651 self.cached_static_schedule = None;
2652 }
2653
2654 pub fn mark_data_edited(&mut self) {
2657 self.snapshot_id
2658 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2659 self.has_edited = true;
2660 }
2661
2662 pub fn mark_topology_edited(&mut self) {
2664 self.snapshot_id
2665 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2666 self.topology_epoch = self.topology_epoch.wrapping_add(1);
2667 self.clear_cached_static_schedule();
2668 self.has_edited = true;
2669 }
2670
2671 fn mark_all_formula_vertices_dirty(&mut self) {
2672 let vertices: Vec<VertexId> = self.graph.vertices_with_formulas().collect();
2673 for vertex in vertices {
2674 self.graph.mark_vertex_dirty(vertex);
2675 }
2676 }
2677
2678 fn mark_moved_formula_vertices_dirty(
2679 &mut self,
2680 summary: &crate::engine::graph::editor::vertex_editor::ShiftSummary,
2681 ) {
2682 for vertex in &summary.vertices_moved {
2683 if self.graph.get_formula_id(*vertex).is_some() {
2684 self.graph.mark_vertex_dirty(*vertex);
2685 }
2686 }
2687 }
2688
2689 pub fn sheet_store(&self) -> &SheetStore {
2691 &self.arrow_sheets
2692 }
2693
2694 pub fn sheet_store_mut(&mut self) -> &mut SheetStore {
2696 &mut self.arrow_sheets
2697 }
2698
2699 pub fn has_staged_formulas(&self) -> bool {
2700 !self.staged_formulas.is_empty()
2701 }
2702
2703 pub fn staged_formula_count(&self) -> usize {
2704 self.staged_formulas.values().map(Vec::len).sum()
2705 }
2706
2707 pub fn staged_formula_state_snapshot(&self) -> Vec<(String, u32, u32, String)> {
2708 let mut snapshot = Vec::new();
2709 for (sheet, entries) in &self.staged_formulas {
2710 for (row, col, text) in entries {
2711 snapshot.push((sheet.clone(), *row, *col, text.clone()));
2712 }
2713 }
2714 snapshot.sort_by(|a, b| {
2715 a.0.cmp(&b.0)
2716 .then(a.1.cmp(&b.1))
2717 .then(a.2.cmp(&b.2))
2718 .then(a.3.cmp(&b.3))
2719 });
2720 snapshot
2721 }
2722
2723 pub fn restore_staged_formula_state(&mut self, snapshot: &[(String, u32, u32, String)]) {
2724 self.staged_formulas.clear();
2725 for (sheet, row, col, text) in snapshot {
2726 self.stage_formula_text(sheet, *row, *col, text.clone());
2727 }
2728 }
2729
2730 pub fn stage_formula_text(&mut self, sheet: &str, row: u32, col: u32, text: String) {
2732 let entries = self.staged_formulas.entry(sheet.to_string()).or_default();
2733 if let Some((_, _, existing)) = entries
2734 .iter_mut()
2735 .find(|(existing_row, existing_col, _)| *existing_row == row && *existing_col == col)
2736 {
2737 *existing = text;
2738 } else {
2739 entries.push((row, col, text));
2740 }
2741 }
2742
2743 pub fn clear_staged_formula_text(&mut self, sheet: &str, row: u32, col: u32) -> Option<String> {
2744 let mut removed = None;
2745 let mut remove_sheet = false;
2746 if let Some(entries) = self.staged_formulas.get_mut(sheet) {
2747 if let Some(idx) = entries.iter().position(|(existing_row, existing_col, _)| {
2748 *existing_row == row && *existing_col == col
2749 }) {
2750 let (_, _, text) = entries.remove(idx);
2751 removed = Some(text);
2752 }
2753 remove_sheet = entries.is_empty();
2754 }
2755 if remove_sheet {
2756 self.staged_formulas.remove(sheet);
2757 }
2758 removed
2759 }
2760
2761 pub fn clear_staged_formulas_for_sheet(&mut self, sheet: &str) {
2762 self.staged_formulas.remove(sheet);
2763 }
2764
2765 pub fn rename_staged_formula_sheet(&mut self, old: &str, new: &str) {
2766 let Some(entries) = self.staged_formulas.remove(old) else {
2767 return;
2768 };
2769 for (row, col, text) in entries {
2770 self.stage_formula_text(new, row, col, text);
2771 }
2772 }
2773
2774 pub fn get_staged_formula_text(&self, sheet: &str, row: u32, col: u32) -> Option<String> {
2776 self.staged_formulas.get(sheet).and_then(|v| {
2777 v.iter()
2778 .rev()
2779 .find(|(r, c, _)| *r == row && *c == col)
2780 .map(|(_, _, s)| s.clone())
2781 })
2782 }
2783
2784 pub fn formula_parse_diagnostics(&self) -> &[FormulaParseDiagnostic] {
2785 &self.formula_parse_diagnostics
2786 }
2787
2788 pub fn take_formula_parse_diagnostics(&mut self) -> Vec<FormulaParseDiagnostic> {
2789 std::mem::take(&mut self.formula_parse_diagnostics)
2790 }
2791
2792 pub fn clear_formula_parse_diagnostics(&mut self) {
2793 self.formula_parse_diagnostics.clear();
2794 }
2795
2796 pub fn last_formula_ingest_report(&self) -> Option<&FormulaIngestReport> {
2797 self.last_formula_ingest_report.as_ref()
2798 }
2799
2800 pub fn formula_ingest_report_total(&self) -> &FormulaIngestReport {
2801 &self.formula_ingest_report_total
2802 }
2803
2804 #[cfg(test)]
2805 pub(crate) fn last_formula_plane_span_eval_report(&self) -> Option<&SpanEvalReport> {
2806 self.last_formula_plane_span_eval_report.as_ref()
2807 }
2808
2809 #[cfg(test)]
2810 pub(crate) fn formula_plane_indexes_epoch(&self) -> u64 {
2811 self.graph.formula_authority().indexes_epoch()
2812 }
2813
2814 fn record_formula_ingest_report(&mut self, report: FormulaIngestReport) {
2815 self.formula_ingest_report_total.mode = report.mode;
2816 self.formula_ingest_report_total.accumulate(&report);
2817 self.last_formula_ingest_report = Some(report);
2818 }
2819
2820 fn analyze_formula_plane_shadow_candidates(
2821 &mut self,
2822 batches: &[FormulaIngestBatch],
2823 ) -> FormulaIngestReport {
2824 let mut report = FormulaIngestReport::with_mode(FormulaPlaneMode::Shadow);
2825 report.formula_cells_seen = batches.iter().map(|batch| batch.len() as u64).sum();
2826
2827 let _active_epoch = self.graph.formula_authority().plane.epoch();
2830
2831 let batch_sheet_ids: Vec<SheetId> = batches
2832 .iter()
2833 .map(|batch| self.graph.sheet_id_mut(&batch.sheet_name))
2834 .collect();
2835 let mut groups: BTreeMap<
2836 (SheetId, u64, u32),
2837 Vec<(FormulaPlacementCandidate, CandidateAnalysis)>,
2838 > = BTreeMap::new();
2839 {
2840 let mut pipeline = self.ingest_pipeline();
2841 for (batch, sheet_id) in batches.iter().zip(batch_sheet_ids.iter().copied()) {
2842 for record in &batch.formulas {
2843 if record.row == 0 || record.col == 0 {
2844 report.shadow_candidate_cells =
2845 report.shadow_candidate_cells.saturating_add(1);
2846 report.shadow_fallback_cells =
2847 report.shadow_fallback_cells.saturating_add(1);
2848 Self::record_shadow_fallback_reason(
2849 &mut report,
2850 PlacementFallbackReason::UnsupportedShapeOrGaps,
2851 1,
2852 );
2853 continue;
2854 }
2855
2856 let placement = CellRef::new(
2857 sheet_id,
2858 Coord::from_excel(record.row, record.col, true, true),
2859 );
2860 let ingested = match pipeline.ingest_formula(
2861 FormulaAstInput::RawArena(record.ast_id),
2862 placement,
2863 record.formula_text.clone(),
2864 ) {
2865 Ok(ingested) => ingested,
2866 Err(_) => {
2867 report.shadow_candidate_cells =
2868 report.shadow_candidate_cells.saturating_add(1);
2869 report.shadow_fallback_cells =
2870 report.shadow_fallback_cells.saturating_add(1);
2871 Self::record_shadow_fallback_reason(
2872 &mut report,
2873 PlacementFallbackReason::UnsupportedCanonicalTemplate,
2874 1,
2875 );
2876 continue;
2877 }
2878 };
2879 let candidate = FormulaPlacementCandidate::new(
2880 sheet_id,
2881 record.row - 1,
2882 record.col - 1,
2883 ingested.ast_id,
2884 record.formula_text.clone(),
2885 );
2886 let analysis = match CandidateAnalysis::from_ingested(&candidate, &ingested) {
2887 Ok(analysis) => analysis,
2888 Err(reason) => {
2889 report.shadow_candidate_cells =
2890 report.shadow_candidate_cells.saturating_add(1);
2891 report.shadow_fallback_cells =
2892 report.shadow_fallback_cells.saturating_add(1);
2893 Self::record_shadow_fallback_reason(&mut report, reason, 1);
2894 continue;
2895 }
2896 };
2897 groups
2898 .entry((
2899 sheet_id,
2900 ingested.parameterized_canonical_hash,
2901 candidate.col,
2902 ))
2903 .or_default()
2904 .push((candidate, analysis));
2905 }
2906 }
2907 }
2908
2909 let mut scratch_plane = FormulaPlane::default();
2910 for entries in groups.into_values() {
2911 let (candidates, analyses): (Vec<_>, Vec<_>) = entries.into_iter().unzip();
2912 for (component, component_analyses) in
2913 Self::split_candidate_components_with_analyses(candidates, analyses)
2914 {
2915 let placement_report = place_candidate_family_with_analyses(
2916 &mut scratch_plane,
2917 component,
2918 component_analyses,
2919 );
2920 let counters = placement_report.counters;
2921 report.shadow_candidate_cells = report
2922 .shadow_candidate_cells
2923 .saturating_add(counters.formula_cells_seen);
2924 report.shadow_accepted_span_cells = report
2925 .shadow_accepted_span_cells
2926 .saturating_add(counters.accepted_span_cells);
2927 report.shadow_fallback_cells = report
2928 .shadow_fallback_cells
2929 .saturating_add(counters.legacy_cells);
2930 report.shadow_templates_interned = report
2931 .shadow_templates_interned
2932 .saturating_add(counters.templates_interned);
2933 report.shadow_spans_created = report
2934 .shadow_spans_created
2935 .saturating_add(counters.spans_created);
2936 report.graph_formula_vertices_avoided_shadow = report
2937 .graph_formula_vertices_avoided_shadow
2938 .saturating_add(counters.formula_vertices_avoided);
2939 report.ast_roots_avoided_shadow = report
2940 .ast_roots_avoided_shadow
2941 .saturating_add(counters.ast_roots_avoided);
2942 report.edge_rows_avoided_shadow = report
2943 .edge_rows_avoided_shadow
2944 .saturating_add(counters.edge_rows_avoided);
2945 for (reason, count) in counters.fallback_reasons {
2946 Self::record_shadow_fallback_reason(&mut report, reason, count);
2947 }
2948 }
2949 }
2950 report
2951 }
2952
2953 fn record_shadow_fallback_reason(
2954 report: &mut FormulaIngestReport,
2955 reason: PlacementFallbackReason,
2956 count: u64,
2957 ) {
2958 *report
2959 .fallback_reasons
2960 .entry(format!("{reason:?}"))
2961 .or_default() += count;
2962 }
2963
2964 fn analyze_formula_plane_authoritative_ingest(
2965 &mut self,
2966 batches: &[FormulaIngestBatch],
2967 ) -> (
2968 FormulaIngestReport,
2969 Vec<FormulaIngestBatch>,
2970 PlannedFormulaMaterialize,
2971 ) {
2972 let mut report =
2973 FormulaIngestReport::with_mode(FormulaPlaneMode::AuthoritativeExperimental);
2974 report.formula_cells_seen = batches.iter().map(|batch| batch.len() as u64).sum();
2975
2976 let mut pending_candidates: Vec<(String, FormulaPlacementCandidate)> = Vec::new();
2977 let mut fallback: BTreeMap<String, Vec<FormulaIngestRecord>> = BTreeMap::new();
2978 let mut planned_fallback: PlannedFormulaMaterialize = BTreeMap::new();
2979
2980 for batch in batches {
2981 let sheet_id = self.graph.sheet_id_mut(&batch.sheet_name);
2982 for record in &batch.formulas {
2983 if record.row == 0 || record.col == 0 {
2984 report.shadow_candidate_cells = report.shadow_candidate_cells.saturating_add(1);
2985 report.shadow_fallback_cells = report.shadow_fallback_cells.saturating_add(1);
2986 Self::record_shadow_fallback_reason(
2987 &mut report,
2988 PlacementFallbackReason::UnsupportedShapeOrGaps,
2989 1,
2990 );
2991 fallback
2992 .entry(batch.sheet_name.clone())
2993 .or_default()
2994 .push(record.clone());
2995 continue;
2996 }
2997
2998 pending_candidates.push((
2999 batch.sheet_name.clone(),
3000 FormulaPlacementCandidate::new(
3001 sheet_id,
3002 record.row - 1,
3003 record.col - 1,
3004 record.ast_id,
3005 record.formula_text.clone(),
3006 ),
3007 ));
3008 }
3009 }
3010
3011 let mut groups: BTreeMap<(SheetId, u64, u32), Vec<usize>> = BTreeMap::new();
3012 let mut analyses_by_index: Vec<Option<CandidateAnalysis>> =
3013 (0..pending_candidates.len()).map(|_| None).collect();
3014 let mut plans_by_index: Vec<Option<DependencyPlanRow>> =
3015 (0..pending_candidates.len()).map(|_| None).collect();
3016 {
3017 let mut pipeline = self.ingest_pipeline();
3018 for (idx, (sheet_name, candidate)) in pending_candidates.iter_mut().enumerate() {
3019 let placement = CellRef::new(
3020 candidate.sheet_id,
3021 Coord::from_excel(
3022 candidate.row.saturating_add(1),
3023 candidate.col.saturating_add(1),
3024 true,
3025 true,
3026 ),
3027 );
3028 let ingested = pipeline.ingest_formula(
3029 FormulaAstInput::RawArena(candidate.ast_id),
3030 placement,
3031 candidate.formula_text.clone(),
3032 );
3033 match ingested {
3034 Ok(ingested) => {
3035 candidate.ast_id = ingested.ast_id;
3036 let canonical_hash = ingested.parameterized_canonical_hash;
3037 let dep_plan = ingested.dep_plan.clone();
3038 match CandidateAnalysis::from_ingested(candidate, &ingested) {
3039 Ok(analysis) => {
3040 groups
3041 .entry((candidate.sheet_id, canonical_hash, candidate.col))
3042 .or_default()
3043 .push(idx);
3044 analyses_by_index[idx] = Some(analysis);
3045 plans_by_index[idx] = Some(dep_plan);
3046 }
3047 Err(reason) => {
3048 report.shadow_candidate_cells =
3049 report.shadow_candidate_cells.saturating_add(1);
3050 report.shadow_fallback_cells =
3051 report.shadow_fallback_cells.saturating_add(1);
3052 Self::record_shadow_fallback_reason(&mut report, reason, 1);
3053 planned_fallback
3054 .entry(sheet_name.clone())
3055 .or_default()
3056 .push((
3057 candidate.row.saturating_add(1),
3058 candidate.col.saturating_add(1),
3059 candidate.ast_id,
3060 dep_plan,
3061 ));
3062 }
3063 }
3064 }
3065 Err(_) => {
3066 report.shadow_candidate_cells =
3067 report.shadow_candidate_cells.saturating_add(1);
3068 report.shadow_fallback_cells =
3069 report.shadow_fallback_cells.saturating_add(1);
3070 Self::record_shadow_fallback_reason(
3071 &mut report,
3072 PlacementFallbackReason::UnsupportedCanonicalTemplate,
3073 1,
3074 );
3075 fallback.entry(sheet_name.clone()).or_default().push(
3076 FormulaIngestRecord::new(
3077 candidate.row.saturating_add(1),
3078 candidate.col.saturating_add(1),
3079 candidate.ast_id,
3080 candidate.formula_text.clone(),
3081 ),
3082 );
3083 }
3084 }
3085 }
3086 }
3087
3088 for ((_sheet_id, _canonical_hash, _col), candidate_indices) in groups {
3089 let sheet_name = pending_candidates[candidate_indices[0]].0.clone();
3090 let mut plans_by_coord: BTreeMap<(u32, u32), Vec<DependencyPlanRow>> = BTreeMap::new();
3091 for idx in &candidate_indices {
3092 if let Some(plan) = plans_by_index[*idx].clone() {
3093 let candidate = &pending_candidates[*idx].1;
3094 plans_by_coord
3095 .entry((candidate.row, candidate.col))
3096 .or_default()
3097 .push(plan);
3098 }
3099 }
3100 let candidates: Vec<_> = candidate_indices
3101 .iter()
3102 .map(|idx| pending_candidates[*idx].1.clone())
3103 .collect();
3104 let components = Self::split_shadow_candidate_components(candidates);
3105 let analyzed_components =
3106 if components.len() == 1 && components[0].len() == candidate_indices.len() {
3107 let component = components.into_iter().next().expect("one component");
3108 let component_analyses = candidate_indices
3109 .iter()
3110 .map(|idx| {
3111 analyses_by_index[*idx]
3112 .take()
3113 .expect("candidate analysis must be used once")
3114 })
3115 .collect();
3116 vec![(component, component_analyses)]
3117 } else {
3118 let mut indices_by_coord: BTreeMap<(u32, u32), Vec<usize>> = BTreeMap::new();
3119 for idx in candidate_indices.iter().rev() {
3120 let candidate = &pending_candidates[*idx].1;
3121 indices_by_coord
3122 .entry((candidate.row, candidate.col))
3123 .or_default()
3124 .push(*idx);
3125 }
3126
3127 components
3128 .into_iter()
3129 .map(|component| {
3130 let mut component_analyses = Vec::with_capacity(component.len());
3131 for candidate in &component {
3132 let idx = indices_by_coord
3133 .get_mut(&(candidate.row, candidate.col))
3134 .and_then(Vec::pop)
3135 .expect("component candidate must have a precomputed analysis");
3136 component_analyses.push(
3137 analyses_by_index[idx]
3138 .take()
3139 .expect("candidate analysis must be used once"),
3140 );
3141 }
3142 (component, component_analyses)
3143 })
3144 .collect()
3145 };
3146
3147 for (component, component_analyses) in analyzed_components {
3148 for (component, component_analyses) in
3149 split_candidate_affine_literal_runs(component, component_analyses)
3150 {
3151 let placement_report = {
3152 let authority = self.graph.formula_authority_mut();
3153 place_candidate_family_with_analyses(
3154 &mut authority.plane,
3155 component.clone(),
3156 component_analyses,
3157 )
3158 };
3159 Self::accumulate_formula_plane_placement_report(&mut report, &placement_report);
3160
3161 for result in &placement_report.results {
3162 let FormulaPlacementResult::Legacy { placement, .. } = result else {
3163 continue;
3164 };
3165 if let Some(candidate) = component
3166 .iter()
3167 .find(|candidate| candidate.placement() == *placement)
3168 {
3169 let plan = plans_by_coord
3170 .get_mut(&(candidate.row, candidate.col))
3171 .and_then(Vec::pop);
3172 if let Some(plan) = plan {
3173 planned_fallback
3174 .entry(sheet_name.clone())
3175 .or_default()
3176 .push((
3177 candidate.row.saturating_add(1),
3178 candidate.col.saturating_add(1),
3179 candidate.ast_id,
3180 plan,
3181 ));
3182 } else {
3183 fallback.entry(sheet_name.clone()).or_default().push(
3184 FormulaIngestRecord::new(
3185 candidate.row.saturating_add(1),
3186 candidate.col.saturating_add(1),
3187 candidate.ast_id,
3188 candidate.formula_text.clone(),
3189 ),
3190 );
3191 }
3192 }
3193 }
3194 }
3195 }
3196 }
3197
3198 let _index_report = self.graph.formula_authority_mut().rebuild_indexes();
3199
3200 let fallback_batches = fallback
3201 .into_iter()
3202 .map(|(sheet_name, formulas)| FormulaIngestBatch::new(sheet_name, formulas))
3203 .collect();
3204 (report, fallback_batches, planned_fallback)
3205 }
3206
3207 fn accumulate_formula_plane_placement_report(
3208 report: &mut FormulaIngestReport,
3209 placement_report: &crate::formula_plane::placement::FormulaPlacementReport,
3210 ) {
3211 let counters = &placement_report.counters;
3212 report.shadow_candidate_cells = report
3213 .shadow_candidate_cells
3214 .saturating_add(counters.formula_cells_seen);
3215 report.shadow_accepted_span_cells = report
3216 .shadow_accepted_span_cells
3217 .saturating_add(counters.accepted_span_cells);
3218 report.shadow_fallback_cells = report
3219 .shadow_fallback_cells
3220 .saturating_add(counters.legacy_cells);
3221 report.shadow_templates_interned = report
3222 .shadow_templates_interned
3223 .saturating_add(counters.templates_interned);
3224 report.shadow_spans_created = report
3225 .shadow_spans_created
3226 .saturating_add(counters.spans_created);
3227 report.graph_formula_vertices_avoided_shadow = report
3228 .graph_formula_vertices_avoided_shadow
3229 .saturating_add(counters.formula_vertices_avoided);
3230 report.ast_roots_avoided_shadow = report
3231 .ast_roots_avoided_shadow
3232 .saturating_add(counters.ast_roots_avoided);
3233 report.edge_rows_avoided_shadow = report
3234 .edge_rows_avoided_shadow
3235 .saturating_add(counters.edge_rows_avoided);
3236 for (reason, count) in &counters.fallback_reasons {
3237 Self::record_shadow_fallback_reason(report, *reason, *count);
3238 }
3239 }
3240
3241 fn split_candidate_components_with_analyses(
3242 candidates: Vec<FormulaPlacementCandidate>,
3243 mut analyses: Vec<CandidateAnalysis>,
3244 ) -> Vec<(Vec<FormulaPlacementCandidate>, Vec<CandidateAnalysis>)> {
3245 let components = Self::split_shadow_candidate_components(candidates.clone());
3246 let mut analysis_by_coord: BTreeMap<(u32, u32), Vec<CandidateAnalysis>> = BTreeMap::new();
3247 for (candidate, analysis) in candidates.into_iter().zip(analyses.drain(..)) {
3248 analysis_by_coord
3249 .entry((candidate.row, candidate.col))
3250 .or_default()
3251 .push(analysis);
3252 }
3253 components
3254 .into_iter()
3255 .flat_map(|component| {
3256 let mut component_analyses = Vec::with_capacity(component.len());
3257 for candidate in &component {
3258 let analysis = analysis_by_coord
3259 .get_mut(&(candidate.row, candidate.col))
3260 .and_then(Vec::pop)
3261 .expect("component candidate must have a precomputed analysis");
3262 component_analyses.push(analysis);
3263 }
3264 split_candidate_affine_literal_runs(component, component_analyses)
3265 })
3266 .collect()
3267 }
3268
3269 fn split_shadow_candidate_components(
3270 candidates: Vec<FormulaPlacementCandidate>,
3271 ) -> Vec<Vec<FormulaPlacementCandidate>> {
3272 if candidates.len() <= 1 {
3273 return vec![candidates];
3274 }
3275
3276 let mut coord_to_indices: BTreeMap<(u32, u32), Vec<usize>> = BTreeMap::new();
3277 for (idx, candidate) in candidates.iter().enumerate() {
3278 coord_to_indices
3279 .entry((candidate.row, candidate.col))
3280 .or_default()
3281 .push(idx);
3282 }
3283
3284 let mut remaining: BTreeSet<usize> = (0..candidates.len()).collect();
3285 let mut components = Vec::new();
3286 while let Some(&start) = remaining.iter().next() {
3287 remaining.remove(&start);
3288 let mut queue = VecDeque::from([start]);
3289 let mut component_indices = Vec::new();
3290
3291 while let Some(idx) = queue.pop_front() {
3292 component_indices.push(idx);
3293 let candidate = &candidates[idx];
3294 let mut neighbor_coords = Vec::with_capacity(5);
3295 neighbor_coords.push((candidate.row, candidate.col));
3296 if let Some(row) = candidate.row.checked_sub(1) {
3297 neighbor_coords.push((row, candidate.col));
3298 }
3299 neighbor_coords.push((candidate.row.saturating_add(1), candidate.col));
3300 if let Some(col) = candidate.col.checked_sub(1) {
3301 neighbor_coords.push((candidate.row, col));
3302 }
3303 neighbor_coords.push((candidate.row, candidate.col.saturating_add(1)));
3304
3305 for coord in neighbor_coords {
3306 if let Some(indices) = coord_to_indices.get(&coord) {
3307 for &neighbor in indices {
3308 if remaining.remove(&neighbor) {
3309 queue.push_back(neighbor);
3310 }
3311 }
3312 }
3313 }
3314 }
3315
3316 component_indices.sort_by_key(|idx| {
3317 let candidate = &candidates[*idx];
3318 (candidate.row, candidate.col, *idx)
3319 });
3320 components.push(
3321 component_indices
3322 .into_iter()
3323 .map(|idx| candidates[idx].clone())
3324 .collect(),
3325 );
3326 }
3327
3328 components
3329 }
3330
3331 pub fn ingest_formula_batches(
3332 &mut self,
3333 batches: Vec<FormulaIngestBatch>,
3334 ) -> Result<FormulaIngestReport, ExcelError> {
3335 let formula_cells_seen = batches.iter().map(|batch| batch.len() as u64).sum();
3336 let (mut report, materialize_batches, planned_materialize) =
3337 match self.config.formula_plane_mode {
3338 FormulaPlaneMode::Off => (
3339 FormulaIngestReport::with_mode(FormulaPlaneMode::Off),
3340 batches,
3341 BTreeMap::new(),
3342 ),
3343 FormulaPlaneMode::Shadow => (
3344 self.analyze_formula_plane_shadow_candidates(&batches),
3345 batches,
3346 BTreeMap::new(),
3347 ),
3348 FormulaPlaneMode::AuthoritativeExperimental => {
3349 self.analyze_formula_plane_authoritative_ingest(&batches)
3350 }
3351 };
3352 report.formula_cells_seen = formula_cells_seen;
3353
3354 if !materialize_batches.iter().all(FormulaIngestBatch::is_empty)
3355 || !planned_materialize.is_empty()
3356 {
3357 let mut builder = self.begin_bulk_ingest();
3358 for batch in materialize_batches {
3359 if batch.is_empty() {
3360 continue;
3361 }
3362 let sheet_id = builder.add_sheet(&batch.sheet_name);
3363 builder.add_formula_ids(
3364 sheet_id,
3365 batch
3366 .formulas
3367 .into_iter()
3368 .map(|record| (record.row, record.col, record.ast_id)),
3369 );
3370 }
3371 for (sheet_name, formulas) in planned_materialize {
3372 if formulas.is_empty() {
3373 continue;
3374 }
3375 let sheet_id = builder.add_sheet(&sheet_name);
3376 builder.add_formula_plans(sheet_id, formulas);
3377 }
3378 let summary = builder.finish()?;
3379 report.graph_formula_cells_materialized = summary.formulas as u64;
3380 report.graph_vertices_created = summary.vertices as u64;
3381 report.graph_edges_created = summary.edges as u64;
3382 }
3383
3384 self.record_formula_ingest_report(report.clone());
3385 Ok(report)
3386 }
3387
3388 pub fn handle_formula_parse_error(
3389 &mut self,
3390 sheet: &str,
3391 row: u32,
3392 col: u32,
3393 formula: &str,
3394 message: String,
3395 ) -> Result<Option<ASTNode>, ExcelError> {
3396 let policy = self.config.formula_parse_policy;
3397
3398 if policy == FormulaParsePolicy::Strict {
3399 let col_a1 = col_letters_from_1based(col).unwrap_or_else(|_| "?".to_string());
3400 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(format!(
3401 "Formula parse error at {sheet}!{col_a1}{row}: {message}"
3402 )));
3403 }
3404
3405 self.formula_parse_diagnostics.push(FormulaParseDiagnostic {
3406 sheet: sheet.to_string(),
3407 row,
3408 col,
3409 formula: formula.to_string(),
3410 message: message.clone(),
3411 policy,
3412 });
3413
3414 match policy {
3415 FormulaParsePolicy::Strict => unreachable!(),
3416 FormulaParsePolicy::KeepCachedValue => Ok(None),
3417 FormulaParsePolicy::AsText => Ok(Some(ASTNode::new(
3418 ASTNodeType::Literal(LiteralValue::Text(formula.to_string())),
3419 None,
3420 ))),
3421 FormulaParsePolicy::CoerceToError => {
3422 let err = ExcelError::new(ExcelErrorKind::Error)
3423 .with_message(format!("Malformed formula: {message}"));
3424 Ok(Some(ASTNode::new(
3425 ASTNodeType::Literal(LiteralValue::Error(err)),
3426 None,
3427 )))
3428 }
3429 }
3430 }
3431
3432 pub fn build_graph_all(&mut self) -> Result<(), formualizer_parse::ExcelError> {
3434 if self.staged_formulas.is_empty() {
3435 return Ok(());
3436 }
3437 let staged = std::mem::take(&mut self.staged_formulas);
3439 for sheet in staged.keys() {
3440 let _ = self.add_sheet(sheet);
3441 }
3442
3443 let mut prepared: PreparedFormulaBatches = Vec::new();
3445 for (sheet, entries) in staged {
3446 let mut formulas: Vec<FormulaIngestRecord> = Vec::new();
3447 let mut cache: rustc_hash::FxHashMap<String, Option<crate::engine::arena::AstNodeId>> =
3448 rustc_hash::FxHashMap::default();
3449 cache.reserve(4096);
3450
3451 for (row, col, txt) in entries {
3452 let key = if txt.starts_with('=') {
3453 txt
3454 } else {
3455 format!("={txt}")
3456 };
3457 let ast_id = if let Some(cached) = cache.get(&key) {
3458 *cached
3459 } else {
3460 let parsed = match formualizer_parse::parser::parse(&key) {
3461 Ok(parsed) => Some(parsed),
3462 Err(e) => {
3463 self.handle_formula_parse_error(&sheet, row, col, &key, e.to_string())?
3464 }
3465 };
3466 let ast_id = parsed.as_ref().map(|ast| self.intern_formula_ast(ast));
3467 cache.insert(key.clone(), ast_id);
3468 ast_id
3469 };
3470
3471 if let Some(ast_id) = ast_id {
3472 formulas.push(FormulaIngestRecord::new(
3473 row,
3474 col,
3475 ast_id,
3476 Some(Arc::<str>::from(key.clone())),
3477 ));
3478 }
3479 }
3480
3481 if !formulas.is_empty() {
3482 prepared.push(FormulaIngestBatch::new(sheet, formulas));
3483 }
3484 }
3485
3486 if !prepared.is_empty() {
3487 let _ = self.ingest_formula_batches(prepared)?;
3488 }
3489 Ok(())
3490 }
3491
3492 pub fn build_graph_for_sheets<'a, I: IntoIterator<Item = &'a str>>(
3494 &mut self,
3495 sheets: I,
3496 ) -> Result<(), formualizer_parse::ExcelError> {
3497 let mut collected: StagedFormulaBatches = Vec::new();
3498 for s in sheets {
3499 if let Some(entries) = self.staged_formulas.remove(s) {
3500 collected.push((s.to_string(), entries));
3501 }
3502 }
3503
3504 if collected.is_empty() {
3505 return Ok(());
3506 }
3507
3508 for (sheet, _) in &collected {
3509 let _ = self.add_sheet(sheet);
3510 }
3511
3512 let mut prepared: PreparedFormulaBatches = Vec::new();
3514 let mut cache: rustc_hash::FxHashMap<String, Option<crate::engine::arena::AstNodeId>> =
3515 rustc_hash::FxHashMap::default();
3516 cache.reserve(4096);
3517
3518 for (sheet, entries) in collected {
3519 let mut formulas: Vec<FormulaIngestRecord> = Vec::new();
3520 for (row, col, txt) in entries {
3521 let key = if txt.starts_with('=') {
3522 txt
3523 } else {
3524 format!("={txt}")
3525 };
3526 let ast_id = if let Some(cached) = cache.get(&key) {
3527 *cached
3528 } else {
3529 let parsed = match formualizer_parse::parser::parse(&key) {
3530 Ok(parsed) => Some(parsed),
3531 Err(e) => {
3532 self.handle_formula_parse_error(&sheet, row, col, &key, e.to_string())?
3533 }
3534 };
3535 let ast_id = parsed.as_ref().map(|ast| self.intern_formula_ast(ast));
3536 cache.insert(key.clone(), ast_id);
3537 ast_id
3538 };
3539
3540 if let Some(ast_id) = ast_id {
3541 formulas.push(FormulaIngestRecord::new(
3542 row,
3543 col,
3544 ast_id,
3545 Some(Arc::<str>::from(key.clone())),
3546 ));
3547 }
3548 }
3549 if !formulas.is_empty() {
3550 prepared.push(FormulaIngestBatch::new(sheet, formulas));
3551 }
3552 }
3553
3554 if !prepared.is_empty() {
3555 let _ = self.ingest_formula_batches(prepared)?;
3556 }
3557 Ok(())
3558 }
3559
3560 pub fn begin_bulk_ingest_arrow(
3562 &mut self,
3563 ) -> crate::engine::arrow_ingest::ArrowBulkIngestBuilder<'_, R> {
3564 crate::engine::arrow_ingest::ArrowBulkIngestBuilder::new(self)
3565 }
3566
3567 pub fn begin_bulk_update_arrow(
3569 &mut self,
3570 ) -> crate::engine::arrow_ingest::ArrowBulkUpdateBuilder<'_, R> {
3571 crate::engine::arrow_ingest::ArrowBulkUpdateBuilder::new(self)
3572 }
3573
3574 fn ensure_known_sheet_id(&self, sheet: &str) -> Result<SheetId, crate::engine::EditorError> {
3575 self.graph.sheet_id(sheet).ok_or(
3576 crate::engine::graph::editor::vertex_editor::EditorError::InvalidName {
3577 name: sheet.to_string(),
3578 reason: "Unknown sheet".to_string(),
3579 },
3580 )
3581 }
3582
3583 fn normalize_row_1based(row_1based: u32) -> Result<u32, crate::engine::EditorError> {
3584 if row_1based == 0 {
3585 return Err(crate::engine::EditorError::OutOfBounds { row: 0, col: 0 });
3586 }
3587 Ok(row_1based - 1)
3588 }
3589
3590 fn normalize_row_range_1based(
3591 start_row_1based: u32,
3592 end_row_1based: u32,
3593 ) -> Result<(u32, u32), crate::engine::EditorError> {
3594 if start_row_1based == 0 || end_row_1based == 0 {
3595 return Err(crate::engine::EditorError::OutOfBounds { row: 0, col: 0 });
3596 }
3597 if start_row_1based > end_row_1based {
3598 return Err(crate::engine::EditorError::TransactionFailed {
3599 reason: "Row range start is greater than end".to_string(),
3600 });
3601 }
3602 Ok((start_row_1based - 1, end_row_1based - 1))
3603 }
3604
3605 fn invalidate_row_visibility_mask_cache(&self) {
3606 if let Ok(mut cache) = self.row_visibility_mask_cache.write() {
3607 cache.clear();
3608 }
3609 }
3610
3611 fn set_row_hidden_by_sheet_id(
3612 &mut self,
3613 sheet_id: SheetId,
3614 row0: u32,
3615 hidden: bool,
3616 source: RowVisibilitySource,
3617 ) -> bool {
3618 let changed = {
3619 let state = self.row_visibility.entry(sheet_id).or_default();
3620 state.set_row_hidden(row0, hidden, source)
3621 };
3622
3623 let remove_entry = self
3624 .row_visibility
3625 .get(&sheet_id)
3626 .map(|state| state.is_empty())
3627 .unwrap_or(false);
3628 if remove_entry {
3629 self.row_visibility.remove(&sheet_id);
3630 }
3631
3632 if changed {
3633 self.invalidate_row_visibility_mask_cache();
3634 }
3635
3636 changed
3637 }
3638
3639 fn set_rows_hidden_by_sheet_id(
3640 &mut self,
3641 sheet_id: SheetId,
3642 start_row0: u32,
3643 end_row0: u32,
3644 hidden: bool,
3645 source: RowVisibilitySource,
3646 ) -> bool {
3647 let changed = {
3648 let state = self.row_visibility.entry(sheet_id).or_default();
3649 state.set_rows_hidden(start_row0, end_row0, hidden, source)
3650 };
3651
3652 let remove_entry = self
3653 .row_visibility
3654 .get(&sheet_id)
3655 .map(|state| state.is_empty())
3656 .unwrap_or(false);
3657 if remove_entry {
3658 self.row_visibility.remove(&sheet_id);
3659 }
3660
3661 if changed {
3662 self.invalidate_row_visibility_mask_cache();
3663 }
3664
3665 changed
3666 }
3667
3668 fn shift_row_visibility_insert(&mut self, sheet_id: SheetId, before0: u32, count: u32) {
3669 if count == 0 {
3670 return;
3671 }
3672 let mut changed = false;
3673 let remove_entry = if let Some(state) = self.row_visibility.get_mut(&sheet_id) {
3674 changed = state.insert_rows(before0, count);
3675 state.is_empty()
3676 } else {
3677 false
3678 };
3679 if remove_entry {
3680 self.row_visibility.remove(&sheet_id);
3681 }
3682 if changed {
3683 self.invalidate_row_visibility_mask_cache();
3684 }
3685 }
3686
3687 fn shift_row_visibility_delete(&mut self, sheet_id: SheetId, start0: u32, count: u32) {
3688 if count == 0 {
3689 return;
3690 }
3691 let mut changed = false;
3692 let remove_entry = if let Some(state) = self.row_visibility.get_mut(&sheet_id) {
3693 changed = state.delete_rows(start0, count);
3694 state.is_empty()
3695 } else {
3696 false
3697 };
3698 if remove_entry {
3699 self.row_visibility.remove(&sheet_id);
3700 }
3701 if changed {
3702 self.invalidate_row_visibility_mask_cache();
3703 }
3704 }
3705
3706 fn apply_inverse_row_visibility_event(&mut self, event: &crate::engine::ChangeEvent) {
3707 if let crate::engine::ChangeEvent::SetRowVisibility {
3708 sheet_id,
3709 row0,
3710 source,
3711 old_hidden,
3712 ..
3713 } = event
3714 {
3715 let _ = self.set_row_hidden_by_sheet_id(*sheet_id, *row0, *old_hidden, *source);
3716 }
3717 }
3718
3719 fn apply_forward_row_visibility_event(&mut self, event: &crate::engine::ChangeEvent) {
3720 if let crate::engine::ChangeEvent::SetRowVisibility {
3721 sheet_id,
3722 row0,
3723 source,
3724 new_hidden,
3725 ..
3726 } = event
3727 {
3728 let _ = self.set_row_hidden_by_sheet_id(*sheet_id, *row0, *new_hidden, *source);
3729 }
3730 }
3731
3732 fn apply_inverse_row_visibility_events(&mut self, events: &[crate::engine::ChangeEvent]) {
3733 for event in events.iter().rev() {
3734 self.apply_inverse_row_visibility_event(event);
3735 }
3736 }
3737
3738 fn apply_forward_row_visibility_events(&mut self, events: &[crate::engine::ChangeEvent]) {
3739 for event in events {
3740 self.apply_forward_row_visibility_event(event);
3741 }
3742 }
3743
3744 fn apply_inverse_staged_formula_event(&mut self, event: &crate::engine::ChangeEvent) {
3745 if let crate::engine::ChangeEvent::StagedFormulaStateChanged { before, .. } = event {
3746 self.restore_staged_formula_state(before);
3747 }
3748 }
3749
3750 fn apply_forward_staged_formula_event(&mut self, event: &crate::engine::ChangeEvent) {
3751 if let crate::engine::ChangeEvent::StagedFormulaStateChanged { after, .. } = event {
3752 self.restore_staged_formula_state(after);
3753 }
3754 }
3755
3756 pub fn set_row_hidden(
3757 &mut self,
3758 sheet: &str,
3759 row_1based: u32,
3760 hidden: bool,
3761 source: RowVisibilitySource,
3762 ) -> Result<(), crate::engine::EditorError> {
3763 let sheet_id = self.ensure_known_sheet_id(sheet)?;
3764 let row0 = Self::normalize_row_1based(row_1based)?;
3765 if self.set_row_hidden_by_sheet_id(sheet_id, row0, hidden, source) {
3766 self.record_formula_plane_structural_change(StructuralScope::Region(
3767 Region::whole_row(sheet_id, row0),
3768 ));
3769 self.mark_data_edited();
3770 }
3771 Ok(())
3772 }
3773
3774 pub fn set_rows_hidden(
3775 &mut self,
3776 sheet: &str,
3777 start_row_1based: u32,
3778 end_row_1based: u32,
3779 hidden: bool,
3780 source: RowVisibilitySource,
3781 ) -> Result<(), crate::engine::EditorError> {
3782 let sheet_id = self.ensure_known_sheet_id(sheet)?;
3783 let (start_row0, end_row0) =
3784 Self::normalize_row_range_1based(start_row_1based, end_row_1based)?;
3785 if self.set_rows_hidden_by_sheet_id(sheet_id, start_row0, end_row0, hidden, source) {
3786 if start_row0 == end_row0 {
3787 self.record_formula_plane_structural_change(StructuralScope::Region(
3788 Region::whole_row(sheet_id, start_row0),
3789 ));
3790 } else {
3791 self.record_formula_plane_structural_change(StructuralScope::Sheet(sheet_id));
3792 }
3793 self.mark_data_edited();
3794 }
3795 Ok(())
3796 }
3797
3798 pub fn is_row_hidden(
3799 &self,
3800 sheet: &str,
3801 row_1based: u32,
3802 source: Option<RowVisibilitySource>,
3803 ) -> Option<bool> {
3804 let sheet_id = self.graph.sheet_id(sheet)?;
3805 let row0 = row_1based.checked_sub(1)?;
3806 Some(
3807 self.row_visibility
3808 .get(&sheet_id)
3809 .map(|state| state.is_row_hidden(row0, source))
3810 .unwrap_or(false),
3811 )
3812 }
3813
3814 pub fn row_visibility_version(&self, sheet: &str) -> Option<u64> {
3815 let sheet_id = self.graph.sheet_id(sheet)?;
3816 Some(
3817 self.row_visibility
3818 .get(&sheet_id)
3819 .map(|state| state.version())
3820 .unwrap_or(0),
3821 )
3822 }
3823
3824 fn build_row_visibility_mask_for_view(
3825 &self,
3826 view: &RangeView<'_>,
3827 mode: VisibilityMaskMode,
3828 ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
3829 let sheet_rows = view.sheet().nrows as usize;
3830 if sheet_rows == 0 || view.start_row() >= sheet_rows {
3831 return Some(std::sync::Arc::new(arrow_array::BooleanArray::new_null(0)));
3832 }
3833
3834 let sheet_id = self.graph.sheet_id(view.sheet_name())?;
3835 let start_row0 = view.start_row() as u32;
3836 let end_row0 = view.end_row().min(sheet_rows.saturating_sub(1)) as u32;
3837 let version = self
3838 .row_visibility
3839 .get(&sheet_id)
3840 .map(|state| state.version())
3841 .unwrap_or(0);
3842 let key = VisibilityMaskCacheKey {
3843 sheet_id,
3844 start_row0,
3845 end_row0,
3846 mode,
3847 version,
3848 };
3849
3850 if let Ok(cache) = self.row_visibility_mask_cache.read()
3851 && let Some(mask) = cache.get(&key)
3852 {
3853 #[cfg(test)]
3854 visibility_mask_test_hooks::inc_hit();
3855 return Some(mask.clone());
3856 }
3857
3858 #[cfg(test)]
3859 visibility_mask_test_hooks::inc_miss();
3860
3861 let state = self.row_visibility.get(&sheet_id);
3862 let mut out = Vec::with_capacity((end_row0 - start_row0 + 1) as usize);
3863 for row0 in start_row0..=end_row0 {
3864 let manual_hidden = state
3865 .map(|s| s.is_row_hidden(row0, Some(RowVisibilitySource::Manual)))
3866 .unwrap_or(false);
3867 let filter_hidden = state
3868 .map(|s| s.is_row_hidden(row0, Some(RowVisibilitySource::Filter)))
3869 .unwrap_or(false);
3870
3871 let include = match mode {
3872 VisibilityMaskMode::IncludeAll => true,
3873 VisibilityMaskMode::ExcludeManualHidden => !manual_hidden,
3874 VisibilityMaskMode::ExcludeFilterHidden => !filter_hidden,
3875 VisibilityMaskMode::ExcludeManualOrFilterHidden => {
3876 !(manual_hidden || filter_hidden)
3877 }
3878 };
3879 out.push(include);
3880 }
3881
3882 let mask = std::sync::Arc::new(arrow_array::BooleanArray::from(out));
3883 if let Ok(mut cache) = self.row_visibility_mask_cache.write() {
3884 const MAX_CACHE_ENTRIES: usize = 4096;
3885 if cache.len() >= MAX_CACHE_ENTRIES {
3886 cache.clear();
3887 #[cfg(test)]
3888 visibility_mask_test_hooks::inc_eviction();
3889 }
3890 cache.insert(key, mask.clone());
3891 }
3892
3893 Some(mask)
3894 }
3895
3896 fn editor_error_to_excel(error: crate::engine::EditorError) -> ExcelError {
3897 match error {
3898 crate::engine::EditorError::Excel(error) => error,
3899 other => ExcelError::new(ExcelErrorKind::Value).with_message(other.to_string()),
3900 }
3901 }
3902
3903 fn demote_span_containing_cell_for_write(
3904 &mut self,
3905 sheet_id: SheetId,
3906 row0: u32,
3907 col0: u32,
3908 ) -> Result<(), crate::engine::EditorError> {
3909 if self.config.formula_plane_mode == FormulaPlaneMode::Off {
3910 return Ok(());
3911 }
3912 let placement = PlacementCoord::new(sheet_id, row0, col0);
3913 let inside_active_span = self
3914 .graph
3915 .formula_authority()
3916 .plane
3917 .spans
3918 .find_at(placement)
3919 .is_some();
3920 if inside_active_span {
3921 self.demote_spans_preserving_computed_overlays(
3922 sheet_id,
3923 Region::point(sheet_id, row0, col0),
3924 )?;
3925 }
3926 Ok(())
3927 }
3928
3929 fn demote_spans_preserving_computed_overlays(
3930 &mut self,
3931 _sheet_id: SheetId,
3932 affected_region: Region,
3933 ) -> Result<(), crate::engine::EditorError> {
3934 self.demote_spans_for_structural_op_impl(None, affected_region, false)
3938 }
3939
3940 fn structural_row_region(sheet_id: SheetId, start_row0: u32) -> Region {
3941 Region::rows_from(sheet_id, start_row0)
3942 }
3943
3944 fn structural_col_region(sheet_id: SheetId, start_col0: u32) -> Region {
3945 Region::cols_from(sheet_id, start_col0)
3946 }
3947
3948 fn span_result_region_intersects_affected(
3949 span: &crate::formula_plane::runtime::FormulaSpan,
3950 affected_region: &Region,
3951 ) -> bool {
3952 Region::from_domain(span.result_region.domain()).intersects(affected_region)
3953 }
3954
3955 fn span_any_read_region_intersects_affected(
3956 plane: &FormulaPlane,
3957 span: &crate::formula_plane::runtime::FormulaSpan,
3958 affected_region: &Region,
3959 ) -> bool {
3960 span.read_summary_id
3961 .and_then(|read_summary_id| plane.span_read_summaries.get(read_summary_id))
3962 .is_some_and(|summary| {
3963 summary
3964 .dependencies
3965 .iter()
3966 .any(|dependency| dependency.read_region.intersects(affected_region))
3967 })
3968 }
3969
3970 fn insert_formula_plane_dirty_coords_for_span(
3971 &self,
3972 span_ref: FormulaSpanRef,
3973 dirty: ProducerDirtyDomain,
3974 out: &mut FxHashSet<(SheetId, u32, u32)>,
3975 ) -> Result<(), crate::engine::EditorError> {
3976 let authority = self.graph.formula_authority();
3977 let span = authority.plane.spans.get(span_ref).ok_or_else(|| {
3978 ExcelError::new(ExcelErrorKind::NImpl)
3979 .with_message("FormulaPlane dirty transfer referenced a stale span")
3980 })?;
3981 match dirty {
3982 ProducerDirtyDomain::Whole => {
3983 out.extend(
3984 span.domain
3985 .iter()
3986 .map(|coord| (coord.sheet_id, coord.row, coord.col)),
3987 );
3988 }
3989 ProducerDirtyDomain::Cells(cells) => {
3990 out.extend(cells.into_iter().filter_map(|key| {
3991 let coord = PlacementCoord::new(key.sheet_id, key.row, key.col);
3992 span.domain
3993 .contains(coord)
3994 .then_some((coord.sheet_id, coord.row, coord.col))
3995 }));
3996 }
3997 ProducerDirtyDomain::Regions(regions) => {
3998 out.extend(span.domain.iter().filter_map(|coord| {
3999 let key = crate::formula_plane::region_index::RegionKey::from(coord);
4000 regions
4001 .iter()
4002 .any(|region| region.contains_key(key))
4003 .then_some((coord.sheet_id, coord.row, coord.col))
4004 }));
4005 }
4006 }
4007 Ok(())
4008 }
4009
4010 fn compute_current_formula_plane_dirty_result_coords(
4011 &self,
4012 ) -> Result<FxHashSet<(SheetId, u32, u32)>, crate::engine::EditorError> {
4013 use crate::formula_plane::producer::compute_dirty_closure;
4014
4015 let authority = self.graph.formula_authority();
4016 let span_refs = authority.active_span_refs();
4017 let span_refs_by_id = span_refs
4018 .iter()
4019 .copied()
4020 .map(|span_ref| (span_ref.id, span_ref))
4021 .collect::<BTreeMap<_, _>>();
4022 let mut dirty_coords = FxHashSet::default();
4023
4024 if self.formula_plane_indexes_epoch_seen != authority.indexes_epoch() {
4025 for span_ref in span_refs {
4026 self.insert_formula_plane_dirty_coords_for_span(
4027 span_ref,
4028 ProducerDirtyDomain::Whole,
4029 &mut dirty_coords,
4030 )?;
4031 }
4032 return Ok(dirty_coords);
4033 }
4034
4035 let pending_changed_regions = authority.pending_changed_regions();
4036 if pending_changed_regions.is_empty() {
4037 return Ok(dirty_coords);
4038 }
4039
4040 let closure = compute_dirty_closure(
4041 &authority.consumer_reads,
4042 pending_changed_regions.iter().copied(),
4043 |producer| authority.producer_results.producer_result_region(producer),
4044 );
4045 for work in closure.work {
4046 let FormulaProducerId::Span(span_id) = work.producer else {
4047 continue;
4048 };
4049 let Some(span_ref) = span_refs_by_id.get(&span_id).copied() else {
4050 continue;
4051 };
4052 self.insert_formula_plane_dirty_coords_for_span(
4053 span_ref,
4054 work.dirty,
4055 &mut dirty_coords,
4056 )?;
4057 }
4058 for fallback in closure.fallbacks {
4059 let FormulaProducerId::Span(span_id) = fallback.consumer else {
4060 continue;
4061 };
4062 let Some(span_ref) = span_refs_by_id.get(&span_id).copied() else {
4063 continue;
4064 };
4065 self.insert_formula_plane_dirty_coords_for_span(
4066 span_ref,
4067 ProducerDirtyDomain::Whole,
4068 &mut dirty_coords,
4069 )?;
4070 }
4071
4072 Ok(dirty_coords)
4073 }
4074
4075 fn demote_spans_for_structural_op(
4085 &mut self,
4086 op: StructuralOp,
4087 affected_region: Region,
4088 ) -> Result<(), crate::engine::EditorError> {
4089 if op.count() == 0 {
4090 return Ok(());
4091 }
4092 self.demote_spans_for_structural_op_impl(Some(op), affected_region, true)
4093 }
4094
4095 fn demote_spans_for_structural_op_impl(
4096 &mut self,
4097 op: Option<StructuralOp>,
4098 affected_region: Region,
4099 clear_computed_overlays: bool,
4100 ) -> Result<(), crate::engine::EditorError> {
4101 struct SpanPlan {
4102 span_ref: FormulaSpanRef,
4103 sheet_id: SheetId,
4104 ast: ASTNode,
4105 origin_row: u32,
4106 origin_col: u32,
4107 binding_set_id: Option<crate::formula_plane::runtime::SpanBindingSetId>,
4108 placements: Vec<(u32, u32)>,
4109 }
4110
4111 fn substitute_literal_slots_for_template_placement(
4112 ast: &ASTNode,
4113 binding: &[LiteralValue],
4114 ) -> ASTNode {
4115 fn clone_with_slots(
4116 ast: &ASTNode,
4117 binding: &[LiteralValue],
4118 next: &mut usize,
4119 in_array: bool,
4120 ) -> ASTNode {
4121 let node_type = match &ast.node_type {
4122 ASTNodeType::Literal(_) if !in_array => {
4123 let value = binding.get(*next).cloned().unwrap_or(LiteralValue::Empty);
4124 *next = next.saturating_add(1);
4125 ASTNodeType::Literal(value)
4126 }
4127 ASTNodeType::Literal(value) => ASTNodeType::Literal(value.clone()),
4128 ASTNodeType::Reference {
4129 original,
4130 reference,
4131 } => ASTNodeType::Reference {
4132 original: original.clone(),
4133 reference: reference.clone(),
4134 },
4135 ASTNodeType::UnaryOp { op, expr } => ASTNodeType::UnaryOp {
4136 op: op.clone(),
4137 expr: Box::new(clone_with_slots(expr, binding, next, in_array)),
4138 },
4139 ASTNodeType::BinaryOp { op, left, right } => ASTNodeType::BinaryOp {
4140 op: op.clone(),
4141 left: Box::new(clone_with_slots(left, binding, next, in_array)),
4142 right: Box::new(clone_with_slots(right, binding, next, in_array)),
4143 },
4144 ASTNodeType::Function { name, args } => ASTNodeType::Function {
4145 name: name.clone(),
4146 args: args
4147 .iter()
4148 .map(|arg| clone_with_slots(arg, binding, next, in_array))
4149 .collect(),
4150 },
4151 ASTNodeType::Call { callee, args } => ASTNodeType::Call {
4152 callee: Box::new(clone_with_slots(callee, binding, next, in_array)),
4153 args: args
4154 .iter()
4155 .map(|arg| clone_with_slots(arg, binding, next, in_array))
4156 .collect(),
4157 },
4158 ASTNodeType::Array(rows) => ASTNodeType::Array(
4159 rows.iter()
4160 .map(|row| {
4161 row.iter()
4162 .map(|cell| clone_with_slots(cell, binding, next, true))
4163 .collect()
4164 })
4165 .collect(),
4166 ),
4167 };
4168 ASTNode::new(node_type, ast.source_token.clone())
4169 }
4170 let mut next = 0usize;
4171 clone_with_slots(ast, binding, &mut next, false)
4172 }
4173
4174 let span_refs = self.graph.formula_authority().active_span_refs();
4175 if span_refs.is_empty() {
4176 return Ok(());
4177 }
4178 let dirty_span_coords = if clear_computed_overlays {
4179 FxHashSet::default()
4180 } else {
4181 self.compute_current_formula_plane_dirty_result_coords()?
4182 };
4183
4184 struct ShiftPlan {
4185 span_ref: FormulaSpanRef,
4186 template_id: crate::formula_plane::ids::FormulaTemplateId,
4187 new_origin_row: u32,
4188 new_origin_col: u32,
4189 new_domain: crate::formula_plane::runtime::PlacementDomain,
4190 new_read_summary: Option<SpanReadSummary>,
4191 binding_set_id: Option<crate::formula_plane::runtime::SpanBindingSetId>,
4192 force_binding_residual_axes: bool,
4193 }
4194
4195 fn checked_shift_u32(value: u32, delta: i64) -> Option<u32> {
4196 u32::try_from(i64::from(value).checked_add(delta)?).ok()
4197 }
4198
4199 fn shifted_read_summary(
4200 read_summary: &SpanReadSummary,
4201 new_result_region: Region,
4202 op: StructuralOp,
4203 row_delta: i64,
4204 col_delta: i64,
4205 ) -> Option<SpanReadSummary> {
4206 let mut dependencies = Vec::with_capacity(read_summary.dependencies.len());
4207 for dependency in &read_summary.dependencies {
4208 let read_region = match op.classify_region(dependency.read_region) {
4209 crate::formula_plane::structural_shift::AxisShiftCase::OtherSheet
4210 | crate::formula_plane::structural_shift::AxisShiftCase::EntirelyBelow => {
4211 dependency.read_region
4212 }
4213 crate::formula_plane::structural_shift::AxisShiftCase::EntirelyAboveShift {
4214 ..
4215 } => dependency
4216 .read_region
4217 .project_through_axis_shift(row_delta, col_delta)?,
4218 crate::formula_plane::structural_shift::AxisShiftCase::Straddles
4219 | crate::formula_plane::structural_shift::AxisShiftCase::DeleteFullyContains => {
4220 return None;
4221 }
4222 };
4223 dependencies.push(crate::formula_plane::producer::SpanReadDependency {
4224 read_region,
4225 projection: dependency.projection,
4226 });
4227 }
4228 Some(SpanReadSummary {
4229 result_region: new_result_region,
4230 dependencies,
4231 })
4232 }
4233
4234 fn compact_axis_through_delete(
4235 min: u32,
4236 max: u32,
4237 start: u32,
4238 count: u32,
4239 ) -> Option<(u32, u32)> {
4240 let end = start.saturating_add(count);
4241 if max < start || min >= end {
4242 return Some((min.saturating_sub(count), max.saturating_sub(count)));
4243 }
4244 let keeps_left = min < start;
4245 let keeps_right = max >= end;
4246 match (keeps_left, keeps_right) {
4247 (false, false) => None,
4248 (true, false) => Some((min, start.checked_sub(1)?)),
4249 (false, true) => Some((start, max.checked_sub(count)?)),
4250 (true, true) => Some((min, max.checked_sub(count)?)),
4251 }
4252 }
4253
4254 fn compact_domain_through_delete(
4255 domain: &PlacementDomain,
4256 op: StructuralOp,
4257 ) -> Option<PlacementDomain> {
4258 match (domain, op) {
4259 (
4260 PlacementDomain::RowRun {
4261 sheet_id,
4262 row_start,
4263 row_end,
4264 col,
4265 },
4266 StructuralOp::DeleteRows { start, count, .. },
4267 ) => {
4268 let (row_start, row_end) =
4269 compact_axis_through_delete(*row_start, *row_end, start, count)?;
4270 Some(PlacementDomain::row_run(
4271 *sheet_id, row_start, row_end, *col,
4272 ))
4273 }
4274 (
4275 PlacementDomain::Rect {
4276 sheet_id,
4277 row_start,
4278 row_end,
4279 col_start,
4280 col_end,
4281 },
4282 StructuralOp::DeleteRows { start, count, .. },
4283 ) => {
4284 let (row_start, row_end) =
4285 compact_axis_through_delete(*row_start, *row_end, start, count)?;
4286 Some(PlacementDomain::rect(
4287 *sheet_id, row_start, row_end, *col_start, *col_end,
4288 ))
4289 }
4290 (
4291 PlacementDomain::ColRun {
4292 sheet_id,
4293 row,
4294 col_start,
4295 col_end,
4296 },
4297 StructuralOp::DeleteColumns { start, count, .. },
4298 ) => {
4299 let (col_start, col_end) =
4300 compact_axis_through_delete(*col_start, *col_end, start, count)?;
4301 Some(PlacementDomain::col_run(
4302 *sheet_id, *row, col_start, col_end,
4303 ))
4304 }
4305 (
4306 PlacementDomain::Rect {
4307 sheet_id,
4308 row_start,
4309 row_end,
4310 col_start,
4311 col_end,
4312 },
4313 StructuralOp::DeleteColumns { start, count, .. },
4314 ) => {
4315 let (col_start, col_end) =
4316 compact_axis_through_delete(*col_start, *col_end, start, count)?;
4317 Some(PlacementDomain::rect(
4318 *sheet_id, *row_start, *row_end, col_start, col_end,
4319 ))
4320 }
4321 _ => None,
4322 }
4323 }
4324
4325 fn compact_axis_range_through_delete(
4326 axis: crate::formula_plane::region_index::AxisRange,
4327 start: u32,
4328 count: u32,
4329 ) -> Option<crate::formula_plane::region_index::AxisRange> {
4330 use crate::formula_plane::region_index::AxisRange;
4331 match axis {
4332 AxisRange::Point(point) => compact_axis_through_delete(point, point, start, count)
4333 .map(|(point, _)| AxisRange::Point(point)),
4334 AxisRange::Span(min, max) => compact_axis_through_delete(min, max, start, count)
4335 .map(|(min, max)| AxisRange::Span(min, max)),
4336 AxisRange::All => Some(AxisRange::All),
4337 AxisRange::From(_) | AxisRange::To(_) => None,
4338 }
4339 }
4340
4341 fn compact_region_through_delete(region: Region, op: StructuralOp) -> Option<Region> {
4342 let (rows, cols) = region.axis_ranges();
4343 match op {
4344 StructuralOp::DeleteRows {
4345 sheet_id,
4346 start,
4347 count,
4348 } if region.sheet_id() == sheet_id => Some(Region {
4349 sheet_id,
4350 rows: compact_axis_range_through_delete(rows, start, count)?,
4351 cols,
4352 }),
4353 StructuralOp::DeleteColumns {
4354 sheet_id,
4355 start,
4356 count,
4357 } if region.sheet_id() == sheet_id => Some(Region {
4358 sheet_id,
4359 rows,
4360 cols: compact_axis_range_through_delete(cols, start, count)?,
4361 }),
4362 _ => Some(region),
4363 }
4364 }
4365
4366 fn compact_read_summary_through_delete(
4367 read_summary: &SpanReadSummary,
4368 new_result_region: Region,
4369 op: StructuralOp,
4370 ) -> Option<SpanReadSummary> {
4371 let mut dependencies = Vec::with_capacity(read_summary.dependencies.len());
4372 for dependency in &read_summary.dependencies {
4373 let read_region = match op.classify_region(dependency.read_region) {
4374 crate::formula_plane::structural_shift::AxisShiftCase::OtherSheet
4375 | crate::formula_plane::structural_shift::AxisShiftCase::EntirelyBelow => {
4376 dependency.read_region
4377 }
4378 crate::formula_plane::structural_shift::AxisShiftCase::EntirelyAboveShift {
4379 ..
4380 } => {
4381 let (row_delta, col_delta) = op.axis_shift_delta();
4382 dependency
4383 .read_region
4384 .project_through_axis_shift(row_delta, col_delta)?
4385 }
4386 crate::formula_plane::structural_shift::AxisShiftCase::Straddles => {
4387 compact_region_through_delete(dependency.read_region, op)?
4388 }
4389 crate::formula_plane::structural_shift::AxisShiftCase::DeleteFullyContains => {
4390 return None;
4391 }
4392 };
4393 dependencies.push(crate::formula_plane::producer::SpanReadDependency {
4394 read_region,
4395 projection: dependency.projection,
4396 });
4397 }
4398 Some(SpanReadSummary {
4399 result_region: new_result_region,
4400 dependencies,
4401 })
4402 }
4403
4404 fn domain_origin_1_based(domain: &PlacementDomain) -> (u32, u32) {
4405 match domain {
4406 PlacementDomain::RowRun { row_start, col, .. } => (row_start + 1, col + 1),
4407 PlacementDomain::ColRun { row, col_start, .. } => (row + 1, col_start + 1),
4408 PlacementDomain::Rect {
4409 row_start,
4410 col_start,
4411 ..
4412 } => (row_start + 1, col_start + 1),
4413 }
4414 }
4415
4416 let mut shift_plans = Vec::new();
4417 let mut remove_refs = Vec::new();
4418 let mut demote_refs = Vec::new();
4419 for span_ref in span_refs {
4420 let authority = self.graph.formula_authority();
4421 let Some(span) = authority.plane.spans.get(span_ref) else {
4422 continue;
4423 };
4424 let read_summary = span
4425 .read_summary_id
4426 .and_then(|id| authority.plane.span_read_summaries.get(id));
4427 let Some(op) = op else {
4428 let result_region_affected =
4433 Self::span_result_region_intersects_affected(span, &affected_region);
4434 let read_region_affected = Self::span_any_read_region_intersects_affected(
4435 &authority.plane,
4436 span,
4437 &affected_region,
4438 );
4439 if result_region_affected || read_region_affected {
4440 demote_refs.push(span_ref);
4441 }
4442 continue;
4443 };
4444 match classify_span_for_op(span, read_summary, op) {
4445 SpanShiftPlan::NoOp => {}
4446 SpanShiftPlan::Remove => {
4447 remove_refs.push(span_ref);
4448 }
4449 SpanShiftPlan::Demote {
4450 reason:
4451 crate::formula_plane::structural_shift::SpanDemoteReason::DeletePartiallyOverlaps,
4452 } => {
4453 let binding_compaction_safe = span
4454 .binding_set_id
4455 .and_then(|id| authority.plane.binding_sets.get(id))
4456 .is_none_or(|binding_set| binding_set.is_single_literal_binding());
4457 if binding_compaction_safe
4458 && let Some(new_domain) = compact_domain_through_delete(&span.domain, op)
4459 {
4460 let new_result_region = Region::from_domain(&new_domain);
4461 let new_read_summary = if let Some(summary) = read_summary {
4462 compact_read_summary_through_delete(summary, new_result_region, op)
4463 } else {
4464 None
4465 };
4466 if read_summary.is_none() || new_read_summary.is_some() {
4467 let (new_origin_row, new_origin_col) = domain_origin_1_based(&new_domain);
4468 let Some(template) = authority.plane.templates.get(span.template_id)
4469 else {
4470 return Err(ExcelError::new(ExcelErrorKind::Ref)
4471 .with_message(
4472 "FormulaPlane delete compaction found a span with a missing template",
4473 )
4474 .into());
4475 };
4476 let force_binding_residual_axes = span
4477 .binding_set_id
4478 .and_then(|id| authority.plane.binding_sets.get(id))
4479 .is_some_and(|binding_set| {
4480 !binding_set.value_ref_slots.is_empty()
4481 && (new_origin_row != template.origin_row
4482 || new_origin_col != template.origin_col)
4483 });
4484 shift_plans.push(ShiftPlan {
4485 span_ref,
4486 template_id: span.template_id,
4487 new_origin_row,
4488 new_origin_col,
4489 new_domain,
4490 new_read_summary,
4491 binding_set_id: span.binding_set_id,
4492 force_binding_residual_axes,
4493 });
4494 } else {
4495 demote_refs.push(span_ref);
4496 }
4497 } else {
4498 demote_refs.push(span_ref);
4499 }
4500 }
4501 SpanShiftPlan::Demote { .. } => {
4502 demote_refs.push(span_ref);
4503 }
4504 SpanShiftPlan::Shift {
4505 row_delta,
4506 col_delta,
4507 origin_row_delta,
4508 origin_col_delta,
4509 } => {
4510 let Some(template) = authority.plane.templates.get(span.template_id) else {
4511 return Err(ExcelError::new(ExcelErrorKind::Ref)
4512 .with_message("FormulaPlane shift found a span with a missing template")
4513 .into());
4514 };
4515 let Some(new_origin_row) =
4516 checked_shift_u32(template.origin_row, origin_row_delta)
4517 else {
4518 return Err(ExcelError::new(ExcelErrorKind::Ref)
4519 .with_message("FormulaPlane shift overflowed template origin row")
4520 .into());
4521 };
4522 let Some(new_origin_col) =
4523 checked_shift_u32(template.origin_col, origin_col_delta)
4524 else {
4525 return Err(ExcelError::new(ExcelErrorKind::Ref)
4526 .with_message("FormulaPlane shift overflowed template origin column")
4527 .into());
4528 };
4529 let Some(new_domain) =
4530 span.domain.project_through_axis_shift(row_delta, col_delta)
4531 else {
4532 return Err(ExcelError::new(ExcelErrorKind::Ref)
4533 .with_message("FormulaPlane shift overflowed span domain")
4534 .into());
4535 };
4536 let new_result_region = Region::from_domain(&new_domain);
4537 let new_read_summary = if let Some(summary) = read_summary {
4538 Some(
4539 shifted_read_summary(
4540 summary,
4541 new_result_region,
4542 op,
4543 row_delta,
4544 col_delta,
4545 )
4546 .ok_or_else(|| {
4547 ExcelError::new(ExcelErrorKind::Ref).with_message(
4548 "FormulaPlane shift could not project read summary",
4549 )
4550 })?,
4551 )
4552 } else {
4553 None
4554 };
4555 let force_binding_residual_axes = span
4556 .binding_set_id
4557 .and_then(|id| authority.plane.binding_sets.get(id))
4558 .is_some_and(|binding_set| {
4559 !binding_set.value_ref_slots.is_empty()
4560 && (origin_row_delta != 0 || origin_col_delta != 0)
4561 });
4562 shift_plans.push(ShiftPlan {
4563 span_ref,
4564 template_id: span.template_id,
4565 new_origin_row,
4566 new_origin_col,
4567 new_domain,
4568 new_read_summary,
4569 binding_set_id: span.binding_set_id,
4570 force_binding_residual_axes,
4571 });
4572 }
4573 }
4574 }
4575 if !shift_plans.is_empty() || !remove_refs.is_empty() {
4576 let authority = self.graph.formula_authority_mut();
4577 for span_ref in remove_refs {
4578 authority.plane.remove_overlays_for_source_span(span_ref);
4579 authority.plane.remove_span(span_ref);
4580 }
4581 for plan in shift_plans {
4582 let Some(template_id) = authority.plane.intern_shifted_template_origin(
4583 plan.template_id,
4584 plan.new_origin_row,
4585 plan.new_origin_col,
4586 ) else {
4587 return Err(ExcelError::new(ExcelErrorKind::Ref)
4588 .with_message("FormulaPlane shift could not clone template origin")
4589 .into());
4590 };
4591 if let Some(binding_set_id) = plan.binding_set_id {
4592 let Some(template) = authority.plane.templates.get(template_id) else {
4593 return Err(ExcelError::new(ExcelErrorKind::Ref)
4594 .with_message("FormulaPlane shift could not find shifted template")
4595 .into());
4596 };
4597 let (ast_id, origin_row, origin_col) =
4598 (template.ast_id, template.origin_row, template.origin_col);
4599 authority.plane.set_binding_template_anchor(
4600 binding_set_id,
4601 ast_id,
4602 origin_row,
4603 origin_col,
4604 );
4605 }
4606 let read_summary_id = plan
4607 .new_read_summary
4608 .map(|summary| authority.plane.insert_span_read_summary(summary));
4609 let result_region = ResultRegion::scalar_cells(plan.new_domain.clone());
4610 if !authority.plane.replace_span_geometry(
4611 plan.span_ref,
4612 template_id,
4613 plan.new_domain,
4614 result_region,
4615 read_summary_id,
4616 ) {
4617 return Err(ExcelError::new(ExcelErrorKind::Ref)
4618 .with_message("FormulaPlane shift could not update span geometry")
4619 .into());
4620 }
4621 if plan.force_binding_residual_axes
4622 && let Some(binding_set_id) = plan.binding_set_id
4623 {
4624 authority.plane.force_binding_residual_axes(binding_set_id);
4633 }
4634 }
4635 authority.rebuild_indexes();
4636 self.formula_plane_indexes_epoch_seen = 0;
4637 }
4638
4639 let mut span_plans = Vec::new();
4640 for span_ref in demote_refs {
4641 let authority = self.graph.formula_authority();
4642 let Some(span) = authority.plane.spans.get(span_ref) else {
4643 continue;
4644 };
4645 let Some(template) = authority.plane.templates.get(span.template_id) else {
4646 return Err(ExcelError::new(ExcelErrorKind::Ref)
4647 .with_message("FormulaPlane demotion found a span with a missing template")
4648 .into());
4649 };
4650 let ast = self
4651 .graph
4652 .data_store()
4653 .retrieve_ast(template.ast_id, self.graph.sheet_reg())
4654 .ok_or_else(|| {
4655 ExcelError::new(ExcelErrorKind::Ref)
4656 .with_message("FormulaPlane demotion could not retrieve the template AST")
4657 })?;
4658 let placements = span
4659 .domain
4660 .iter()
4661 .map(|placement| (placement.row + 1, placement.col + 1))
4662 .collect();
4663 span_plans.push(SpanPlan {
4664 span_ref,
4665 sheet_id: span.sheet_id,
4666 ast,
4667 origin_row: template.origin_row,
4668 origin_col: template.origin_col,
4669 binding_set_id: span.binding_set_id,
4670 placements,
4671 });
4672 }
4673 if span_plans.is_empty() {
4674 return Ok(());
4675 }
4676
4677 let mut relocated = Vec::new();
4678 let mut placement_cells = Vec::new();
4679 for plan in &span_plans {
4680 for &(row, col) in &plan.placements {
4681 let row_delta = i64::from(row) - i64::from(plan.origin_row);
4682 let col_delta = i64::from(col) - i64::from(plan.origin_col);
4683 let bound_ast = if let Some(binding_set_id) = plan.binding_set_id {
4684 let authority = self.graph.formula_authority();
4685 if let Some(binding_set) = authority.plane.binding_sets.get(binding_set_id) {
4686 if binding_set.is_single_literal_binding() {
4687 plan.ast.clone()
4688 } else {
4689 let placement = crate::formula_plane::runtime::PlacementCoord::new(
4690 plan.sheet_id,
4691 row.saturating_sub(1),
4692 col.saturating_sub(1),
4693 );
4694 let binding =
4695 authority.plane.spans.get(plan.span_ref).and_then(|span| {
4696 binding_set
4697 .literal_bindings_for_placement(&span.domain, placement)
4698 });
4699 if let Some(binding) = binding {
4700 substitute_literal_slots_for_template_placement(
4701 &plan.ast,
4702 binding.as_ref(),
4703 )
4704 } else {
4705 plan.ast.clone()
4706 }
4707 }
4708 } else {
4709 plan.ast.clone()
4710 }
4711 } else {
4712 plan.ast.clone()
4713 };
4714 let ast = relocate_ast_for_template_placement(&bound_ast, row_delta, col_delta)?;
4715 relocated.push((plan.sheet_id, row, col, ast));
4716 placement_cells.push((plan.sheet_id, row, col));
4717 }
4718 }
4719 let planned_by_sheet = {
4720 let mut pipeline = self.ingest_pipeline();
4721 let mut planned_by_sheet: BTreeMap<
4722 SheetId,
4723 Vec<(u32, u32, AstNodeId, DependencyPlanRow)>,
4724 > = BTreeMap::new();
4725 for (formula_sheet_id, row, col, ast) in relocated {
4726 let placement =
4727 CellRef::new(formula_sheet_id, Coord::from_excel(row, col, true, true));
4728 let ingested =
4729 pipeline.ingest_formula(FormulaAstInput::Tree(ast), placement, None)?;
4730 planned_by_sheet.entry(formula_sheet_id).or_default().push((
4731 row,
4732 col,
4733 ingested.ast_id,
4734 ingested.dep_plan,
4735 ));
4736 }
4737 planned_by_sheet
4738 };
4739 {
4740 let authority = self.graph.formula_authority_mut();
4741 for plan in &span_plans {
4742 authority
4743 .plane
4744 .remove_overlays_for_source_span(plan.span_ref);
4745 authority.plane.remove_span(plan.span_ref);
4746 }
4747 authority.rebuild_indexes();
4748 }
4749 if clear_computed_overlays {
4750 self.clear_computed_overlay_cells_in_region(&placement_cells, &affected_region);
4757 }
4758 for (formula_sheet_id, planned) in planned_by_sheet {
4759 let sheet_name = self.graph.sheet_name(formula_sheet_id).to_string();
4760 self.graph
4761 .bulk_set_formulas_with_plans(&sheet_name, planned)?;
4762 }
4763 if !clear_computed_overlays {
4764 for (formula_sheet_id, row, col) in &placement_cells {
4765 let row0 = row.saturating_sub(1);
4766 let col0 = col.saturating_sub(1);
4767 if dirty_span_coords.contains(&(*formula_sheet_id, row0, col0)) {
4768 continue;
4769 }
4770 let cell =
4771 CellRef::new(*formula_sheet_id, Coord::from_excel(*row, *col, true, true));
4772 if let Some(&vertex_id) = self.graph.get_vertex_id_for_address(&cell) {
4773 self.graph.set_dirty(vertex_id, false);
4774 }
4775 }
4776 }
4777 self.formula_plane_indexes_epoch_seen = 0;
4778 Ok(())
4779 }
4780
4781 pub fn insert_rows(
4783 &mut self,
4784 sheet: &str,
4785 before: u32,
4786 count: u32,
4787 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
4788 {
4789 use crate::engine::graph::editor::vertex_editor::VertexEditor;
4790 let sheet_id = self.ensure_known_sheet_id(sheet)?;
4791 let before0 = before.saturating_sub(1);
4792 let affected_region = Self::structural_row_region(sheet_id, before0);
4793 let op = StructuralOp::InsertRows {
4794 sheet_id,
4795 before: before0,
4796 count,
4797 };
4798 self.demote_spans_for_structural_op(op, affected_region)?;
4799 let summary = {
4800 let mut editor = VertexEditor::new(&mut self.graph);
4801 editor.insert_rows(sheet_id, before0, count)?
4802 };
4803 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
4804 let before0 = before0 as usize;
4805 asheet.insert_rows(before0, count as usize);
4806 }
4807 self.mark_moved_formula_vertices_dirty(&summary);
4808 self.clear_computed_overlay_after_row(sheet, before0 as usize);
4809 self.shift_row_visibility_insert(sheet_id, before0, count);
4810 self.record_formula_plane_structural_change(StructuralScope::Region(affected_region));
4811 self.mark_topology_edited();
4812 Ok(summary)
4813 }
4814
4815 pub fn delete_rows(
4817 &mut self,
4818 sheet: &str,
4819 start: u32,
4820 count: u32,
4821 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
4822 {
4823 use crate::engine::graph::editor::vertex_editor::VertexEditor;
4824 let sheet_id = self.ensure_known_sheet_id(sheet)?;
4825 let start0 = start.saturating_sub(1);
4826 let affected_region = Self::structural_row_region(sheet_id, start0);
4827 let op = StructuralOp::DeleteRows {
4828 sheet_id,
4829 start: start0,
4830 count,
4831 };
4832 self.demote_spans_for_structural_op(op, affected_region)?;
4833 let summary = {
4834 let mut editor = VertexEditor::new(&mut self.graph);
4835 editor.delete_rows(sheet_id, start0, count)?
4836 };
4837 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
4838 let start0 = start0 as usize;
4839 asheet.delete_rows(start0, count as usize);
4840 }
4841 self.mark_moved_formula_vertices_dirty(&summary);
4842 self.clear_computed_overlay_after_row(sheet, start0 as usize);
4843 self.shift_row_visibility_delete(sheet_id, start0, count);
4844 self.record_formula_plane_structural_change(StructuralScope::Region(affected_region));
4845 self.mark_topology_edited();
4846 Ok(summary)
4847 }
4848
4849 pub fn insert_columns(
4851 &mut self,
4852 sheet: &str,
4853 before: u32,
4854 count: u32,
4855 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
4856 {
4857 use crate::engine::graph::editor::vertex_editor::VertexEditor;
4858 let sheet_id = self.graph.sheet_id(sheet).ok_or(
4859 crate::engine::graph::editor::vertex_editor::EditorError::InvalidName {
4860 name: sheet.to_string(),
4861 reason: "Unknown sheet".to_string(),
4862 },
4863 )?;
4864 let before0 = before.saturating_sub(1);
4865 let affected_region = Self::structural_col_region(sheet_id, before0);
4866 let op = StructuralOp::InsertColumns {
4867 sheet_id,
4868 before: before0,
4869 count,
4870 };
4871 self.demote_spans_for_structural_op(op, affected_region)?;
4872 let summary = {
4873 let mut editor = VertexEditor::new(&mut self.graph);
4874 editor.insert_columns(sheet_id, before0, count)?
4875 };
4876 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
4877 let before0 = before0 as usize;
4878 asheet.insert_columns(before0, count as usize);
4879 }
4880 self.mark_moved_formula_vertices_dirty(&summary);
4881 self.clear_computed_overlay_after_col(sheet, before0 as usize);
4882 self.record_formula_plane_structural_change(StructuralScope::Region(affected_region));
4883 self.mark_topology_edited();
4884 Ok(summary)
4885 }
4886
4887 pub fn delete_columns(
4889 &mut self,
4890 sheet: &str,
4891 start: u32,
4892 count: u32,
4893 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
4894 {
4895 use crate::engine::graph::editor::vertex_editor::VertexEditor;
4896 let sheet_id = self.graph.sheet_id(sheet).ok_or(
4897 crate::engine::graph::editor::vertex_editor::EditorError::InvalidName {
4898 name: sheet.to_string(),
4899 reason: "Unknown sheet".to_string(),
4900 },
4901 )?;
4902 let start0 = start.saturating_sub(1);
4903 let affected_region = Self::structural_col_region(sheet_id, start0);
4904 let op = StructuralOp::DeleteColumns {
4905 sheet_id,
4906 start: start0,
4907 count,
4908 };
4909 self.demote_spans_for_structural_op(op, affected_region)?;
4910 let summary = {
4911 let mut editor = VertexEditor::new(&mut self.graph);
4912 editor.delete_columns(sheet_id, start0, count)?
4913 };
4914 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
4915 let start0 = start0 as usize;
4916 asheet.delete_columns(start0, count as usize);
4917 }
4918 self.mark_moved_formula_vertices_dirty(&summary);
4919 self.clear_computed_overlay_after_col(sheet, start0 as usize);
4920 self.record_formula_plane_structural_change(StructuralScope::Region(affected_region));
4921 self.mark_topology_edited();
4922 Ok(summary)
4923 }
4924 fn arrow_used_row_bounds(
4926 &self,
4927 sheet: &str,
4928 start_col: u32,
4929 end_col: u32,
4930 ) -> Option<(u32, u32)> {
4931 let a = self.sheet_store().sheet(sheet)?;
4932 if a.columns.is_empty() {
4933 return None;
4934 }
4935 let sc0 = start_col.saturating_sub(1) as usize;
4936 let ec0 = end_col.saturating_sub(1) as usize;
4937 let col_hi = a.columns.len().saturating_sub(1);
4938 if sc0 > col_hi {
4939 return None;
4940 }
4941 let ec0 = ec0.min(col_hi);
4942 let snap = self.data_snapshot_id();
4944 let mut min_r0: Option<usize> = None;
4945 for ci in sc0..=ec0 {
4946 let sheet_id = self.graph.sheet_id(sheet)?;
4947 if let Some((Some(mv), _)) = self.row_bounds_cache.read().ok().and_then(|g| {
4948 g.as_ref()
4949 .and_then(|c| c.get_row_bounds(sheet_id, ci, snap))
4950 }) {
4951 let mv = mv as usize;
4952 min_r0 = Some(min_r0.map(|m| m.min(mv)).unwrap_or(mv));
4953 continue;
4954 }
4955 let (min_c, max_c) = Self::scan_column_used_bounds(a, ci);
4957 if let Ok(mut g) = self.row_bounds_cache.write() {
4958 g.get_or_insert_with(|| RowBoundsCache::new(snap))
4959 .put_row_bounds(sheet_id, ci, snap, (min_c, max_c));
4960 }
4961 if let Some(m) = min_c {
4962 min_r0 = Some(min_r0.map(|mm| mm.min(m as usize)).unwrap_or(m as usize));
4963 }
4964 }
4965 min_r0?;
4966 let mut max_r0: Option<usize> = None;
4967 for ci in sc0..=ec0 {
4968 let sheet_id = self.graph.sheet_id(sheet)?;
4969 if let Some((_, Some(mv))) = self.row_bounds_cache.read().ok().and_then(|g| {
4970 g.as_ref()
4971 .and_then(|c| c.get_row_bounds(sheet_id, ci, snap))
4972 }) {
4973 let mv = mv as usize;
4974 max_r0 = Some(max_r0.map(|m| m.max(mv)).unwrap_or(mv));
4975 continue;
4976 }
4977 let (_min_c, max_c) = Self::scan_column_used_bounds(a, ci);
4978 if let Ok(mut g) = self.row_bounds_cache.write() {
4979 g.get_or_insert_with(|| RowBoundsCache::new(snap))
4980 .put_row_bounds(sheet_id, ci, snap, (_min_c, max_c));
4981 }
4982 if let Some(m) = max_c {
4983 max_r0 = Some(max_r0.map(|mm| mm.max(m as usize)).unwrap_or(m as usize));
4984 }
4985 }
4986 match (min_r0, max_r0) {
4987 (Some(a0), Some(b0)) => Some(((a0 as u32) + 1, (b0 as u32) + 1)),
4988 _ => None,
4989 }
4990 }
4991
4992 fn scan_column_used_bounds(
4993 a: &crate::arrow_store::ArrowSheet,
4994 ci: usize,
4995 ) -> (Option<u32>, Option<u32>) {
4996 let col = &a.columns[ci];
4997
4998 let mut min_r0: Option<u32> = None;
5000 for (chunk_idx, chunk) in col.chunks.iter().enumerate() {
5001 let tags = chunk.type_tag.values();
5002 for (off, &t) in tags.iter().enumerate() {
5003 let overlay_non_empty = chunk
5004 .overlay
5005 .get(off)
5006 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
5007 .unwrap_or(false)
5008 || chunk
5009 .computed_overlay
5010 .get(off)
5011 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
5012 .unwrap_or(false);
5013 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
5014 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
5015 break;
5016 };
5017 let row0 = chunk_start + off;
5018 min_r0 = Some(row0 as u32);
5019 break;
5020 }
5021 }
5022 if min_r0.is_some() {
5023 break;
5024 }
5025 }
5026 if min_r0.is_none() && !col.sparse_chunks.is_empty() {
5027 let mut sparse_idxs: Vec<usize> = col.sparse_chunks.keys().copied().collect();
5028 sparse_idxs.sort_unstable();
5029 for chunk_idx in sparse_idxs {
5030 let Some(chunk) = col.sparse_chunks.get(&chunk_idx) else {
5031 continue;
5032 };
5033 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
5034 continue;
5035 };
5036 let tags = chunk.type_tag.values();
5037 for (off, &t) in tags.iter().enumerate() {
5038 let overlay_non_empty = chunk
5039 .overlay
5040 .get(off)
5041 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
5042 .unwrap_or(false)
5043 || chunk
5044 .computed_overlay
5045 .get(off)
5046 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
5047 .unwrap_or(false);
5048 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
5049 let row0 = chunk_start + off;
5050 min_r0 = Some(row0 as u32);
5051 break;
5052 }
5053 }
5054 if min_r0.is_some() {
5055 break;
5056 }
5057 }
5058 }
5059
5060 let mut max_r0: Option<u32> = None;
5062 if !col.sparse_chunks.is_empty() {
5063 let mut sparse_idxs: Vec<usize> = col.sparse_chunks.keys().copied().collect();
5064 sparse_idxs.sort_unstable_by(|a, b| b.cmp(a));
5065 for chunk_idx in sparse_idxs {
5066 let Some(chunk) = col.sparse_chunks.get(&chunk_idx) else {
5067 continue;
5068 };
5069 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
5070 continue;
5071 };
5072 let tags = chunk.type_tag.values();
5073 for (rev_idx, &t) in tags.iter().enumerate().rev() {
5074 let overlay_non_empty = chunk
5075 .overlay
5076 .get(rev_idx)
5077 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
5078 .unwrap_or(false)
5079 || chunk
5080 .computed_overlay
5081 .get(rev_idx)
5082 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
5083 .unwrap_or(false);
5084 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
5085 let row0 = chunk_start + rev_idx;
5086 max_r0 = Some(row0 as u32);
5087 break;
5088 }
5089 }
5090 if max_r0.is_some() {
5091 break;
5092 }
5093 }
5094 }
5095 if max_r0.is_none() {
5096 for (chunk_idx, chunk) in col.chunks.iter().enumerate().rev() {
5097 let tags = chunk.type_tag.values();
5098 for (rev_idx, &t) in tags.iter().enumerate().rev() {
5099 let overlay_non_empty = chunk
5100 .overlay
5101 .get(rev_idx)
5102 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
5103 .unwrap_or(false)
5104 || chunk
5105 .computed_overlay
5106 .get(rev_idx)
5107 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
5108 .unwrap_or(false);
5109 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
5110 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
5111 break;
5112 };
5113 let row0 = chunk_start + rev_idx;
5114 max_r0 = Some(row0 as u32);
5115 break;
5116 }
5117 }
5118 if max_r0.is_some() {
5119 break;
5120 }
5121 }
5122 }
5123
5124 (min_r0, max_r0)
5125 }
5126
5127 fn arrow_used_col_bounds(
5129 &self,
5130 sheet: &str,
5131 start_row: u32,
5132 end_row: u32,
5133 ) -> Option<(u32, u32)> {
5134 let a = self.sheet_store().sheet(sheet)?;
5135 if a.columns.is_empty() {
5136 return None;
5137 }
5138 let sr0 = start_row.saturating_sub(1) as usize;
5139 let er0 = end_row.saturating_sub(1) as usize;
5140 if sr0 > er0 {
5141 return None;
5142 }
5143 let mut min_c0: Option<usize> = None;
5146 let mut max_c0: Option<usize> = None;
5147 for (ci, col) in a.columns.iter().enumerate() {
5149 let mut any_in_range = false;
5150
5151 let scan_chunk = |chunk_idx: usize, chunk: &crate::arrow_store::ColumnChunk| -> bool {
5152 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
5153 return false;
5154 };
5155 let chunk_len = chunk.type_tag.len();
5156 if chunk_len == 0 {
5157 return false;
5158 }
5159 let chunk_end = chunk_start + chunk_len.saturating_sub(1);
5160 if sr0 > chunk_end || er0 < chunk_start {
5162 return false;
5163 }
5164 let start_off = sr0.max(chunk_start) - chunk_start;
5165 let end_off = er0.min(chunk_end) - chunk_start;
5166 let tags = chunk.type_tag.values();
5167 for off in start_off..=end_off {
5168 let overlay_non_empty = chunk
5169 .overlay
5170 .get(off)
5171 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
5172 .unwrap_or(false)
5173 || chunk
5174 .computed_overlay
5175 .get(off)
5176 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
5177 .unwrap_or(false);
5178 if overlay_non_empty || tags[off] != crate::arrow_store::TypeTag::Empty as u8 {
5179 return true;
5180 }
5181 }
5182 false
5183 };
5184
5185 for (chunk_idx, chunk) in col.chunks.iter().enumerate() {
5186 if scan_chunk(chunk_idx, chunk) {
5187 any_in_range = true;
5188 break;
5189 }
5190 }
5191
5192 if !any_in_range && !col.sparse_chunks.is_empty() {
5193 for (&chunk_idx, chunk) in col.sparse_chunks.iter() {
5194 if scan_chunk(chunk_idx, chunk) {
5195 any_in_range = true;
5196 break;
5197 }
5198 }
5199 }
5200
5201 if any_in_range {
5202 min_c0 = Some(min_c0.map(|m| m.min(ci)).unwrap_or(ci));
5203 max_c0 = Some(max_c0.map(|m| m.max(ci)).unwrap_or(ci));
5204 }
5205 }
5206 match (min_c0, max_c0) {
5207 (Some(a0), Some(b0)) => Some(((a0 as u32) + 1, (b0 as u32) + 1)),
5208 _ => None,
5209 }
5210 }
5211
5212 fn formula_row_bounds_for_columns(
5213 &self,
5214 sheet: &str,
5215 start_col: u32,
5216 end_col: u32,
5217 ) -> Option<(u32, u32)> {
5218 let sheet_id = self.graph.sheet_id(sheet)?;
5219 let sc0 = start_col.saturating_sub(1);
5220 let ec0 = end_col.saturating_sub(1);
5221 let mut min_r0: Option<u32> = None;
5222 let mut max_r0: Option<u32> = None;
5223
5224 if let Some(index) = self.graph.sheet_index(sheet_id) {
5225 for vid in index.vertices_in_col_range(sc0, ec0) {
5226 if !matches!(
5227 self.graph.get_vertex_kind(vid),
5228 VertexKind::FormulaScalar | VertexKind::FormulaArray
5229 ) {
5230 continue;
5231 }
5232 let row0 = self.graph.vertex_coord(vid).row();
5233 min_r0 = Some(min_r0.map(|m| m.min(row0)).unwrap_or(row0));
5234 max_r0 = Some(max_r0.map(|m| m.max(row0)).unwrap_or(row0));
5235 }
5236 } else {
5237 for vid in self.graph.vertices_in_sheet(sheet_id) {
5238 if !matches!(
5239 self.graph.get_vertex_kind(vid),
5240 VertexKind::FormulaScalar | VertexKind::FormulaArray
5241 ) {
5242 continue;
5243 }
5244 let coord = self.graph.vertex_coord(vid);
5245 let col0 = coord.col();
5246 if col0 < sc0 || col0 > ec0 {
5247 continue;
5248 }
5249 let row0 = coord.row();
5250 min_r0 = Some(min_r0.map(|m| m.min(row0)).unwrap_or(row0));
5251 max_r0 = Some(max_r0.map(|m| m.max(row0)).unwrap_or(row0));
5252 }
5253 }
5254
5255 match (min_r0, max_r0) {
5256 (Some(a0), Some(b0)) => Some((a0 + 1, b0 + 1)),
5257 _ => None,
5258 }
5259 }
5260
5261 fn formula_col_bounds_for_rows(
5262 &self,
5263 sheet: &str,
5264 start_row: u32,
5265 end_row: u32,
5266 ) -> Option<(u32, u32)> {
5267 let sheet_id = self.graph.sheet_id(sheet)?;
5268 let sr0 = start_row.saturating_sub(1);
5269 let er0 = end_row.saturating_sub(1);
5270 let mut min_c0: Option<u32> = None;
5271 let mut max_c0: Option<u32> = None;
5272
5273 if let Some(index) = self.graph.sheet_index(sheet_id) {
5274 for vid in index.vertices_in_row_range(sr0, er0) {
5275 if !matches!(
5276 self.graph.get_vertex_kind(vid),
5277 VertexKind::FormulaScalar | VertexKind::FormulaArray
5278 ) {
5279 continue;
5280 }
5281 let col0 = self.graph.vertex_coord(vid).col();
5282 min_c0 = Some(min_c0.map(|m| m.min(col0)).unwrap_or(col0));
5283 max_c0 = Some(max_c0.map(|m| m.max(col0)).unwrap_or(col0));
5284 }
5285 } else {
5286 for vid in self.graph.vertices_in_sheet(sheet_id) {
5287 if !matches!(
5288 self.graph.get_vertex_kind(vid),
5289 VertexKind::FormulaScalar | VertexKind::FormulaArray
5290 ) {
5291 continue;
5292 }
5293 let coord = self.graph.vertex_coord(vid);
5294 let row0 = coord.row();
5295 if row0 < sr0 || row0 > er0 {
5296 continue;
5297 }
5298 let col0 = coord.col();
5299 min_c0 = Some(min_c0.map(|m| m.min(col0)).unwrap_or(col0));
5300 max_c0 = Some(max_c0.map(|m| m.max(col0)).unwrap_or(col0));
5301 }
5302 }
5303
5304 match (min_c0, max_c0) {
5305 (Some(a0), Some(b0)) => Some((a0 + 1, b0 + 1)),
5306 _ => None,
5307 }
5308 }
5309
5310 fn union_used_bounds(
5311 first: Option<(u32, u32)>,
5312 second: Option<(u32, u32)>,
5313 ) -> Option<(u32, u32)> {
5314 match (first, second) {
5315 (Some((a0, b0)), Some((a1, b1))) => Some((a0.min(a1), b0.max(b1))),
5316 (Some(bounds), None) | (None, Some(bounds)) => Some(bounds),
5317 (None, None) => None,
5318 }
5319 }
5320
5321 fn mirror_value_to_overlay(&mut self, sheet: &str, row: u32, col: u32, value: &LiteralValue) {
5324 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
5325 return;
5326 }
5327 if self.arrow_sheets.sheet(sheet).is_none() {
5328 self.arrow_sheets
5329 .sheets
5330 .push(crate::arrow_store::ArrowSheet {
5331 name: std::sync::Arc::<str>::from(sheet),
5332 columns: Vec::new(),
5333 nrows: 0,
5334 chunk_starts: Vec::new(),
5335 chunk_rows: 32 * 1024,
5336 });
5337 }
5338
5339 let row0 = row.saturating_sub(1) as usize;
5340 let col0 = col.saturating_sub(1) as usize;
5341
5342 let asheet = self
5343 .arrow_sheets
5344 .sheet_mut(sheet)
5345 .expect("ArrowSheet must exist");
5346
5347 let cur_cols = asheet.columns.len();
5348 if col0 >= cur_cols {
5349 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
5350 }
5351
5352 if row0 >= asheet.nrows as usize {
5353 if asheet.columns.is_empty() {
5354 asheet.insert_columns(0, 1);
5355 }
5356 asheet.ensure_row_capacity(row0 + 1);
5357 }
5358 if let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) {
5359 use crate::arrow_store::OverlayValue;
5360 let ov = match value {
5361 LiteralValue::Empty => OverlayValue::Empty,
5362 LiteralValue::Int(i) => OverlayValue::Number(*i as f64),
5363 LiteralValue::Number(n) => OverlayValue::Number(*n),
5364 LiteralValue::Boolean(b) => OverlayValue::Boolean(*b),
5365 LiteralValue::Text(s) => OverlayValue::Text(std::sync::Arc::from(s.clone())),
5366 LiteralValue::Error(e) => {
5367 OverlayValue::Error(crate::arrow_store::map_error_code(e.kind))
5368 }
5369 LiteralValue::Date(d) => {
5370 let dt = d.and_hms_opt(0, 0, 0).unwrap();
5371 let serial = crate::builtins::datetime::datetime_to_serial_for(
5372 self.config.date_system,
5373 &dt,
5374 );
5375 OverlayValue::DateTime(serial)
5376 }
5377 LiteralValue::DateTime(dt) => {
5378 let serial = crate::builtins::datetime::datetime_to_serial_for(
5379 self.config.date_system,
5380 dt,
5381 );
5382 OverlayValue::DateTime(serial)
5383 }
5384 LiteralValue::Time(t) => {
5385 let serial = t.num_seconds_from_midnight() as f64 / 86_400.0;
5386 OverlayValue::DateTime(serial)
5387 }
5388 LiteralValue::Duration(d) => {
5389 let serial = d.num_seconds() as f64 / 86_400.0;
5390 OverlayValue::Duration(serial)
5391 }
5392 LiteralValue::Pending => OverlayValue::Pending,
5393 LiteralValue::Array(_) => OverlayValue::Error(crate::arrow_store::map_error_code(
5394 formualizer_common::ExcelErrorKind::Value,
5395 )),
5396 };
5397 let computed_delta = if let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) {
5398 let _ = ch.overlay.set(in_off, ov);
5399 ch.computed_overlay.remove(in_off)
5404 } else {
5405 return;
5406 };
5407 let abs_threshold = 1024usize;
5409 let frac_den = 50usize;
5410 let freed = asheet.maybe_compact_chunk(col0, ch_idx, abs_threshold, frac_den);
5411 if freed > 0 {
5412 self.overlay_compactions = self.overlay_compactions.saturating_add(1);
5413 }
5414 self.adjust_computed_overlay_bytes(computed_delta);
5415 }
5416 }
5417
5418 fn clear_delta_overlay_cell(&mut self, sheet: &str, row: u32, col: u32) {
5423 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
5424 return;
5425 }
5426 let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) else {
5427 return;
5428 };
5429 let row0 = row.saturating_sub(1) as usize;
5430 let col0 = col.saturating_sub(1) as usize;
5431 if row0 >= asheet.nrows as usize {
5432 return;
5433 }
5434 if col0 >= asheet.columns.len() {
5435 return;
5436 }
5437 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
5438 return;
5439 };
5440 if let Some(ch) = asheet.columns[col0].chunk_mut(ch_idx) {
5441 let _ = ch.overlay.remove(in_off);
5442 }
5443 }
5444
5445 fn clear_computed_overlay_col_row_range(
5446 &mut self,
5447 sheet: &str,
5448 col0: usize,
5449 start_row0: usize,
5450 end_row0_exclusive: usize,
5451 ) {
5452 if !(self.config.arrow_storage_enabled && self.config.write_formula_overlay_enabled) {
5453 return;
5454 }
5455 if start_row0 >= end_row0_exclusive {
5456 return;
5457 }
5458
5459 let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) else {
5460 return;
5461 };
5462 if col0 >= asheet.columns.len() || start_row0 >= asheet.nrows as usize {
5463 return;
5464 }
5465 let end_row0_exclusive = end_row0_exclusive.min(asheet.nrows as usize);
5466 if start_row0 >= end_row0_exclusive {
5467 return;
5468 }
5469
5470 let starts = asheet.chunk_starts.clone();
5471 let nrows = asheet.nrows as usize;
5472 let mut delta = 0isize;
5473 let Some(col) = asheet.columns.get_mut(col0) else {
5474 return;
5475 };
5476 for (chunk_idx, ch) in col.chunks.iter_mut().enumerate() {
5477 let Some(&chunk_start) = starts.get(chunk_idx) else {
5478 continue;
5479 };
5480 let chunk_end = starts
5481 .get(chunk_idx + 1)
5482 .copied()
5483 .unwrap_or(nrows)
5484 .min(chunk_start.saturating_add(ch.len()));
5485 let clear_start = start_row0.max(chunk_start);
5486 let clear_end = end_row0_exclusive.min(chunk_end);
5487 if clear_start >= clear_end {
5488 continue;
5489 }
5490 if clear_start == chunk_start && clear_end == chunk_end {
5491 delta = delta.saturating_sub(ch.computed_overlay.clear() as isize);
5492 } else {
5493 let start_in_chunk = clear_start.saturating_sub(chunk_start).min(ch.len());
5494 let end_in_chunk = clear_end.saturating_sub(chunk_start).min(ch.len());
5495 delta = delta.saturating_add(
5496 ch.computed_overlay
5497 .remove_range(start_in_chunk..end_in_chunk),
5498 );
5499 }
5500 }
5501 for (chunk_idx, ch) in &mut col.sparse_chunks {
5502 let Some(&chunk_start) = starts.get(*chunk_idx) else {
5503 continue;
5504 };
5505 let chunk_end = starts
5506 .get(*chunk_idx + 1)
5507 .copied()
5508 .unwrap_or(nrows)
5509 .min(chunk_start.saturating_add(ch.len()));
5510 let clear_start = start_row0.max(chunk_start);
5511 let clear_end = end_row0_exclusive.min(chunk_end);
5512 if clear_start >= clear_end {
5513 continue;
5514 }
5515 if clear_start == chunk_start && clear_end == chunk_end {
5516 delta = delta.saturating_sub(ch.computed_overlay.clear() as isize);
5517 } else {
5518 let start_in_chunk = clear_start.saturating_sub(chunk_start).min(ch.len());
5519 let end_in_chunk = clear_end.saturating_sub(chunk_start).min(ch.len());
5520 delta = delta.saturating_add(
5521 ch.computed_overlay
5522 .remove_range(start_in_chunk..end_in_chunk),
5523 );
5524 }
5525 }
5526 self.adjust_computed_overlay_bytes(delta);
5527 }
5528
5529 fn clear_computed_overlay_cells_in_region(
5530 &mut self,
5531 cells: &[(SheetId, u32, u32)],
5532 affected_region: &Region,
5533 ) {
5534 let mut by_col: BTreeMap<(SheetId, u32), Vec<u32>> = BTreeMap::new();
5535 for (formula_sheet_id, row, col) in cells {
5536 let row0 = row.saturating_sub(1);
5537 let col0 = col.saturating_sub(1);
5538 let placement_region = Region::point(*formula_sheet_id, row0, col0);
5539 if placement_region.intersects(affected_region) {
5540 by_col
5541 .entry((*formula_sheet_id, col0))
5542 .or_default()
5543 .push(row0);
5544 }
5545 }
5546
5547 for ((formula_sheet_id, col0), mut rows) in by_col {
5548 rows.sort_unstable();
5549 rows.dedup();
5550 let sheet_name = self.graph.sheet_name(formula_sheet_id).to_string();
5551 let mut start = rows[0];
5552 let mut prev = rows[0];
5553 for row in rows.into_iter().skip(1) {
5554 if row == prev.saturating_add(1) {
5555 prev = row;
5556 continue;
5557 }
5558 self.clear_computed_overlay_col_row_range(
5559 &sheet_name,
5560 col0 as usize,
5561 start as usize,
5562 prev.saturating_add(1) as usize,
5563 );
5564 start = row;
5565 prev = row;
5566 }
5567 self.clear_computed_overlay_col_row_range(
5568 &sheet_name,
5569 col0 as usize,
5570 start as usize,
5571 prev.saturating_add(1) as usize,
5572 );
5573 }
5574 }
5575
5576 fn clear_computed_overlay_after_row(&mut self, sheet: &str, start_row0: usize) {
5577 if !(self.config.arrow_storage_enabled && self.config.write_formula_overlay_enabled) {
5578 return;
5579 }
5580
5581 let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) else {
5582 return;
5583 };
5584 if start_row0 >= asheet.nrows as usize {
5585 return;
5586 }
5587
5588 let starts = asheet.chunk_starts.clone();
5589 let nrows = asheet.nrows as usize;
5590 let mut delta = 0isize;
5591 for col in &mut asheet.columns {
5592 for (chunk_idx, ch) in col.chunks.iter_mut().enumerate() {
5593 let Some(&chunk_start) = starts.get(chunk_idx) else {
5594 continue;
5595 };
5596 let chunk_end = starts
5597 .get(chunk_idx + 1)
5598 .copied()
5599 .unwrap_or(nrows)
5600 .min(chunk_start.saturating_add(ch.len()));
5601 if chunk_end <= start_row0 {
5602 continue;
5603 }
5604 if chunk_start >= start_row0 {
5605 delta = delta.saturating_sub(ch.computed_overlay.clear() as isize);
5606 } else {
5607 let start_in_chunk = start_row0.saturating_sub(chunk_start).min(ch.len());
5608 delta = delta
5609 .saturating_add(ch.computed_overlay.remove_range(start_in_chunk..ch.len()));
5610 }
5611 }
5612
5613 for (chunk_idx, ch) in &mut col.sparse_chunks {
5614 let Some(&chunk_start) = starts.get(*chunk_idx) else {
5615 continue;
5616 };
5617 let chunk_end = starts
5618 .get(*chunk_idx + 1)
5619 .copied()
5620 .unwrap_or(nrows)
5621 .min(chunk_start.saturating_add(ch.len()));
5622 if chunk_end <= start_row0 {
5623 continue;
5624 }
5625 if chunk_start >= start_row0 {
5626 delta = delta.saturating_sub(ch.computed_overlay.clear() as isize);
5627 } else {
5628 let start_in_chunk = start_row0.saturating_sub(chunk_start).min(ch.len());
5629 delta = delta
5630 .saturating_add(ch.computed_overlay.remove_range(start_in_chunk..ch.len()));
5631 }
5632 }
5633 }
5634 self.adjust_computed_overlay_bytes(delta);
5635 }
5636
5637 fn clear_computed_overlay_after_col(&mut self, sheet: &str, start_col0: usize) {
5638 if !(self.config.arrow_storage_enabled && self.config.write_formula_overlay_enabled) {
5639 return;
5640 }
5641
5642 let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) else {
5643 return;
5644 };
5645 if start_col0 >= asheet.columns.len() {
5646 return;
5647 }
5648
5649 let mut delta = 0isize;
5650 for col in asheet.columns.iter_mut().skip(start_col0) {
5651 for ch in &mut col.chunks {
5652 delta = delta.saturating_sub(ch.computed_overlay.clear() as isize);
5653 }
5654 for ch in col.sparse_chunks.values_mut() {
5655 delta = delta.saturating_sub(ch.computed_overlay.clear() as isize);
5656 }
5657 }
5658 self.adjust_computed_overlay_bytes(delta);
5659 }
5660
5661 #[inline]
5662 fn literal_to_overlay_value(&self, value: &LiteralValue) -> crate::arrow_store::OverlayValue {
5663 use crate::arrow_store::OverlayValue;
5664 match value {
5665 LiteralValue::Empty => OverlayValue::Empty,
5666 LiteralValue::Int(i) => OverlayValue::Number(*i as f64),
5667 LiteralValue::Number(n) => OverlayValue::Number(*n),
5668 LiteralValue::Boolean(b) => OverlayValue::Boolean(*b),
5669 LiteralValue::Text(s) => OverlayValue::Text(std::sync::Arc::from(s.clone())),
5670 LiteralValue::Error(e) => {
5671 OverlayValue::Error(crate::arrow_store::map_error_code(e.kind))
5672 }
5673 LiteralValue::Date(d) => {
5674 let dt = d.and_hms_opt(0, 0, 0).unwrap();
5675 let serial =
5676 crate::builtins::datetime::datetime_to_serial_for(self.config.date_system, &dt);
5677 OverlayValue::DateTime(serial)
5678 }
5679 LiteralValue::DateTime(dt) => {
5680 let serial =
5681 crate::builtins::datetime::datetime_to_serial_for(self.config.date_system, dt);
5682 OverlayValue::DateTime(serial)
5683 }
5684 LiteralValue::Time(t) => {
5685 let serial = t.num_seconds_from_midnight() as f64 / 86_400.0;
5686 OverlayValue::DateTime(serial)
5687 }
5688 LiteralValue::Duration(d) => {
5689 let serial = d.num_seconds() as f64 / 86_400.0;
5690 OverlayValue::Duration(serial)
5691 }
5692 LiteralValue::Pending => OverlayValue::Pending,
5693 LiteralValue::Array(_) => OverlayValue::Error(crate::arrow_store::map_error_code(
5694 formualizer_common::ExcelErrorKind::Value,
5695 )),
5696 }
5697 }
5698
5699 fn read_delta_overlay_cell(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
5702 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
5703 return None;
5704 }
5705 let asheet = self.arrow_sheets.sheet(sheet)?;
5706 let row0 = row.saturating_sub(1) as usize;
5707 let col0 = col.saturating_sub(1) as usize;
5708 if row0 >= asheet.nrows as usize || col0 >= asheet.columns.len() {
5709 return None;
5710 }
5711 let (ch_idx, in_off) = asheet.chunk_of_row(row0)?;
5712 let ch = asheet.columns[col0].chunk(ch_idx)?;
5713 ch.overlay.get_scalar(in_off).map(|ov| ov.to_literal())
5714 }
5715
5716 fn read_computed_overlay_cell(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
5719 if !(self.config.arrow_storage_enabled
5720 && self.config.delta_overlay_enabled
5721 && self.config.write_formula_overlay_enabled)
5722 {
5723 return None;
5724 }
5725 let asheet = self.arrow_sheets.sheet(sheet)?;
5726 let row0 = row.saturating_sub(1) as usize;
5727 let col0 = col.saturating_sub(1) as usize;
5728 if row0 >= asheet.nrows as usize || col0 >= asheet.columns.len() {
5729 return None;
5730 }
5731 let (ch_idx, in_off) = asheet.chunk_of_row(row0)?;
5732 let ch = asheet.columns[col0].chunk(ch_idx)?;
5733 ch.computed_overlay
5734 .get_scalar(in_off)
5735 .map(|ov| ov.to_literal())
5736 }
5737
5738 fn set_delta_overlay_cell_raw(
5739 &mut self,
5740 sheet: &str,
5741 row: u32,
5742 col: u32,
5743 value: Option<LiteralValue>,
5744 ) {
5745 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
5746 return;
5747 }
5748
5749 self.ensure_arrow_sheet(sheet);
5750 let ov_opt = value.as_ref().map(|v| self.literal_to_overlay_value(v));
5751 let row0 = row.saturating_sub(1) as usize;
5752 let col0 = col.saturating_sub(1) as usize;
5753 let asheet = self
5754 .arrow_sheets
5755 .sheet_mut(sheet)
5756 .expect("ArrowSheet must exist");
5757
5758 let cur_cols = asheet.columns.len();
5759 if col0 >= cur_cols {
5760 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
5761 }
5762 if row0 >= asheet.nrows as usize {
5763 if asheet.columns.is_empty() {
5764 asheet.insert_columns(0, 1);
5765 }
5766 asheet.ensure_row_capacity(row0 + 1);
5767 }
5768
5769 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
5770 return;
5771 };
5772 let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) else {
5773 return;
5774 };
5775
5776 if let Some(ov) = ov_opt {
5777 let _ = ch.overlay.set(in_off, ov);
5778 } else {
5779 let _ = ch.overlay.remove(in_off);
5780 }
5781 }
5782
5783 fn set_computed_overlay_cell_raw(
5784 &mut self,
5785 sheet: &str,
5786 row: u32,
5787 col: u32,
5788 value: Option<LiteralValue>,
5789 ) {
5790 if !(self.config.arrow_storage_enabled
5791 && self.config.delta_overlay_enabled
5792 && self.config.write_formula_overlay_enabled)
5793 {
5794 return;
5795 }
5796
5797 self.ensure_arrow_sheet(sheet);
5798 let ov_opt = value.as_ref().map(|v| self.literal_to_overlay_value(v));
5799 let row0 = row.saturating_sub(1) as usize;
5800 let col0 = col.saturating_sub(1) as usize;
5801 let asheet = self
5802 .arrow_sheets
5803 .sheet_mut(sheet)
5804 .expect("ArrowSheet must exist");
5805
5806 let cur_cols = asheet.columns.len();
5807 if col0 >= cur_cols {
5808 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
5809 }
5810 if row0 >= asheet.nrows as usize {
5811 if asheet.columns.is_empty() {
5812 asheet.insert_columns(0, 1);
5813 }
5814 asheet.ensure_row_capacity(row0 + 1);
5815 }
5816
5817 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
5818 return;
5819 };
5820 let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) else {
5821 return;
5822 };
5823
5824 let delta = if let Some(ov) = ov_opt {
5825 ch.computed_overlay.set(in_off, ov)
5826 } else {
5827 ch.computed_overlay.remove(in_off)
5828 };
5829 self.adjust_computed_overlay_bytes(delta);
5830 }
5831
5832 fn apply_arrow_undo_batch(&mut self, batch: &crate::engine::ArrowUndoBatch, undo: bool) {
5833 use crate::engine::ArrowOp;
5834
5835 let iter: Box<dyn Iterator<Item = &ArrowOp>> = if undo {
5836 Box::new(batch.ops.iter().rev())
5837 } else {
5838 Box::new(batch.ops.iter())
5839 };
5840
5841 for op in iter {
5842 match op {
5843 ArrowOp::SetDeltaCell {
5844 sheet_id,
5845 row0,
5846 col0,
5847 old,
5848 new,
5849 } => {
5850 let sheet = self.graph.sheet_name(*sheet_id).to_string();
5851 let v = if undo { old.clone() } else { new.clone() };
5852 self.set_delta_overlay_cell_raw(&sheet, row0 + 1, col0 + 1, v);
5853 }
5854 ArrowOp::SetComputedCell {
5855 sheet_id,
5856 row0,
5857 col0,
5858 old,
5859 new,
5860 } => {
5861 let sheet = self.graph.sheet_name(*sheet_id).to_string();
5862 let v = if undo { old.clone() } else { new.clone() };
5863 self.set_computed_overlay_cell_raw(&sheet, row0 + 1, col0 + 1, v);
5864 }
5865 ArrowOp::RestoreComputedRect {
5866 sheet_id,
5867 sr0,
5868 sc0,
5869 er0,
5870 ec0,
5871 old,
5872 new,
5873 } => {
5874 let sheet = self.graph.sheet_name(*sheet_id).to_string();
5875 let vals = if undo { old } else { new };
5876 let height = (*er0).saturating_sub(*sr0) as usize + 1;
5877 let width = (*ec0).saturating_sub(*sc0) as usize + 1;
5878 for r in 0..height {
5879 for c in 0..width {
5880 let v = vals
5881 .get(r)
5882 .and_then(|row| row.get(c))
5883 .cloned()
5884 .unwrap_or(LiteralValue::Empty);
5885 self.set_computed_overlay_cell_raw(
5886 &sheet,
5887 *sr0 + 1 + r as u32,
5888 *sc0 + 1 + c as u32,
5889 Some(v),
5890 );
5891 }
5892 }
5893 }
5894 ArrowOp::InsertRows {
5895 sheet_id,
5896 before0,
5897 count,
5898 } => {
5899 let sheet = self.graph.sheet_name(*sheet_id).to_string();
5900 self.ensure_arrow_sheet(&sheet);
5901 if let Some(asheet) = self.arrow_sheets.sheet_mut(&sheet) {
5902 if undo {
5903 asheet.delete_rows(*before0 as usize, *count as usize);
5904 } else {
5905 asheet.insert_rows(*before0 as usize, *count as usize);
5906 }
5907 }
5908 }
5909 ArrowOp::InsertCols {
5910 sheet_id,
5911 before0,
5912 count,
5913 } => {
5914 let sheet = self.graph.sheet_name(*sheet_id).to_string();
5915 self.ensure_arrow_sheet(&sheet);
5916 if let Some(asheet) = self.arrow_sheets.sheet_mut(&sheet) {
5917 if undo {
5918 asheet.delete_columns(*before0 as usize, *count as usize);
5919 } else {
5920 asheet.insert_columns(*before0 as usize, *count as usize);
5921 }
5922 }
5923 }
5924 }
5925 }
5926 }
5927
5928 fn record_spill_ops_into_arrow_undo(
5929 &mut self,
5930 undo: &mut crate::engine::ArrowUndoBatch,
5931 events: &[crate::engine::ChangeEvent],
5932 ) {
5933 use crate::engine::ChangeEvent;
5934 use formualizer_common::LiteralValue;
5935
5936 #[allow(clippy::type_complexity)]
5937 let rect_from_snapshot =
5938 |snap: &crate::engine::graph::editor::change_log::SpillSnapshot|
5939 -> Option<(SheetId, u32, u32, u32, u32, Vec<Vec<LiteralValue>>)> {
5940 if snap.target_cells.is_empty() {
5941 return None;
5942 }
5943 let sheet_id = snap.target_cells[0].sheet_id;
5944 let sr0 = snap.target_cells[0].coord.row();
5945 let sc0 = snap.target_cells[0].coord.col();
5946 if snap.values.is_empty() || snap.values[0].is_empty() {
5947 return None;
5948 }
5949 let h = snap.values.len() as u32;
5950 let w = snap.values[0].len() as u32;
5951 let er0 = sr0.saturating_add(h.saturating_sub(1));
5952 let ec0 = sc0.saturating_add(w.saturating_sub(1));
5953 Some((sheet_id, sr0, sc0, er0, ec0, snap.values.clone()))
5954 };
5955
5956 for ev in events {
5957 match ev {
5958 ChangeEvent::SpillCommitted { old, new, .. } => {
5959 if let Some((sid, sr0, sc0, er0, ec0, new_vals)) = rect_from_snapshot(new) {
5960 let old_vals = if let Some(old_snap) = old {
5961 rect_from_snapshot(old_snap)
5962 .map(|(_, _, _, _, _, v)| v)
5963 .unwrap_or_else(|| {
5964 vec![
5965 vec![LiteralValue::Empty; new_vals[0].len()];
5966 new_vals.len()
5967 ]
5968 })
5969 } else {
5970 vec![vec![LiteralValue::Empty; new_vals[0].len()]; new_vals.len()]
5971 };
5972 undo.record_restore_computed_rect(
5973 sid, sr0, sc0, er0, ec0, old_vals, new_vals,
5974 );
5975 }
5976 }
5977 ChangeEvent::SpillCleared { old, .. } => {
5978 if let Some((sid, sr0, sc0, er0, ec0, old_vals)) = rect_from_snapshot(old) {
5979 let new_vals =
5980 vec![vec![LiteralValue::Empty; old_vals[0].len()]; old_vals.len()];
5981 undo.record_restore_computed_rect(
5982 sid, sr0, sc0, er0, ec0, old_vals, new_vals,
5983 );
5984 }
5985 }
5986 _ => {}
5987 }
5988 }
5989 }
5990
5991 fn mirror_value_to_computed_overlay(
5996 &mut self,
5997 sheet: &str,
5998 row: u32,
5999 col: u32,
6000 value: &LiteralValue,
6001 ) {
6002 if !(self.config.arrow_storage_enabled
6003 && self.config.delta_overlay_enabled
6004 && self.config.write_formula_overlay_enabled)
6005 {
6006 return;
6007 }
6008 if self.computed_overlay_mirroring_disabled {
6009 return;
6010 }
6011
6012 let ov = self.literal_to_overlay_value(value);
6013 self.write_computed_overlay_value_0based(
6014 sheet,
6015 row.saturating_sub(1),
6016 col.saturating_sub(1),
6017 ov,
6018 );
6019 }
6020
6021 fn write_computed_overlay_value_0based(
6022 &mut self,
6023 sheet: &str,
6024 row0: u32,
6025 col0: u32,
6026 value: OverlayValue,
6027 ) {
6028 if !(self.config.arrow_storage_enabled
6029 && self.config.delta_overlay_enabled
6030 && self.config.write_formula_overlay_enabled)
6031 {
6032 return;
6033 }
6034 if self.computed_overlay_mirroring_disabled {
6035 return;
6036 }
6037
6038 self.ensure_arrow_sheet(sheet);
6039
6040 let row0 = row0 as usize;
6041 let col0 = col0 as usize;
6042 let asheet = self
6043 .arrow_sheets
6044 .sheet_mut(sheet)
6045 .expect("ArrowSheet must exist");
6046
6047 let cur_cols = asheet.columns.len();
6048 if col0 >= cur_cols {
6049 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
6050 }
6051
6052 if row0 >= asheet.nrows as usize {
6053 if asheet.columns.is_empty() {
6054 asheet.insert_columns(0, 1);
6055 }
6056 asheet.ensure_row_capacity(row0 + 1);
6057 }
6058
6059 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
6060 return;
6061 };
6062 let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) else {
6063 return;
6064 };
6065
6066 let delta = ch.computed_overlay.set_scalar(in_off, value);
6067 self.adjust_computed_overlay_bytes(delta);
6068
6069 if let Some(cap) = self.config.max_overlay_memory_bytes
6070 && self.computed_overlay_bytes_estimate > cap
6071 {
6072 self.disable_computed_overlay_mirroring_due_to_budget(cap);
6073 }
6074 }
6075
6076 pub(crate) fn plan_computed_write_coalescing(
6077 &self,
6078 buffer: &ComputedWriteBuffer,
6079 ) -> ComputedWriteCoalescingPlan {
6080 self.plan_computed_write_coalescing_from_writes(buffer.writes().iter().cloned())
6081 }
6082
6083 fn plan_owned_computed_write_coalescing(
6084 &self,
6085 writes: Vec<ComputedWrite>,
6086 ) -> ComputedWriteCoalescingPlan {
6087 self.plan_computed_write_coalescing_from_writes(writes)
6088 }
6089
6090 fn plan_computed_write_coalescing_from_writes(
6091 &self,
6092 writes: impl IntoIterator<Item = ComputedWrite>,
6093 ) -> ComputedWriteCoalescingPlan {
6094 let mut groups: BTreeMap<ComputedWriteChunkKey, Vec<ComputedWriteChunkEntryPlan>> =
6095 BTreeMap::new();
6096 let mut input_cells = 0usize;
6097
6098 for write in writes {
6099 match write {
6100 ComputedWrite::Cell {
6101 seq,
6102 sheet_id,
6103 row0,
6104 col0,
6105 value,
6106 } => {
6107 input_cells = input_cells.saturating_add(1);
6108 self.push_computed_write_plan_entry(
6109 &mut groups,
6110 seq,
6111 sheet_id,
6112 row0,
6113 col0,
6114 value,
6115 );
6116 }
6117 ComputedWrite::Rect {
6118 seq,
6119 sheet_id,
6120 sr0,
6121 sc0,
6122 values,
6123 } => {
6124 for (r_off, row) in values.into_iter().enumerate() {
6125 for (c_off, value) in row.into_iter().enumerate() {
6126 input_cells = input_cells.saturating_add(1);
6127 self.push_computed_write_plan_entry(
6128 &mut groups,
6129 seq,
6130 sheet_id,
6131 sr0.saturating_add(r_off as u32),
6132 sc0.saturating_add(c_off as u32),
6133 value,
6134 );
6135 }
6136 }
6137 }
6138 }
6139 }
6140
6141 let mut plan = ComputedWriteCoalescingPlan {
6142 chunks: Vec::with_capacity(groups.len()),
6143 input_cells,
6144 coalesced_cells: 0,
6145 overwritten_cells: 0,
6146 };
6147 for (key, entries) in groups {
6148 let (chunk_plan, overwritten) = ComputedWriteChunkPlan::from_group(key, entries);
6149 plan.coalesced_cells = plan
6150 .coalesced_cells
6151 .saturating_add(chunk_plan.entries.len());
6152 plan.overwritten_cells = plan.overwritten_cells.saturating_add(overwritten);
6153 plan.chunks.push(chunk_plan);
6154 }
6155 debug_assert_eq!(
6156 plan.input_cells,
6157 plan.coalesced_cells.saturating_add(plan.overwritten_cells)
6158 );
6159 plan
6160 }
6161
6162 fn push_computed_write_plan_entry(
6163 &self,
6164 groups: &mut BTreeMap<ComputedWriteChunkKey, Vec<ComputedWriteChunkEntryPlan>>,
6165 seq: u64,
6166 sheet_id: SheetId,
6167 row0: u32,
6168 col0: u32,
6169 value: OverlayValue,
6170 ) {
6171 let (chunk_idx, chunk_start_row0, row_in_chunk) =
6172 self.locate_computed_write_chunk(sheet_id, row0);
6173 let key = ComputedWriteChunkKey {
6174 sheet_id,
6175 col0,
6176 chunk_idx,
6177 chunk_start_row0,
6178 };
6179 groups
6180 .entry(key)
6181 .or_default()
6182 .push(ComputedWriteChunkEntryPlan {
6183 row_in_chunk,
6184 seq,
6185 value,
6186 });
6187 }
6188
6189 fn locate_computed_write_chunk(&self, sheet_id: SheetId, row0: u32) -> (usize, u32, usize) {
6190 let sheet_name = self.graph.sheet_name(sheet_id);
6191 if let Some(sheet) = self.arrow_sheets.sheet(sheet_name) {
6192 return Self::locate_row_in_sheet_for_computed_write_plan(sheet, row0 as usize);
6193 }
6194 Self::locate_row_in_empty_sheet_for_computed_write_plan(row0 as usize, 32 * 1024)
6195 }
6196
6197 fn locate_row_in_sheet_for_computed_write_plan(
6198 sheet: &crate::arrow_store::ArrowSheet,
6199 row0: usize,
6200 ) -> (usize, u32, usize) {
6201 if row0 < sheet.nrows as usize
6202 && let Some((chunk_idx, row_in_chunk)) = sheet.chunk_of_row(row0)
6203 {
6204 let chunk_start = sheet.chunk_starts.get(chunk_idx).copied().unwrap_or(0);
6205 return (chunk_idx, chunk_start as u32, row_in_chunk);
6206 }
6207
6208 let chunk_rows = sheet.chunk_rows.max(1);
6209 if sheet.chunk_starts.is_empty() {
6210 return Self::locate_row_in_empty_sheet_for_computed_write_plan(row0, chunk_rows);
6211 }
6212
6213 let mut chunk_idx = sheet.chunk_starts.len().saturating_sub(1);
6214 let mut chunk_start = sheet.chunk_starts[chunk_idx];
6215 while chunk_start.saturating_add(chunk_rows) <= row0 {
6216 chunk_idx = chunk_idx.saturating_add(1);
6217 chunk_start = chunk_start.saturating_add(chunk_rows);
6218 }
6219 (
6220 chunk_idx,
6221 chunk_start as u32,
6222 row0.saturating_sub(chunk_start),
6223 )
6224 }
6225
6226 fn locate_row_in_empty_sheet_for_computed_write_plan(
6227 row0: usize,
6228 chunk_rows: usize,
6229 ) -> (usize, u32, usize) {
6230 let chunk_rows = chunk_rows.max(1);
6231 let chunk_idx = row0 / chunk_rows;
6232 let chunk_start = chunk_idx.saturating_mul(chunk_rows);
6233 (
6234 chunk_idx,
6235 chunk_start as u32,
6236 row0.saturating_sub(chunk_start),
6237 )
6238 }
6239
6240 #[cfg(test)]
6241 pub(crate) fn debug_plan_computed_write_coalescing(
6242 &self,
6243 buffer: &ComputedWriteBuffer,
6244 ) -> ComputedWriteCoalescingPlan {
6245 self.plan_computed_write_coalescing(buffer)
6246 }
6247
6248 pub(crate) fn flush_computed_write_buffer(
6249 &mut self,
6250 buffer: &mut ComputedWriteBuffer,
6251 ) -> Result<(), ExcelError> {
6252 if buffer.is_empty() {
6253 return Ok(());
6254 }
6255
6256 let plan = self.plan_owned_computed_write_coalescing(buffer.take_writes());
6257 self.flush_computed_write_plan(plan);
6258
6259 Ok(())
6260 }
6261
6262 fn flush_computed_write_plan(&mut self, plan: ComputedWriteCoalescingPlan) {
6263 for chunk in plan.chunks {
6264 self.flush_computed_write_chunk_plan(chunk);
6265 }
6266 }
6267
6268 fn flush_computed_write_chunk_plan(&mut self, chunk: ComputedWriteChunkPlan) {
6269 match &chunk.shape {
6270 ComputedWriteChunkPlanShape::Point => {
6271 self.flush_computed_write_chunk_plan_as_points(chunk);
6272 }
6273 ComputedWriteChunkPlanShape::SparseOffsets { .. } => {
6274 self.flush_computed_write_chunk_plan_as_sparse_fragment_or_points(chunk);
6275 }
6276 ComputedWriteChunkPlanShape::DenseRange { .. } => {
6277 self.flush_computed_write_chunk_plan_as_dense_fragment(chunk);
6278 }
6279 ComputedWriteChunkPlanShape::RunRange { len, runs, .. } => {
6280 if Self::should_emit_computed_run_fragment(*len, *runs) {
6281 self.flush_computed_write_chunk_plan_as_run_fragment(chunk);
6282 } else {
6283 self.flush_computed_write_chunk_plan_as_dense_fragment(chunk);
6284 }
6285 }
6286 }
6287 }
6288
6289 #[inline]
6290 fn should_emit_computed_run_fragment(len: usize, runs: usize) -> bool {
6291 runs <= len / 2
6292 }
6293
6294 fn flush_computed_write_chunk_plan_as_points(&mut self, chunk: ComputedWriteChunkPlan) {
6295 let sheet_name = self.graph.sheet_name(chunk.sheet_id).to_string();
6296 for entry in chunk.entries {
6297 let row0 = chunk
6298 .chunk_start_row0
6299 .saturating_add(entry.row_in_chunk as u32);
6300 self.write_computed_overlay_value_0based(&sheet_name, row0, chunk.col0, entry.value);
6301 }
6302 }
6303
6304 fn flush_computed_write_chunk_plan_as_sparse_fragment_or_points(
6305 &mut self,
6306 chunk: ComputedWriteChunkPlan,
6307 ) {
6308 let point_estimate = Self::computed_write_chunk_plan_point_estimate(&chunk);
6309 let sheet_id = chunk.sheet_id;
6310 let col0 = chunk.col0;
6311 let chunk_idx = chunk.chunk_idx;
6312 let chunk_start_row0 = chunk.chunk_start_row0;
6313 let items: Vec<(usize, OverlayValue)> = chunk
6314 .entries
6315 .into_iter()
6316 .map(|entry| (entry.row_in_chunk, entry.value))
6317 .collect();
6318 match OverlayFragment::sparse_offsets_if_estimated_smaller_than_points(
6319 items,
6320 point_estimate,
6321 ) {
6322 Some(Ok(fragment)) => {
6323 self.apply_computed_overlay_fragment(sheet_id, col0, chunk_idx, fragment);
6324 }
6325 Some(Err(cells)) => {
6326 self.flush_computed_overlay_cells_as_points(
6327 sheet_id,
6328 col0,
6329 chunk_start_row0,
6330 cells,
6331 );
6332 }
6333 None => {}
6334 }
6335 }
6336
6337 #[inline]
6338 fn computed_write_chunk_plan_point_estimate(chunk: &ComputedWriteChunkPlan) -> usize {
6339 chunk
6340 .entries
6341 .iter()
6342 .map(|entry| ComputedWriteBuffer::estimate_value_bytes(&entry.value))
6343 .fold(0usize, usize::saturating_add)
6344 }
6345
6346 fn flush_computed_overlay_cells_as_points(
6347 &mut self,
6348 sheet_id: SheetId,
6349 col0: u32,
6350 chunk_start_row0: u32,
6351 cells: Vec<(usize, OverlayValue)>,
6352 ) {
6353 let sheet_name = self.graph.sheet_name(sheet_id).to_string();
6354 for (row_in_chunk, value) in cells {
6355 let row0 = chunk_start_row0.saturating_add(row_in_chunk as u32);
6356 self.write_computed_overlay_value_0based(&sheet_name, row0, col0, value);
6357 }
6358 }
6359
6360 fn flush_computed_write_chunk_plan_as_dense_fragment(&mut self, chunk: ComputedWriteChunkPlan) {
6361 if chunk.entries.is_empty() {
6362 return;
6363 }
6364 let start = chunk.entries[0].row_in_chunk;
6365 let values: Vec<OverlayValue> =
6366 chunk.entries.into_iter().map(|entry| entry.value).collect();
6367 if let Some(fragment) = OverlayFragment::dense_range(start, values) {
6368 self.apply_computed_overlay_fragment(
6369 chunk.sheet_id,
6370 chunk.col0,
6371 chunk.chunk_idx,
6372 fragment,
6373 );
6374 }
6375 }
6376
6377 fn flush_computed_write_chunk_plan_as_run_fragment(&mut self, chunk: ComputedWriteChunkPlan) {
6378 if chunk.entries.is_empty() {
6379 return;
6380 }
6381 let start = chunk.entries[0].row_in_chunk;
6382 let values: Vec<OverlayValue> =
6383 chunk.entries.into_iter().map(|entry| entry.value).collect();
6384 if let Some(fragment) = OverlayFragment::run_range(start, values) {
6385 self.apply_computed_overlay_fragment(
6386 chunk.sheet_id,
6387 chunk.col0,
6388 chunk.chunk_idx,
6389 fragment,
6390 );
6391 }
6392 }
6393
6394 fn apply_computed_overlay_fragment(
6395 &mut self,
6396 sheet_id: SheetId,
6397 col0: u32,
6398 chunk_idx: usize,
6399 fragment: OverlayFragment,
6400 ) {
6401 if !(self.config.arrow_storage_enabled
6402 && self.config.delta_overlay_enabled
6403 && self.config.write_formula_overlay_enabled)
6404 {
6405 return;
6406 }
6407 if self.computed_overlay_mirroring_disabled {
6408 return;
6409 }
6410
6411 let sheet_name = self.graph.sheet_name(sheet_id).to_string();
6412 self.ensure_arrow_sheet(&sheet_name);
6413
6414 let col0 = col0 as usize;
6415 let asheet = self
6416 .arrow_sheets
6417 .sheet_mut(&sheet_name)
6418 .expect("ArrowSheet must exist");
6419
6420 let cur_cols = asheet.columns.len();
6421 if col0 >= cur_cols {
6422 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
6423 }
6424
6425 let start_row0 = asheet
6426 .chunk_starts
6427 .get(chunk_idx)
6428 .copied()
6429 .unwrap_or_else(|| chunk_idx.saturating_mul(asheet.chunk_rows.max(1)));
6430 let required_rows =
6431 start_row0.saturating_add(fragment.max_covered_offset().saturating_add(1));
6432 if required_rows > asheet.nrows as usize {
6433 if asheet.columns.is_empty() {
6434 asheet.insert_columns(0, 1);
6435 }
6436 asheet.ensure_row_capacity(required_rows);
6437 }
6438
6439 let Some(ch) = asheet.ensure_column_chunk_mut(col0, chunk_idx) else {
6440 return;
6441 };
6442 let delta = ch.computed_overlay.apply_fragment(fragment);
6443 self.adjust_computed_overlay_bytes(delta);
6444
6445 if let Some(cap) = self.config.max_overlay_memory_bytes
6446 && self.computed_overlay_bytes_estimate > cap
6447 {
6448 self.disable_computed_overlay_mirroring_due_to_budget(cap);
6449 }
6450 }
6451
6452 #[inline]
6453 fn adjust_computed_overlay_bytes(&mut self, delta: isize) {
6454 if delta >= 0 {
6455 self.computed_overlay_bytes_estimate = self
6456 .computed_overlay_bytes_estimate
6457 .saturating_add(delta as usize);
6458 } else {
6459 self.computed_overlay_bytes_estimate = self
6460 .computed_overlay_bytes_estimate
6461 .saturating_sub((-delta) as usize);
6462 }
6463 }
6464
6465 fn clear_all_computed_overlays(&mut self) {
6466 let mut freed_total = 0usize;
6467 for sh in self.arrow_sheets.sheets.iter_mut() {
6468 for col in sh.columns.iter_mut() {
6469 for ch in col.chunks.iter_mut() {
6470 freed_total = freed_total.saturating_add(ch.computed_overlay.clear());
6471 }
6472 for ch in col.sparse_chunks.values_mut() {
6473 freed_total = freed_total.saturating_add(ch.computed_overlay.clear());
6474 }
6475 }
6476 }
6477 self.computed_overlay_bytes_estimate = self
6478 .computed_overlay_bytes_estimate
6479 .saturating_sub(freed_total);
6480 }
6481
6482 fn disable_computed_overlay_mirroring_due_to_budget(&mut self, _cap: usize) {
6483 self.compact_all_computed_overlays();
6486 }
6487
6488 fn compact_all_computed_overlays(&mut self) {
6491 let mut freed_total = 0usize;
6492 for sheet in self.arrow_sheets.sheets.iter_mut() {
6493 for col_idx in 0..sheet.columns.len() {
6494 let num_dense = sheet.columns[col_idx].chunks.len();
6496 for ch_idx in 0..num_dense {
6497 freed_total += sheet.compact_computed_overlay_chunk(col_idx, ch_idx);
6498 }
6499 let sparse_keys: Vec<usize> = sheet.columns[col_idx]
6501 .sparse_chunks
6502 .keys()
6503 .copied()
6504 .collect();
6505 for ch_idx in sparse_keys {
6506 freed_total += sheet.compact_computed_overlay_sparse_chunk(col_idx, ch_idx);
6507 }
6508 }
6509 }
6510 self.computed_overlay_bytes_estimate = self
6511 .computed_overlay_bytes_estimate
6512 .saturating_sub(freed_total);
6513 self.overlay_compactions = self.overlay_compactions.saturating_add(1);
6514 }
6515
6516 fn mirror_vertex_value_to_overlay(&mut self, vertex_id: VertexId, value: &LiteralValue) {
6517 let _ = self.record_vertex_value_to_overlay(vertex_id, value, None);
6518 }
6519
6520 fn record_vertex_value_to_overlay(
6521 &mut self,
6522 vertex_id: VertexId,
6523 value: &LiteralValue,
6524 computed_writes: Option<&mut ComputedWriteBuffer>,
6525 ) -> Result<(), ExcelError> {
6526 if !(self.config.arrow_storage_enabled
6527 && self.config.delta_overlay_enabled
6528 && self.config.write_formula_overlay_enabled)
6529 {
6530 return Ok(());
6531 }
6532 if self.computed_overlay_mirroring_disabled {
6533 return Ok(());
6534 }
6535 if !matches!(
6536 self.graph.get_vertex_kind(vertex_id),
6537 VertexKind::FormulaScalar | VertexKind::FormulaArray
6538 ) {
6539 return Ok(());
6540 }
6541 let Some(cell) = self.graph.get_cell_ref(vertex_id) else {
6542 return Ok(());
6543 };
6544 let ov = self.literal_to_overlay_value(value);
6545 if let Some(buffer) = computed_writes {
6546 buffer.push_cell(cell.sheet_id, cell.coord.row(), cell.coord.col(), ov);
6547 if self.should_flush_computed_write_buffer(buffer) {
6548 self.flush_computed_write_buffer(buffer)?;
6549 }
6550 } else {
6551 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
6552 self.write_computed_overlay_value_0based(
6553 &sheet_name,
6554 cell.coord.row(),
6555 cell.coord.col(),
6556 ov,
6557 );
6558 }
6559 Ok(())
6560 }
6561
6562 #[inline]
6563 fn should_flush_computed_write_buffer(&self, buffer: &ComputedWriteBuffer) -> bool {
6564 self.config.max_overlay_memory_bytes.is_some_and(|cap| {
6565 if cap == 0 {
6566 return false;
6567 }
6568 self.computed_overlay_bytes_estimate
6569 .saturating_add(buffer.estimated_bytes())
6570 > cap
6571 })
6572 }
6573
6574 pub fn overlay_memory_usage(&self) -> usize {
6576 self.computed_overlay_bytes_estimate
6577 }
6578
6579 #[cfg(test)]
6580 pub(crate) fn debug_overlay_compactions(&self) -> u64 {
6581 self.overlay_compactions
6582 }
6583
6584 #[cfg(test)]
6585 pub(crate) fn debug_recompute_computed_overlay_bytes(&mut self) -> usize {
6586 let mut total = 0usize;
6587 for sheet in &self.arrow_sheets.sheets {
6588 for column in &sheet.columns {
6589 for chunk in &column.chunks {
6590 total = total.saturating_add(chunk.computed_overlay.estimated_bytes());
6591 }
6592 for chunk in column.sparse_chunks.values() {
6593 total = total.saturating_add(chunk.computed_overlay.estimated_bytes());
6594 }
6595 }
6596 }
6597 self.computed_overlay_bytes_estimate = total;
6598 total
6599 }
6600
6601 fn resolve_sheet_locator_for_write(
6602 &mut self,
6603 loc: formualizer_common::SheetLocator<'_>,
6604 current_sheet: &str,
6605 ) -> Result<SheetId, ExcelError> {
6606 Ok(match loc {
6607 formualizer_common::SheetLocator::Id(id) => id,
6608 formualizer_common::SheetLocator::Name(name) => self.graph.sheet_id_mut(name.as_ref()),
6609 formualizer_common::SheetLocator::Current => self.graph.sheet_id_mut(current_sheet),
6610 })
6611 }
6612
6613 fn resolve_sheet_locator_for_read(
6614 &self,
6615 loc: formualizer_common::SheetLocator<'_>,
6616 current_sheet: &str,
6617 ) -> Result<SheetId, ExcelError> {
6618 match loc {
6619 formualizer_common::SheetLocator::Id(id) => Ok(id),
6620 formualizer_common::SheetLocator::Name(name) => self
6621 .graph
6622 .sheet_id(name.as_ref())
6623 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref)),
6624 formualizer_common::SheetLocator::Current => self
6625 .graph
6626 .sheet_id(current_sheet)
6627 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref)),
6628 }
6629 }
6630
6631 pub fn set_cell_value(
6633 &mut self,
6634 sheet: &str,
6635 row: u32,
6636 col: u32,
6637 value: LiteralValue,
6638 ) -> Result<(), ExcelError> {
6639 let sheet_id = self.graph.sheet_id_mut(sheet);
6640 self.demote_span_containing_cell_for_write(
6641 sheet_id,
6642 row.saturating_sub(1),
6643 col.saturating_sub(1),
6644 )
6645 .map_err(Self::editor_error_to_excel)?;
6646 self.graph.set_cell_value(sheet, row, col, value.clone())?;
6647 self.record_formula_plane_changed_cell(sheet, row, col);
6648 self.mirror_value_to_overlay(sheet, row, col, &value);
6650 self.snapshot_id
6652 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
6653 self.has_edited = true;
6654 Ok(())
6655 }
6656
6657 fn record_formula_plane_changed_cell(&mut self, sheet: &str, row: u32, col: u32) {
6662 if self.config.formula_plane_mode == FormulaPlaneMode::Off {
6663 return;
6664 }
6665 let sheet_id = self.graph.sheet_id_mut(sheet);
6666 self.record_formula_plane_structural_change(StructuralScope::Cell {
6667 sheet: sheet_id,
6668 row: row.saturating_sub(1),
6669 col: col.saturating_sub(1),
6670 });
6671 }
6672
6673 fn record_formula_plane_change_for_event(&mut self, event: &ChangeEvent) {
6674 if self.config.formula_plane_mode == FormulaPlaneMode::Off {
6675 return;
6676 }
6677
6678 match event {
6679 ChangeEvent::SetValue { addr, .. } | ChangeEvent::SetFormula { addr, .. } => {
6680 self.record_formula_plane_structural_change(StructuralScope::Cell {
6681 sheet: addr.sheet_id,
6682 row: addr.coord.row(),
6683 col: addr.coord.col(),
6684 });
6685 }
6686 ChangeEvent::SpillCommitted { new, .. } => {
6687 if let Some(scope) = Self::formula_plane_region_from_cells(&new.target_cells) {
6688 self.record_formula_plane_structural_change(scope);
6689 }
6690 }
6691 ChangeEvent::SpillCleared { old, .. } => {
6692 if let Some(scope) = Self::formula_plane_region_from_cells(&old.target_cells) {
6693 self.record_formula_plane_structural_change(scope);
6694 }
6695 }
6696 ChangeEvent::DefineName { .. }
6697 | ChangeEvent::UpdateName { .. }
6698 | ChangeEvent::DeleteName { .. }
6699 | ChangeEvent::VertexMoved { .. }
6700 | ChangeEvent::FormulaAdjusted { .. }
6701 | ChangeEvent::NamedRangeAdjusted { .. } => {
6702 self.record_formula_plane_structural_change(StructuralScope::AllSheets);
6703 }
6704 ChangeEvent::SetRowVisibility { sheet_id, row0, .. } => {
6705 self.record_formula_plane_structural_change(StructuralScope::Region(
6706 Region::whole_row(*sheet_id, *row0),
6707 ));
6708 }
6709 ChangeEvent::AddVertex { .. }
6710 | ChangeEvent::RemoveVertex { .. }
6711 | ChangeEvent::EdgeAdded { .. }
6712 | ChangeEvent::EdgeRemoved { .. }
6713 | ChangeEvent::CompoundStart { .. }
6714 | ChangeEvent::CompoundEnd { .. }
6715 | ChangeEvent::StagedFormulaStateChanged { .. } => {}
6716 }
6717 }
6718
6719 fn record_formula_plane_structural_change(&mut self, scope: StructuralScope) {
6720 if self.config.formula_plane_mode == FormulaPlaneMode::Off {
6721 return;
6722 }
6723
6724 match scope {
6725 StructuralScope::Cell { sheet, row, col } => {
6726 self.graph
6727 .formula_authority_mut()
6728 .record_changed_region(Region::point(sheet, row, col));
6729 }
6730 StructuralScope::Region(region) => {
6731 self.graph
6732 .formula_authority_mut()
6733 .record_changed_region(region);
6734 }
6735 StructuralScope::Sheet(sheet_id) => {
6736 self.graph
6737 .formula_authority_mut()
6738 .record_changed_region(Region::whole_sheet(sheet_id));
6739 }
6740 StructuralScope::RemovedSheet(sheet_id) => {
6741 let removed_refs = {
6742 let authority = self.graph.formula_authority();
6743 authority
6744 .active_span_refs()
6745 .into_iter()
6746 .filter(|span_ref| {
6747 authority
6748 .plane
6749 .spans
6750 .get(*span_ref)
6751 .map(|span| span.sheet_id == sheet_id)
6752 .unwrap_or(false)
6753 })
6754 .collect::<Vec<_>>()
6755 };
6756
6757 let authority = self.graph.formula_authority_mut();
6758 for span_ref in removed_refs {
6759 authority.plane.remove_span(span_ref);
6760 }
6761 authority.mark_all_active_spans_dirty();
6762 let _ = authority.rebuild_indexes();
6763 }
6764 StructuralScope::AllSheets => {
6765 let authority = self.graph.formula_authority_mut();
6766 authority.mark_all_active_spans_dirty();
6767 let _ = authority.rebuild_indexes();
6768 }
6769 }
6770 }
6771
6772 fn formula_plane_region_from_cells(cells: &[CellRef]) -> Option<StructuralScope> {
6773 let first = cells.first()?;
6774 let sheet_id = first.sheet_id;
6775 if cells.iter().any(|cell| cell.sheet_id != sheet_id) {
6776 return Some(StructuralScope::AllSheets);
6777 }
6778 let mut row_start = first.coord.row();
6779 let mut row_end = row_start;
6780 let mut col_start = first.coord.col();
6781 let mut col_end = col_start;
6782 for cell in cells.iter().skip(1) {
6783 row_start = row_start.min(cell.coord.row());
6784 row_end = row_end.max(cell.coord.row());
6785 col_start = col_start.min(cell.coord.col());
6786 col_end = col_end.max(cell.coord.col());
6787 }
6788 Some(StructuralScope::Region(Region::rect(
6789 sheet_id, row_start, row_end, col_start, col_end,
6790 )))
6791 }
6792
6793 pub fn set_cell_value_ref(
6794 &mut self,
6795 cell: formualizer_common::SheetCellRef<'_>,
6796 current_sheet: &str,
6797 value: LiteralValue,
6798 ) -> Result<(), ExcelError> {
6799 let owned = cell.into_owned();
6800 let sheet_id = self.resolve_sheet_locator_for_write(owned.sheet, current_sheet)?;
6801 let sheet_name = self.graph.sheet_name(sheet_id).to_string();
6802 self.set_cell_value(
6803 &sheet_name,
6804 owned.coord.row() + 1,
6805 owned.coord.col() + 1,
6806 value,
6807 )
6808 }
6809
6810 pub fn set_cell_formula_ref(
6811 &mut self,
6812 cell: formualizer_common::SheetCellRef<'_>,
6813 current_sheet: &str,
6814 ast: ASTNode,
6815 ) -> Result<(), ExcelError> {
6816 let owned = cell.into_owned();
6817 let sheet_id = self.resolve_sheet_locator_for_write(owned.sheet, current_sheet)?;
6818 let sheet_name = self.graph.sheet_name(sheet_id).to_string();
6819 self.set_cell_formula(
6820 &sheet_name,
6821 owned.coord.row() + 1,
6822 owned.coord.col() + 1,
6823 ast,
6824 )
6825 }
6826
6827 pub fn get_cell_value_ref(
6828 &self,
6829 cell: formualizer_common::SheetCellRef<'_>,
6830 current_sheet: &str,
6831 ) -> Result<Option<LiteralValue>, ExcelError> {
6832 let owned = cell.into_owned();
6833 let sheet_id = self.resolve_sheet_locator_for_read(owned.sheet, current_sheet)?;
6834 let sheet_name = self.graph.sheet_name(sheet_id);
6835 Ok(self.get_cell_value(sheet_name, owned.coord.row() + 1, owned.coord.col() + 1))
6836 }
6837
6838 pub fn resolve_range_view_sheet_ref<'c>(
6839 &'c self,
6840 r: &formualizer_common::SheetRef<'_>,
6841 current_sheet: &str,
6842 ) -> Result<RangeView<'c>, ExcelError> {
6843 use formualizer_common::SheetLocator;
6844
6845 let sheet_to_opt_name = |loc: SheetLocator<'_>| -> Result<Option<String>, ExcelError> {
6846 match loc {
6847 SheetLocator::Current => Ok(None),
6848 SheetLocator::Name(name) => Ok(Some(name.as_ref().to_string())),
6849 SheetLocator::Id(id) => Ok(Some(self.graph.sheet_name(id).to_string())),
6850 }
6851 };
6852
6853 let rt = match r {
6854 formualizer_common::SheetRef::Cell(cell) => ReferenceType::Cell {
6855 sheet: sheet_to_opt_name(cell.sheet.clone())?,
6856 row: cell.coord.row() + 1,
6857 col: cell.coord.col() + 1,
6858 row_abs: cell.coord.row_abs(),
6859 col_abs: cell.coord.col_abs(),
6860 },
6861 formualizer_common::SheetRef::Range(range) => ReferenceType::Range {
6862 sheet: sheet_to_opt_name(range.sheet.clone())?,
6863 start_row: range.start_row.map(|b| b.index + 1),
6864 start_col: range.start_col.map(|b| b.index + 1),
6865 end_row: range.end_row.map(|b| b.index + 1),
6866 end_col: range.end_col.map(|b| b.index + 1),
6867 start_row_abs: range.start_row.map(|b| b.abs).unwrap_or(false),
6868 start_col_abs: range.start_col.map(|b| b.abs).unwrap_or(false),
6869 end_row_abs: range.end_row.map(|b| b.abs).unwrap_or(false),
6870 end_col_abs: range.end_col.map(|b| b.abs).unwrap_or(false),
6871 },
6872 };
6873
6874 crate::traits::EvaluationContext::resolve_range_view(self, &rt, current_sheet)
6875 }
6876
6877 pub fn set_cell_formula(
6879 &mut self,
6880 sheet: &str,
6881 row: u32,
6882 col: u32,
6883 ast: ASTNode,
6884 ) -> Result<(), ExcelError> {
6885 let sheet_id = self.graph.sheet_id_mut(sheet);
6886 self.demote_span_containing_cell_for_write(
6887 sheet_id,
6888 row.saturating_sub(1),
6889 col.saturating_sub(1),
6890 )
6891 .map_err(Self::editor_error_to_excel)?;
6892 let placement = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
6893 let ingested = {
6894 let mut pipeline = self.ingest_pipeline();
6895 pipeline.ingest_formula(FormulaAstInput::Tree(ast), placement, None)?
6896 };
6897 self.graph.set_cell_formula_with_plan(
6898 sheet,
6899 row,
6900 col,
6901 ingested.ast_id,
6902 &ingested.dep_plan,
6903 ingested.dep_plan.volatile,
6904 ingested.dep_plan.dynamic,
6905 )?;
6906 self.record_formula_plane_changed_cell(sheet, row, col);
6907
6908 self.clear_delta_overlay_cell(sheet, row, col);
6913
6914 self.mark_topology_edited();
6916 Ok(())
6917 }
6918
6919 pub fn bulk_set_formulas<I>(&mut self, sheet: &str, items: I) -> Result<usize, ExcelError>
6921 where
6922 I: IntoIterator<Item = (u32, u32, ASTNode)>,
6923 {
6924 let collected: Vec<(u32, u32, ASTNode)> = items.into_iter().collect();
6925 let edited_cells: Vec<(u32, u32)> = collected.iter().map(|(r, c, _)| (*r, *c)).collect();
6926 let sheet_id = self.graph.sheet_id_mut(sheet);
6927 let writes_inside_active_span = edited_cells.iter().any(|(row, col)| {
6928 let placement =
6929 PlacementCoord::new(sheet_id, row.saturating_sub(1), col.saturating_sub(1));
6930 self.graph
6931 .formula_authority()
6932 .plane
6933 .spans
6934 .find_at(placement)
6935 .is_some()
6936 });
6937 if writes_inside_active_span {
6938 self.demote_spans_preserving_computed_overlays(sheet_id, Region::whole_sheet(sheet_id))
6939 .map_err(Self::editor_error_to_excel)?;
6940 }
6941 let ingested = {
6942 let mut pipeline = self.ingest_pipeline();
6943 let inputs = collected.into_iter().map(|(row, col, ast)| {
6944 let placement = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
6945 (FormulaAstInput::Tree(ast), placement, None)
6946 });
6947 pipeline.ingest_batch(inputs)?
6948 };
6949 let planned = ingested
6950 .into_iter()
6951 .map(|formula| {
6952 (
6953 formula.placement.coord.row() + 1,
6954 formula.placement.coord.col() + 1,
6955 formula.ast_id,
6956 formula.dep_plan,
6957 )
6958 })
6959 .collect();
6960 let n = self.graph.bulk_set_formulas_with_plans(sheet, planned)?;
6961 for (row, col) in edited_cells {
6962 self.record_formula_plane_changed_cell(sheet, row, col);
6963 }
6964 if n > 0 {
6966 self.mark_topology_edited();
6967 }
6968 Ok(n)
6969 }
6970
6971 #[inline]
6972 fn normalize_public_cell_read(v: LiteralValue) -> Option<LiteralValue> {
6973 match v {
6974 LiteralValue::Empty => None,
6975 LiteralValue::Int(i) => Some(LiteralValue::Number(i as f64)),
6976 other => Some(other),
6977 }
6978 }
6979
6980 pub fn get_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
6982 self.read_cell_value(sheet, row, col)
6983 .and_then(Self::normalize_public_cell_read)
6984 }
6985
6986 pub(crate) fn read_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
6988 let asheet = self.sheet_store().sheet(sheet)?;
6989 let r0 = row.saturating_sub(1) as usize;
6990 let c0 = col.saturating_sub(1) as usize;
6991 let v = asheet.get_cell_value(r0, c0);
6992 if matches!(v, LiteralValue::Empty) {
6993 None
6994 } else {
6995 Some(v)
6996 }
6997 }
6998
6999 pub(crate) fn read_range_values(
7001 &self,
7002 sheet: &str,
7003 sr: u32,
7004 sc: u32,
7005 er: u32,
7006 ec: u32,
7007 ) -> RangeView<'_> {
7008 let Some(asheet) = self.sheet_store().sheet(sheet) else {
7009 return RangeView::from_owned_rows(Vec::new(), self.config.date_system);
7010 };
7011 if er < sr || ec < sc {
7012 return asheet.range_view(1, 1, 0, 0);
7013 }
7014 let sr0 = sr.saturating_sub(1) as usize;
7015 let sc0 = sc.saturating_sub(1) as usize;
7016 let er0 = er.saturating_sub(1) as usize;
7017 let ec0 = ec.saturating_sub(1) as usize;
7018 asheet.range_view(sr0, sc0, er0, ec0)
7019 }
7020
7021 pub fn get_cell(
7023 &self,
7024 sheet: &str,
7025 row: u32,
7026 col: u32,
7027 ) -> Option<(Option<formualizer_parse::ASTNode>, Option<LiteralValue>)> {
7028 let v = self.get_cell_value(sheet, row, col);
7029 let sheet_id = self.graph.sheet_id(sheet)?;
7030 let coord = Coord::from_excel(row, col, true, true);
7031 let cell = CellRef::new(sheet_id, coord);
7032 if let Some(vid) = self.graph.get_vertex_for_cell(&cell) {
7033 let ast = self.graph.get_formula_id(vid).and_then(|ast_id| {
7034 self.graph
7035 .data_store()
7036 .retrieve_ast(ast_id, self.graph.sheet_reg())
7037 });
7038 return Some((ast, v));
7039 }
7040
7041 let placement =
7042 crate::formula_plane::runtime::PlacementCoord::new(sheet_id, coord.row(), coord.col());
7043 let handle = self
7044 .graph
7045 .formula_authority()
7046 .plane
7047 .resolve_formula_at(placement, None);
7048 let template_id = match handle.resolution {
7049 crate::formula_plane::runtime::FormulaResolution::SpanPlacement {
7050 template_id, ..
7051 } => Some(template_id),
7052 crate::formula_plane::runtime::FormulaResolution::Overlay(overlay_ref) => self
7053 .graph
7054 .formula_authority()
7055 .plane
7056 .formula_overlay
7057 .get(overlay_ref)
7058 .and_then(|overlay| match overlay.kind {
7059 crate::formula_plane::runtime::FormulaOverlayEntryKind::FormulaOverride(
7060 template_id,
7061 ) => Some(template_id),
7062 _ => None,
7063 }),
7064 _ => None,
7065 };
7066 let ast = template_id.and_then(|template_id| {
7067 let ast_id = self
7068 .graph
7069 .formula_authority()
7070 .plane
7071 .templates
7072 .get(template_id)?
7073 .ast_id;
7074 self.graph
7075 .data_store()
7076 .retrieve_ast(ast_id, self.graph.sheet_reg())
7077 });
7078 if let Some(ast) = ast {
7079 Some((Some(ast), v))
7080 } else if v.is_some() {
7081 Some((None, v))
7082 } else {
7083 None
7084 }
7085 }
7086
7087 pub fn begin_batch(&mut self) {
7089 self.graph.begin_batch();
7090 }
7091
7092 pub fn end_batch(&mut self) {
7094 self.graph.end_batch();
7095 }
7096
7097 #[inline]
7100 fn record_cell_if_changed(
7101 delta: &mut DeltaCollector,
7102 cell: &CellRef,
7103 old: &LiteralValue,
7104 new: &LiteralValue,
7105 ) {
7106 if old != new {
7107 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
7108 }
7109 }
7110
7111 pub fn evaluate_vertex(&mut self, vertex_id: VertexId) -> Result<LiteralValue, ExcelError> {
7112 if self.graph.formula_authority().active_span_count() > 0 {
7113 let _ = self.evaluate_authoritative_formula_plane_all()?;
7114 }
7115 self.evaluate_vertex_impl(vertex_id, None)
7116 }
7117
7118 fn evaluate_vertex_impl(
7119 &mut self,
7120 vertex_id: VertexId,
7121 delta: Option<&mut DeltaCollector>,
7122 ) -> Result<LiteralValue, ExcelError> {
7123 let mut delta = delta;
7124 if !self.graph.vertex_exists(vertex_id) {
7126 return Err(ExcelError::new(formualizer_common::ExcelErrorKind::Ref)
7127 .with_message(format!("Vertex not found: {vertex_id:?}")));
7128 }
7129
7130 let kind = self.graph.get_vertex_kind(vertex_id);
7132 let sheet_id = self.graph.get_vertex_sheet_id(vertex_id);
7133
7134 let ast_id = match kind {
7135 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
7136 if let Some(ast_id) = self.graph.get_formula_id(vertex_id) {
7137 ast_id
7138 } else {
7139 return Ok(LiteralValue::Number(0.0));
7140 }
7141 }
7142 VertexKind::Empty | VertexKind::Cell => {
7143 if let Some(cell_ref) = self.graph.get_cell_ref(vertex_id) {
7144 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
7145 let row = cell_ref.coord.row() + 1;
7146 let col = cell_ref.coord.col() + 1;
7147 if let Some(v) = self.read_cell_value(sheet_name, row, col) {
7148 return Ok(v);
7149 }
7150 }
7151 return Ok(LiteralValue::Number(0.0));
7152 }
7153 VertexKind::NamedScalar => {
7154 let value = self.evaluate_named_scalar(vertex_id, sheet_id)?;
7155 return Ok(value);
7156 }
7157 VertexKind::NamedArray => {
7158 let value = self.evaluate_named_array(vertex_id, sheet_id)?;
7159 return Ok(value);
7160 }
7161 VertexKind::InfiniteRange
7162 | VertexKind::Range
7163 | VertexKind::External
7164 | VertexKind::Table => {
7165 return Ok(LiteralValue::Number(0.0));
7167 }
7168 };
7169
7170 let sheet_name = self.graph.sheet_name(sheet_id);
7172 let cell_ref = self
7173 .graph
7174 .get_cell_ref(vertex_id)
7175 .expect("cell ref for vertex");
7176 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
7177
7178 let result =
7179 interpreter.evaluate_arena_ast(ast_id, self.graph.data_store(), self.graph.sheet_reg());
7180
7181 match result {
7183 Ok(cv) => {
7184 let result_literal = cv.into_literal();
7185 match result_literal {
7186 LiteralValue::Array(rows) => {
7187 self.graph
7189 .set_kind(vertex_id, crate::engine::vertex::VertexKind::FormulaArray);
7190 let anchor = self
7192 .graph
7193 .get_cell_ref(vertex_id)
7194 .expect("cell ref for vertex");
7195 let sheet_id = anchor.sheet_id;
7196 let h = rows.len() as u32;
7197 let w = rows.first().map(|r| r.len()).unwrap_or(0) as u32;
7198
7199 let spill_cells = (h as u64).saturating_mul(w as u64);
7201 if spill_cells > self.config.spill.max_spill_cells as u64 {
7202 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
7203 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
7204 .with_message("SpillTooLarge")
7205 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
7206 expected_rows: h,
7207 expected_cols: w,
7208 });
7209 let spill_val = LiteralValue::Error(spill_err.clone());
7210 if let Some(d) = delta.as_deref_mut() {
7211 let old = self
7212 .read_cell_value(
7213 self.graph.sheet_name(anchor.sheet_id),
7214 anchor.coord.row() + 1,
7215 anchor.coord.col() + 1,
7216 )
7217 .unwrap_or(LiteralValue::Empty);
7218 if old != spill_val {
7219 d.record_cell(
7220 anchor.sheet_id,
7221 anchor.coord.row(),
7222 anchor.coord.col(),
7223 );
7224 }
7225 }
7226 self.graph.update_vertex_value(vertex_id, spill_val.clone());
7227 if self.config.arrow_storage_enabled
7228 && self.config.delta_overlay_enabled
7229 && self.config.write_formula_overlay_enabled
7230 {
7231 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
7232 self.mirror_value_to_computed_overlay(
7233 &sheet_name,
7234 anchor.coord.row() + 1,
7235 anchor.coord.col() + 1,
7236 &spill_val,
7237 );
7238 }
7239 return Ok(spill_val);
7240 }
7241 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);
7245 let end_col = anchor.coord.col().saturating_add(w).saturating_sub(1);
7246 if end_row > PACKED_MAX_ROW || end_col > PACKED_MAX_COL {
7247 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
7248 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
7249 .with_message("Spill exceeds sheet bounds")
7250 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
7251 expected_rows: h,
7252 expected_cols: w,
7253 });
7254 let spill_val = LiteralValue::Error(spill_err.clone());
7255 if let Some(d) = delta.as_deref_mut() {
7256 let old = self
7257 .read_cell_value(
7258 self.graph.sheet_name(anchor.sheet_id),
7259 anchor.coord.row() + 1,
7260 anchor.coord.col() + 1,
7261 )
7262 .unwrap_or(LiteralValue::Empty);
7263 if old != spill_val {
7264 d.record_cell(
7265 anchor.sheet_id,
7266 anchor.coord.row(),
7267 anchor.coord.col(),
7268 );
7269 }
7270 }
7271 self.graph.update_vertex_value(vertex_id, spill_val.clone());
7272 if self.config.arrow_storage_enabled
7273 && self.config.delta_overlay_enabled
7274 && self.config.write_formula_overlay_enabled
7275 {
7276 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
7277 self.mirror_value_to_computed_overlay(
7278 &sheet_name,
7279 anchor.coord.row() + 1,
7280 anchor.coord.col() + 1,
7281 &spill_val,
7282 );
7283 }
7284 return Ok(spill_val);
7285 }
7286 let mut targets = Vec::new();
7287 for r in 0..h {
7288 for c in 0..w {
7289 targets.push(self.graph.make_cell_ref_internal(
7290 sheet_id,
7291 anchor.coord.row() + r,
7292 anchor.coord.col() + c,
7293 ));
7294 }
7295 }
7296
7297 match self.spill_mgr.reserve(
7299 vertex_id,
7300 anchor,
7301 SpillShape { rows: h, cols: w },
7302 SpillMeta {
7303 epoch: self.recalc_epoch,
7304 config: self.config.spill,
7305 },
7306 ) {
7307 Ok(()) => {
7308 if let Err(e) = self.commit_spill_and_mirror(
7312 vertex_id,
7313 &targets,
7314 rows.clone(),
7315 delta.as_deref_mut(),
7316 None,
7317 ) {
7318 self.clear_spill_projection_and_mirror(
7320 vertex_id,
7321 delta.as_deref_mut(),
7322 );
7323 if let Some(d) = delta.as_deref_mut() {
7324 let old = self
7325 .read_cell_value(
7326 self.graph.sheet_name(anchor.sheet_id),
7327 anchor.coord.row() + 1,
7328 anchor.coord.col() + 1,
7329 )
7330 .unwrap_or(LiteralValue::Empty);
7331 let new = LiteralValue::Error(e.clone());
7332 if old != new {
7333 d.record_cell(
7334 anchor.sheet_id,
7335 anchor.coord.row(),
7336 anchor.coord.col(),
7337 );
7338 }
7339 }
7340 let err_val = LiteralValue::Error(e.clone());
7341 self.graph.update_vertex_value(vertex_id, err_val.clone());
7342 if self.config.arrow_storage_enabled
7343 && self.config.delta_overlay_enabled
7344 && self.config.write_formula_overlay_enabled
7345 {
7346 let sheet_name =
7347 self.graph.sheet_name(anchor.sheet_id).to_string();
7348 self.mirror_value_to_computed_overlay(
7349 &sheet_name,
7350 anchor.coord.row() + 1,
7351 anchor.coord.col() + 1,
7352 &err_val,
7353 );
7354 }
7355 return Ok(err_val);
7356 }
7357 let top_left = rows
7359 .first()
7360 .and_then(|r| r.first())
7361 .cloned()
7362 .unwrap_or(LiteralValue::Empty);
7363 self.graph.update_vertex_value(vertex_id, top_left.clone());
7364 Ok(top_left)
7365 }
7366 Err(e) => {
7367 self.clear_spill_projection_and_mirror(
7368 vertex_id,
7369 delta.as_deref_mut(),
7370 );
7371 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
7372 .with_message(
7373 e.message.unwrap_or_else(|| "Spill blocked".to_string()),
7374 )
7375 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
7376 expected_rows: h,
7377 expected_cols: w,
7378 });
7379 let spill_val = LiteralValue::Error(spill_err.clone());
7380 if let Some(d) = delta.as_deref_mut() {
7381 let old = self
7382 .read_cell_value(
7383 self.graph.sheet_name(anchor.sheet_id),
7384 anchor.coord.row() + 1,
7385 anchor.coord.col() + 1,
7386 )
7387 .unwrap_or(LiteralValue::Empty);
7388 if old != spill_val {
7389 d.record_cell(
7390 anchor.sheet_id,
7391 anchor.coord.row(),
7392 anchor.coord.col(),
7393 );
7394 }
7395 }
7396 self.graph.update_vertex_value(vertex_id, spill_val.clone());
7397 if self.config.arrow_storage_enabled
7398 && self.config.delta_overlay_enabled
7399 && self.config.write_formula_overlay_enabled
7400 {
7401 let sheet_name =
7402 self.graph.sheet_name(anchor.sheet_id).to_string();
7403 self.mirror_value_to_computed_overlay(
7404 &sheet_name,
7405 anchor.coord.row() + 1,
7406 anchor.coord.col() + 1,
7407 &spill_val,
7408 );
7409 }
7410 Ok(spill_val)
7411 }
7412 }
7413 }
7414 other => {
7415 let spill_cells = self
7417 .graph
7418 .spill_cells_for_anchor(vertex_id)
7419 .map(|cells| cells.to_vec())
7420 .unwrap_or_default();
7421 if let Some(d) = delta.as_deref_mut()
7422 && let Some(anchor) = self.graph.get_cell_ref_for_vertex(vertex_id)
7423 {
7424 if spill_cells.is_empty() {
7425 let old = self
7426 .read_cell_value(
7427 self.graph.sheet_name(anchor.sheet_id),
7428 anchor.coord.row() + 1,
7429 anchor.coord.col() + 1,
7430 )
7431 .unwrap_or(LiteralValue::Empty);
7432 if old != other {
7433 d.record_cell(
7434 anchor.sheet_id,
7435 anchor.coord.row(),
7436 anchor.coord.col(),
7437 );
7438 }
7439 } else {
7440 for cell in spill_cells.iter() {
7441 let sheet_name = self.graph.sheet_name(cell.sheet_id);
7442 let old = self
7443 .get_cell_value(
7444 sheet_name,
7445 cell.coord.row() + 1,
7446 cell.coord.col() + 1,
7447 )
7448 .unwrap_or(LiteralValue::Empty);
7449 let new = if cell.sheet_id == anchor.sheet_id
7450 && cell.coord.row() == anchor.coord.row()
7451 && cell.coord.col() == anchor.coord.col()
7452 {
7453 other.clone()
7454 } else {
7455 LiteralValue::Empty
7456 };
7457 Self::record_cell_if_changed(d, cell, &old, &new);
7458 }
7459 }
7460 }
7461 self.graph.clear_spill_region(vertex_id);
7462 if let Some(scope) = Self::formula_plane_region_from_cells(&spill_cells) {
7463 self.record_formula_plane_structural_change(scope);
7464 }
7465 if self.config.arrow_storage_enabled
7466 && self.config.delta_overlay_enabled
7467 && self.config.write_formula_overlay_enabled
7468 {
7469 let empty = LiteralValue::Empty;
7470 for cell in spill_cells.iter() {
7471 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
7472 self.mirror_value_to_computed_overlay(
7473 &sheet_name,
7474 cell.coord.row() + 1,
7475 cell.coord.col() + 1,
7476 &empty,
7477 );
7478 }
7479 }
7480 self.graph.update_vertex_value(vertex_id, other.clone());
7481 if self.config.arrow_storage_enabled
7483 && self.config.delta_overlay_enabled
7484 && self.config.write_formula_overlay_enabled
7485 {
7486 let anchor = self
7487 .graph
7488 .get_cell_ref(vertex_id)
7489 .expect("cell ref for vertex");
7490 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
7491 self.mirror_value_to_computed_overlay(
7492 &sheet_name,
7493 anchor.coord.row() + 1,
7494 anchor.coord.col() + 1,
7495 &other,
7496 );
7497 }
7498 Ok(other)
7499 }
7500 }
7501 }
7502 Err(e) => {
7503 let spill_cells = self
7506 .graph
7507 .spill_cells_for_anchor(vertex_id)
7508 .map(|cells| cells.to_vec())
7509 .unwrap_or_default();
7510 let err_val = LiteralValue::Error(e.clone());
7511 if let Some(d) = delta
7512 && let Some(anchor) = self.graph.get_cell_ref_for_vertex(vertex_id)
7513 {
7514 if spill_cells.is_empty() {
7515 let old = self
7516 .read_cell_value(
7517 self.graph.sheet_name(anchor.sheet_id),
7518 anchor.coord.row() + 1,
7519 anchor.coord.col() + 1,
7520 )
7521 .unwrap_or(LiteralValue::Empty);
7522 if old != err_val {
7523 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
7524 }
7525 } else {
7526 for cell in spill_cells.iter() {
7527 let sheet_name = self.graph.sheet_name(cell.sheet_id);
7528 let old = self
7529 .get_cell_value(
7530 sheet_name,
7531 cell.coord.row() + 1,
7532 cell.coord.col() + 1,
7533 )
7534 .unwrap_or(LiteralValue::Empty);
7535 let new = if cell.sheet_id == anchor.sheet_id
7536 && cell.coord.row() == anchor.coord.row()
7537 && cell.coord.col() == anchor.coord.col()
7538 {
7539 err_val.clone()
7540 } else {
7541 LiteralValue::Empty
7542 };
7543 Self::record_cell_if_changed(d, cell, &old, &new);
7544 }
7545 }
7546 }
7547 self.graph.clear_spill_region(vertex_id);
7548 if let Some(scope) = Self::formula_plane_region_from_cells(&spill_cells) {
7549 self.record_formula_plane_structural_change(scope);
7550 }
7551 if self.config.arrow_storage_enabled
7552 && self.config.delta_overlay_enabled
7553 && self.config.write_formula_overlay_enabled
7554 {
7555 let empty = LiteralValue::Empty;
7556 for cell in spill_cells.iter() {
7557 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
7558 self.mirror_value_to_computed_overlay(
7559 &sheet_name,
7560 cell.coord.row() + 1,
7561 cell.coord.col() + 1,
7562 &empty,
7563 );
7564 }
7565 }
7566 self.graph.update_vertex_value(vertex_id, err_val.clone());
7567 if self.config.arrow_storage_enabled
7568 && self.config.delta_overlay_enabled
7569 && self.config.write_formula_overlay_enabled
7570 {
7571 let anchor = self
7572 .graph
7573 .get_cell_ref(vertex_id)
7574 .expect("cell ref for vertex");
7575 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
7576 self.mirror_value_to_computed_overlay(
7577 &sheet_name,
7578 anchor.coord.row() + 1,
7579 anchor.coord.col() + 1,
7580 &err_val,
7581 );
7582 }
7583 Ok(err_val)
7584 }
7585 }
7586 }
7587
7588 fn evaluate_named_scalar(
7589 &mut self,
7590 vertex_id: VertexId,
7591 sheet_id: SheetId,
7592 ) -> Result<LiteralValue, ExcelError> {
7593 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
7594 ExcelError::new(ExcelErrorKind::Name)
7595 .with_message("Named range metadata missing".to_string())
7596 })?;
7597
7598 match &named_range.definition {
7599 NamedDefinition::Cell(cell_ref) => {
7600 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
7601 let row = cell_ref.coord.row() + 1;
7602 let col = cell_ref.coord.col() + 1;
7603
7604 if let Some(dep_vertex) = self.graph.get_vertex_for_cell(cell_ref)
7605 && matches!(
7606 self.graph.get_vertex_kind(dep_vertex),
7607 VertexKind::FormulaScalar | VertexKind::FormulaArray
7608 )
7609 {
7610 let value = self.evaluate_vertex(dep_vertex)?;
7612 self.graph.update_vertex_value(vertex_id, value.clone());
7613 Ok(value)
7614 } else {
7615 let value = self
7616 .get_cell_value(sheet_name, row, col)
7617 .unwrap_or(LiteralValue::Empty);
7618 self.graph.update_vertex_value(vertex_id, value.clone());
7619 Ok(value)
7620 }
7621 }
7622 NamedDefinition::Literal(v) => {
7623 let out = v.clone();
7624 self.graph.update_vertex_value(vertex_id, out.clone());
7625 Ok(out)
7626 }
7627 NamedDefinition::Formula { ast, .. } => {
7628 let context_sheet = match named_range.scope {
7629 NameScope::Sheet(id) => id,
7630 NameScope::Workbook => sheet_id,
7631 };
7632 let sheet_name = self.graph.sheet_name(context_sheet);
7633 let cell_ref = self
7634 .graph
7635 .get_cell_ref(vertex_id)
7636 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
7637 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
7638 match interpreter.evaluate_ast(ast) {
7639 Ok(cv) => {
7640 let value = cv.into_literal();
7641 match value {
7642 LiteralValue::Array(_) => {
7643 let err = ExcelError::new(ExcelErrorKind::NImpl)
7644 .with_message("Array result in scalar named range".to_string());
7645 let err_val = LiteralValue::Error(err.clone());
7646 self.graph.update_vertex_value(vertex_id, err_val.clone());
7647 Ok(err_val)
7648 }
7649 other => {
7650 self.graph.update_vertex_value(vertex_id, other.clone());
7651 Ok(other)
7652 }
7653 }
7654 }
7655 Err(err) => {
7656 let err_val = LiteralValue::Error(err.clone());
7657 self.graph.update_vertex_value(vertex_id, err_val.clone());
7658 Ok(err_val)
7659 }
7660 }
7661 }
7662 NamedDefinition::Range(_) => Err(ExcelError::new(ExcelErrorKind::Value)
7663 .with_message("Range-valued name evaluated as scalar".to_string())),
7664 }
7665 }
7666
7667 fn evaluate_named_array(
7668 &mut self,
7669 vertex_id: VertexId,
7670 sheet_id: SheetId,
7671 ) -> Result<LiteralValue, ExcelError> {
7672 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
7673 ExcelError::new(ExcelErrorKind::Name)
7674 .with_message("Named range metadata missing".to_string())
7675 })?;
7676
7677 let out = match &named_range.definition {
7678 NamedDefinition::Range(range_ref) => {
7679 if range_ref.start.sheet_id != range_ref.end.sheet_id {
7680 return Err(ExcelError::new(ExcelErrorKind::Ref)
7681 .with_message("Named range cannot span sheets".to_string()));
7682 }
7683
7684 let sheet_name = self.graph.sheet_name(range_ref.start.sheet_id);
7685 let sr0 = range_ref.start.coord.row();
7686 let sc0 = range_ref.start.coord.col();
7687 let er0 = range_ref.end.coord.row();
7688 let ec0 = range_ref.end.coord.col();
7689 if sr0 > er0 || sc0 > ec0 {
7690 return Err(ExcelError::new(ExcelErrorKind::Ref)
7691 .with_message("Invalid named range bounds".to_string()));
7692 }
7693
7694 let h = (er0 - sr0 + 1) as usize;
7695 let w = (ec0 - sc0 + 1) as usize;
7696 let cell_count = (h as u64).saturating_mul(w as u64);
7697 if cell_count > self.config.spill.max_spill_cells as u64 {
7698 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
7699 "Named range too large to materialize as an array".to_string(),
7700 ));
7701 }
7702
7703 let mut rows = Vec::with_capacity(h);
7704 for r0 in sr0..=er0 {
7705 let mut row = Vec::with_capacity(w);
7706 for c0 in sc0..=ec0 {
7707 let v = self
7708 .get_cell_value(sheet_name, r0 + 1, c0 + 1)
7709 .unwrap_or(LiteralValue::Empty);
7710 row.push(v);
7711 }
7712 rows.push(row);
7713 }
7714 LiteralValue::Array(rows)
7715 }
7716 NamedDefinition::Cell(cell_ref) => {
7717 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
7718 let row = cell_ref.coord.row() + 1;
7719 let col = cell_ref.coord.col() + 1;
7720 let v = self
7721 .get_cell_value(sheet_name, row, col)
7722 .unwrap_or(LiteralValue::Empty);
7723 LiteralValue::Array(vec![vec![v]])
7724 }
7725 NamedDefinition::Literal(v) => LiteralValue::Array(vec![vec![v.clone()]]),
7726 NamedDefinition::Formula { ast, .. } => {
7727 let context_sheet = match named_range.scope {
7728 NameScope::Sheet(id) => id,
7729 NameScope::Workbook => sheet_id,
7730 };
7731 let sheet_name = self.graph.sheet_name(context_sheet);
7732 let cell_ref = self
7733 .graph
7734 .get_cell_ref(vertex_id)
7735 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
7736 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
7737 match interpreter.evaluate_ast(ast) {
7738 Ok(cv) => {
7739 let v = cv.into_literal();
7740 match v {
7741 LiteralValue::Array(_) => v,
7742 other => LiteralValue::Array(vec![vec![other]]),
7743 }
7744 }
7745 Err(err) => LiteralValue::Error(err),
7746 }
7747 }
7748 };
7749
7750 self.graph.update_vertex_value(vertex_id, out.clone());
7751 Ok(out)
7752 }
7753
7754 pub fn evaluate_until(
7756 &mut self,
7757 targets: &[(&str, u32, u32)],
7758 ) -> Result<EvalResult, ExcelError> {
7759 #[cfg(feature = "tracing")]
7760 let _span_eval = tracing::info_span!("evaluate_until", targets = targets.len()).entered();
7761 let start = crate::instant::FzInstant::now();
7762 let _source_cache = self.source_cache_session();
7763 if self.graph.formula_authority().active_span_count() > 0 {
7764 return self.evaluate_authoritative_formula_plane_all();
7765 }
7766
7767 let mut target_addrs = Vec::new();
7769 for (sheet, row, col) in targets {
7770 let sheet_id = self.graph.sheet_id_mut(sheet);
7773 let coord = Coord::from_excel(*row, *col, true, true);
7774 target_addrs.push(CellRef::new(sheet_id, coord));
7775 }
7776
7777 let mut target_vertex_ids = Vec::new();
7779 for addr in &target_addrs {
7780 if let Some(vertex_id) = self.graph.get_vertex_id_for_address(addr) {
7781 target_vertex_ids.push(*vertex_id);
7782 }
7783 }
7784
7785 if target_vertex_ids.is_empty() {
7786 return Ok(EvalResult {
7787 computed_vertices: 0,
7788 cycle_errors: 0,
7789 elapsed: start.elapsed(),
7790 });
7791 }
7792
7793 #[cfg(feature = "tracing")]
7795 let _span_sub = tracing::info_span!("demand_subgraph_build").entered();
7796 let (precedents_to_eval, vdeps) = self.build_demand_subgraph(&target_vertex_ids);
7797 #[cfg(feature = "tracing")]
7798 drop(_span_sub);
7799
7800 if precedents_to_eval.is_empty() {
7801 return Ok(EvalResult {
7802 computed_vertices: 0,
7803 cycle_errors: 0,
7804 elapsed: start.elapsed(),
7805 });
7806 }
7807
7808 let scheduler = Scheduler::new(&self.graph);
7810 #[cfg(feature = "tracing")]
7811 let _span_sched =
7812 tracing::info_span!("schedule_build", vertices = precedents_to_eval.len()).entered();
7813 let schedule = scheduler.create_schedule_with_virtual(&precedents_to_eval, &vdeps)?;
7814 #[cfg(feature = "tracing")]
7815 drop(_span_sched);
7816
7817 let mut cycle_errors = 0;
7819 for cycle in &schedule.cycles {
7820 cycle_errors += 1;
7821 let circ_error = LiteralValue::Error(
7822 ExcelError::new(ExcelErrorKind::Circ)
7823 .with_message("Circular dependency detected".to_string()),
7824 );
7825 for &vertex_id in cycle {
7826 self.graph
7827 .update_vertex_value(vertex_id, circ_error.clone());
7828 self.mirror_vertex_value_to_overlay(vertex_id, &circ_error);
7829 }
7830 }
7831
7832 let mut computed_vertices = 0;
7834 for layer in &schedule.layers {
7835 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
7836 computed_vertices += self.evaluate_layer_parallel(layer)?;
7837 } else {
7838 computed_vertices += self.evaluate_layer_sequential(layer)?;
7839 }
7840 }
7841
7842 self.graph.clear_dirty_flags(&precedents_to_eval);
7846
7847 self.graph.redirty_volatiles();
7849
7850 Ok(EvalResult {
7851 computed_vertices,
7852 cycle_errors,
7853 elapsed: start.elapsed(),
7854 })
7855 }
7856
7857 fn evaluate_until_with_delta_collector(
7858 &mut self,
7859 targets: &[(&str, u32, u32)],
7860 delta: &mut DeltaCollector,
7861 ) -> Result<EvalResult, ExcelError> {
7862 #[cfg(feature = "tracing")]
7863 let _span_eval =
7864 tracing::info_span!("evaluate_until_with_delta", targets = targets.len()).entered();
7865 let start = crate::instant::FzInstant::now();
7866 let _source_cache = self.source_cache_session();
7867
7868 let mut target_addrs = Vec::new();
7869 for (sheet, row, col) in targets {
7870 let sheet_id = self.graph.sheet_id_mut(sheet);
7871 let coord = Coord::from_excel(*row, *col, true, true);
7872 target_addrs.push(CellRef::new(sheet_id, coord));
7873 }
7874
7875 let mut target_vertex_ids = Vec::new();
7876 for addr in &target_addrs {
7877 if let Some(vertex_id) = self.graph.get_vertex_id_for_address(addr) {
7878 target_vertex_ids.push(*vertex_id);
7879 }
7880 }
7881
7882 if target_vertex_ids.is_empty() {
7883 return Ok(EvalResult {
7884 computed_vertices: 0,
7885 cycle_errors: 0,
7886 elapsed: start.elapsed(),
7887 });
7888 }
7889
7890 let (precedents_to_eval, vdeps) = self.build_demand_subgraph(&target_vertex_ids);
7891
7892 if precedents_to_eval.is_empty() {
7893 return Ok(EvalResult {
7894 computed_vertices: 0,
7895 cycle_errors: 0,
7896 elapsed: start.elapsed(),
7897 });
7898 }
7899
7900 let scheduler = Scheduler::new(&self.graph);
7901 let schedule = scheduler.create_schedule_with_virtual(&precedents_to_eval, &vdeps)?;
7902
7903 let mut cycle_errors = 0;
7904 let circ_error = LiteralValue::Error(
7905 ExcelError::new(ExcelErrorKind::Circ)
7906 .with_message("Circular dependency detected".to_string()),
7907 );
7908 for cycle in &schedule.cycles {
7909 cycle_errors += 1;
7910 for &vertex_id in cycle {
7911 if delta.mode != DeltaMode::Off
7912 && let Some(cell) = self.graph.get_cell_ref_for_vertex(vertex_id)
7913 {
7914 let sheet_name = self.graph.sheet_name(cell.sheet_id);
7915 let old = self
7916 .read_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
7917 .unwrap_or(LiteralValue::Empty);
7918 if old != circ_error {
7919 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
7920 }
7921 }
7922 self.graph
7923 .update_vertex_value(vertex_id, circ_error.clone());
7924 self.mirror_vertex_value_to_overlay(vertex_id, &circ_error);
7925 }
7926 }
7927
7928 let mut computed_vertices = 0;
7929 for layer in &schedule.layers {
7930 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
7931 computed_vertices += self.evaluate_layer_parallel_with_delta(layer, delta)?;
7932 } else {
7933 computed_vertices += self.evaluate_layer_sequential_with_delta(layer, delta)?;
7934 }
7935 }
7936
7937 self.graph.clear_dirty_flags(&precedents_to_eval);
7938 self.graph.redirty_volatiles();
7939
7940 Ok(EvalResult {
7941 computed_vertices,
7942 cycle_errors,
7943 elapsed: start.elapsed(),
7944 })
7945 }
7946
7947 pub fn build_recalc_plan(&self) -> Result<RecalcPlan, ExcelError> {
7949 let mut vertices: Vec<VertexId> = self.graph.vertices_with_formulas().collect();
7950 vertices.sort_unstable();
7951 if vertices.is_empty() {
7952 return Ok(RecalcPlan {
7953 schedule: crate::engine::Schedule {
7954 layers: Vec::new(),
7955 cycles: Vec::new(),
7956 },
7957 has_dynamic_refs: false,
7958 });
7959 }
7960
7961 let has_dynamic_refs = vertices.iter().copied().any(|v| self.graph.is_dynamic(v));
7962 let (schedule, _, _) = self.create_evaluation_schedule_uncached(&vertices)?;
7963 Ok(RecalcPlan {
7964 schedule,
7965 has_dynamic_refs,
7966 })
7967 }
7968
7969 pub fn evaluate_recalc_plan(&mut self, plan: &RecalcPlan) -> Result<EvalResult, ExcelError> {
7971 let _source_cache = self.source_cache_session();
7972 self.validate_deterministic_mode()?;
7973 if self.config.defer_graph_building {
7974 self.build_graph_all()?;
7975 }
7976 if self.graph.formula_authority().active_span_count() > 0 {
7977 return self.evaluate_authoritative_formula_plane_all();
7978 }
7979
7980 let start = crate::instant::FzInstant::now();
7981 let dirty_vertices = self.graph.get_evaluation_vertices();
7982 if dirty_vertices.is_empty() {
7983 return Ok(EvalResult {
7984 computed_vertices: 0,
7985 cycle_errors: 0,
7986 elapsed: start.elapsed(),
7987 });
7988 }
7989
7990 if plan.has_dynamic_refs {
7993 self.virtual_dep_fallback_activations =
7994 self.virtual_dep_fallback_activations.saturating_add(1);
7995 return self.evaluate_all();
7996 }
7997
7998 let dirty_set: FxHashSet<VertexId> = dirty_vertices.iter().copied().collect();
7999 let mut computed_vertices = 0;
8000 let mut cycle_errors = 0;
8001
8002 if !plan.schedule.cycles.is_empty() {
8003 let circ_error = LiteralValue::Error(
8004 ExcelError::new(ExcelErrorKind::Circ)
8005 .with_message("Circular dependency detected".to_string()),
8006 );
8007 for cycle in &plan.schedule.cycles {
8008 if !cycle.iter().any(|v| dirty_set.contains(v)) {
8009 continue;
8010 }
8011 cycle_errors += 1;
8012 for &vertex_id in cycle {
8013 if dirty_set.contains(&vertex_id) {
8014 self.graph
8015 .update_vertex_value(vertex_id, circ_error.clone());
8016 self.mirror_vertex_value_to_overlay(vertex_id, &circ_error);
8017 }
8018 }
8019 }
8020 }
8021
8022 for layer in &plan.schedule.layers {
8023 let work: Vec<VertexId> = layer
8024 .vertices
8025 .iter()
8026 .copied()
8027 .filter(|v| dirty_set.contains(v))
8028 .collect();
8029 if work.is_empty() {
8030 continue;
8031 }
8032 let temp_layer = crate::engine::scheduler::Layer { vertices: work };
8033 if self.thread_pool.is_some() && temp_layer.vertices.len() > 1 {
8034 computed_vertices += self.evaluate_layer_parallel(&temp_layer)?;
8035 } else {
8036 computed_vertices += self.evaluate_layer_sequential(&temp_layer)?;
8037 }
8038 }
8039
8040 self.graph.clear_dirty_flags(&dirty_vertices);
8041 self.graph.redirty_volatiles();
8042
8043 Ok(EvalResult {
8044 computed_vertices,
8045 cycle_errors,
8046 elapsed: start.elapsed(),
8047 })
8048 }
8049 fn evaluate_authoritative_formula_plane_all(&mut self) -> Result<EvalResult, ExcelError> {
8050 if self.graph.formula_authority().active_span_count() == 0 {
8055 #[cfg(test)]
8056 {
8057 self.last_formula_plane_span_eval_report = None;
8058 }
8059 return self.evaluate_all_legacy_impl();
8060 }
8061
8062 let current_indexes_epoch = self.graph.formula_authority().indexes_epoch();
8067 let span_seed_mode = if self.formula_plane_indexes_epoch_seen != current_indexes_epoch {
8068 SpanSeedMode::WholeAll
8069 } else {
8070 SpanSeedMode::DirtyClosure
8071 };
8072 let pending_changed_regions = self
8075 .graph
8076 .formula_authority_mut()
8077 .take_pending_changed_regions();
8078
8079 let start = crate::instant::FzInstant::now();
8080 let (schedule, span_refs_by_id, plane_epoch, legacy_vertices) =
8081 self.build_formula_plane_mixed_schedule(span_seed_mode, &pending_changed_regions)?;
8082
8083 if !schedule.is_authoritative_safe() {
8084 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(format!(
8085 "FormulaPlane mixed schedule is not authoritative-safe: {:?}",
8086 schedule.fallbacks
8087 )));
8088 }
8089
8090 let mut computed_vertices = 0usize;
8091 #[cfg(test)]
8092 {
8093 self.last_formula_plane_span_eval_report = None;
8094 }
8095 for layer in schedule.layers {
8096 let mut buffer = ComputedWriteBuffer::default();
8097 let mut sink = SpanComputedWriteSink::new(&mut buffer);
8098 let work_items = layer.work;
8099 let mut work_index = 0usize;
8100 while work_index < work_items.len() {
8101 match work_items[work_index].producer {
8102 FormulaProducerId::Span(span_id) => {
8103 let span_ref = *span_refs_by_id.get(&span_id).ok_or_else(|| {
8104 ExcelError::new(ExcelErrorKind::NImpl)
8105 .with_message("FormulaPlane schedule referenced a stale span")
8106 })?;
8107 let sheet_id = {
8108 let authority = self.graph.formula_authority();
8109 let span = authority.plane.spans.get(span_ref).ok_or_else(|| {
8110 ExcelError::new(ExcelErrorKind::NImpl)
8111 .with_message("FormulaPlane schedule referenced a stale span")
8112 })?;
8113 span.sheet_id
8114 };
8115 let current_sheet = self.graph.sheet_name(sheet_id);
8116 let authority = self.graph.formula_authority();
8117 let evaluator = SpanEvaluator::new(
8118 &authority.plane,
8119 self,
8120 current_sheet,
8121 self.graph.data_store(),
8122 self.graph.sheet_reg(),
8123 );
8124 #[cfg(test)]
8125 let mut last_group_report = None;
8126 while work_index < work_items.len() {
8127 let FormulaProducerId::Span(group_span_id) =
8128 work_items[work_index].producer
8129 else {
8130 break;
8131 };
8132 let group_span_ref =
8133 *span_refs_by_id.get(&group_span_id).ok_or_else(|| {
8134 ExcelError::new(ExcelErrorKind::NImpl).with_message(
8135 "FormulaPlane schedule referenced a stale span",
8136 )
8137 })?;
8138 let group_sheet_id = {
8139 let authority = self.graph.formula_authority();
8140 let span =
8141 authority.plane.spans.get(group_span_ref).ok_or_else(|| {
8142 ExcelError::new(ExcelErrorKind::NImpl).with_message(
8143 "FormulaPlane schedule referenced a stale span",
8144 )
8145 })?;
8146 span.sheet_id
8147 };
8148 if group_sheet_id != sheet_id {
8149 break;
8150 }
8151
8152 let dirty = producer_dirty_to_span_dirty(
8153 work_items[work_index].dirty.clone(),
8154 group_span_ref,
8155 );
8156 let task = SpanEvalTask {
8157 span: group_span_ref,
8158 dirty,
8159 plane_epoch,
8160 };
8161 let report =
8162 evaluator.evaluate_task(&task, &mut sink).map_err(|err| {
8163 ExcelError::new(ExcelErrorKind::NImpl).with_message(format!(
8164 "FormulaPlane span evaluation failed: {err:?}"
8165 ))
8166 })?;
8167 #[cfg(test)]
8168 {
8169 last_group_report = Some(report.clone());
8170 }
8171 computed_vertices = computed_vertices
8172 .saturating_add(report.span_eval_placement_count as usize);
8173 work_index = work_index.saturating_add(1);
8174 }
8175 #[cfg(test)]
8176 {
8177 if let Some(report) = last_group_report {
8178 self.last_formula_plane_span_eval_report = Some(report);
8179 }
8180 }
8181 }
8182 FormulaProducerId::Legacy(vertex_id) => {
8183 let _ = self.evaluate_vertex_impl(vertex_id, None)?;
8184 computed_vertices = computed_vertices.saturating_add(1);
8185 work_index = work_index.saturating_add(1);
8186 }
8187 }
8188 }
8189 self.flush_computed_write_buffer(&mut buffer)?;
8190 }
8191
8192 self.graph.clear_dirty_flags(&legacy_vertices);
8193 self.graph.redirty_volatiles();
8197 self.formula_plane_indexes_epoch_seen = self.graph.formula_authority().indexes_epoch();
8200 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
8201 Ok(EvalResult {
8202 computed_vertices,
8203 cycle_errors: 0,
8204 elapsed: start.elapsed(),
8205 })
8206 }
8207
8208 fn build_formula_plane_mixed_schedule(
8209 &self,
8210 span_seed_mode: SpanSeedMode,
8211 pending_changed_regions: &[Region],
8212 ) -> Result<FormulaPlaneMixedScheduleBuild, ExcelError> {
8213 let authority = self.graph.formula_authority();
8214 let mut producer_results = FormulaProducerResultIndex::default();
8215 let mut consumer_reads = FormulaConsumerReadIndex::default();
8216 let mut work = Vec::new();
8217
8218 let dirty_legacy: rustc_hash::FxHashSet<VertexId> =
8224 self.graph.get_evaluation_vertices().into_iter().collect();
8225
8226 let span_refs = authority.active_span_refs();
8227 let span_refs_by_id = span_refs
8228 .iter()
8229 .copied()
8230 .map(|span_ref| (span_ref.id, span_ref))
8231 .collect::<BTreeMap<_, _>>();
8232 for span_ref in &span_refs {
8233 let span = authority.plane.spans.get(*span_ref).ok_or_else(|| {
8234 ExcelError::new(ExcelErrorKind::NImpl)
8235 .with_message("FormulaPlane active span ref is stale")
8236 })?;
8237 let result_region = Region::from_domain(span.result_region.domain());
8238 producer_results.insert_producer(FormulaProducerId::Span(span.id), result_region);
8239 let Some(read_summary_id) = span.read_summary_id else {
8240 return Err(ExcelError::new(ExcelErrorKind::NImpl)
8241 .with_message("FormulaPlane active span is missing read summary"));
8242 };
8243 let Some(read_summary) = authority.plane.span_read_summaries.get(read_summary_id)
8244 else {
8245 return Err(ExcelError::new(ExcelErrorKind::NImpl)
8246 .with_message("FormulaPlane active span has stale read summary"));
8247 };
8248 if read_summary.result_region != result_region {
8249 return Err(ExcelError::new(ExcelErrorKind::NImpl)
8250 .with_message("FormulaPlane active span read summary is stale"));
8251 }
8252 for dependency in &read_summary.dependencies {
8253 consumer_reads.insert_read(
8254 FormulaProducerId::Span(span.id),
8255 dependency.read_region,
8256 read_summary.result_region,
8257 dependency.projection,
8258 );
8259 }
8260 if matches!(span_seed_mode, SpanSeedMode::WholeAll) {
8261 work.push(FormulaProducerWork {
8262 producer: FormulaProducerId::Span(span.id),
8263 dirty: ProducerDirtyDomain::Whole,
8264 });
8265 }
8266 }
8267
8268 let legacy_vertices = self.graph.formula_vertices();
8269 let mut scheduled_legacy_vertices = Vec::new();
8270 for vertex in &legacy_vertices {
8271 let Some(cell) = self.graph.get_cell_ref_for_vertex(*vertex) else {
8272 continue;
8273 };
8274 let result_region = Region::point(cell.sheet_id, cell.coord.row(), cell.coord.col());
8275 producer_results.insert_producer(FormulaProducerId::Legacy(*vertex), result_region);
8276 if dirty_legacy.contains(vertex) {
8277 scheduled_legacy_vertices.push(*vertex);
8278 work.push(FormulaProducerWork {
8279 producer: FormulaProducerId::Legacy(*vertex),
8280 dirty: ProducerDirtyDomain::Whole,
8281 });
8282 }
8283 }
8284
8285 for vertex in &legacy_vertices {
8286 let Some(cell) = self.graph.get_cell_ref_for_vertex(*vertex) else {
8287 continue;
8288 };
8289 let result_region = Region::point(cell.sheet_id, cell.coord.row(), cell.coord.col());
8290 let mut seen = rustc_hash::FxHashSet::default();
8291 for dep in self.graph.get_dependencies(*vertex) {
8292 let Some(dep_cell) = self.graph.get_cell_ref_for_vertex(dep) else {
8293 continue;
8294 };
8295 let read_region = Region::point(
8296 dep_cell.sheet_id,
8297 dep_cell.coord.row(),
8298 dep_cell.coord.col(),
8299 );
8300 if seen.insert(read_region) {
8301 consumer_reads.insert_read(
8302 FormulaProducerId::Legacy(*vertex),
8303 read_region,
8304 result_region,
8305 DirtyProjectionRule::WholeResult,
8306 );
8307 }
8308 }
8309 if let Some(ranges) = self.graph.get_range_dependencies(*vertex) {
8310 for range in ranges {
8311 let Some(read_region) = self.shared_range_to_region_pattern(range)? else {
8312 continue;
8313 };
8314 if seen.insert(read_region) {
8315 consumer_reads.insert_read(
8316 FormulaProducerId::Legacy(*vertex),
8317 read_region,
8318 result_region,
8319 DirtyProjectionRule::WholeResult,
8320 );
8321 }
8322 }
8323 }
8324 }
8325
8326 if matches!(span_seed_mode, SpanSeedMode::DirtyClosure)
8331 && !pending_changed_regions.is_empty()
8332 {
8333 use crate::formula_plane::producer::compute_dirty_closure;
8334 let producer_results_ref = &producer_results;
8335 let closure = compute_dirty_closure(
8336 &consumer_reads,
8337 pending_changed_regions.iter().copied(),
8338 |producer| producer_results_ref.producer_result_region(producer),
8339 );
8340 for fallback_work in closure.work {
8341 work.push(fallback_work);
8342 }
8343 if !closure.fallbacks.is_empty() {
8346 let mut already_whole: rustc_hash::FxHashSet<_> = work
8347 .iter()
8348 .filter_map(|w| match (w.producer, &w.dirty) {
8349 (FormulaProducerId::Span(id), ProducerDirtyDomain::Whole) => Some(id),
8350 _ => None,
8351 })
8352 .collect();
8353 for fb in &closure.fallbacks {
8354 if let FormulaProducerId::Span(id) = fb.consumer
8355 && already_whole.insert(id)
8356 {
8357 work.push(FormulaProducerWork {
8358 producer: FormulaProducerId::Span(id),
8359 dirty: ProducerDirtyDomain::Whole,
8360 });
8361 }
8362 }
8363 }
8364 }
8365
8366 let schedule = build_mixed_schedule(work, &producer_results, &consumer_reads);
8367 Ok((
8368 schedule,
8369 span_refs_by_id,
8370 authority.plane.epoch().0,
8371 scheduled_legacy_vertices,
8372 ))
8373 }
8374}
8375
8376#[derive(Clone, Copy, Debug)]
8380enum SpanSeedMode {
8381 WholeAll,
8382 DirtyClosure,
8383}
8384
8385impl<R> Engine<R>
8386where
8387 R: EvaluationContext,
8388{
8389 fn shared_range_to_region_pattern(
8390 &self,
8391 range: &crate::reference::SharedRangeRef<'static>,
8392 ) -> Result<Option<Region>, ExcelError> {
8393 use crate::reference::SharedSheetLocator;
8394 let sheet_id = match range.sheet {
8395 SharedSheetLocator::Id(id) => id,
8396 SharedSheetLocator::Current => self.graph.default_sheet_id(),
8397 SharedSheetLocator::Name(_) => return Ok(None),
8398 };
8399 match (
8400 range.start_row,
8401 range.end_row,
8402 range.start_col,
8403 range.end_col,
8404 ) {
8405 (Some(sr), Some(er), Some(sc), Some(ec)) => Ok(Some(Region::rect(
8406 sheet_id, sr.index, er.index, sc.index, ec.index,
8407 ))),
8408 (None, None, Some(sc), Some(ec)) if sc.index == ec.index => {
8409 Ok(Some(Region::whole_col(sheet_id, sc.index)))
8410 }
8411 (Some(sr), Some(er), None, None) if sr.index == er.index => {
8412 Ok(Some(Region::whole_row(sheet_id, sr.index)))
8413 }
8414 _ => Ok(None),
8415 }
8416 }
8417
8418 pub fn evaluate_all(&mut self) -> Result<EvalResult, ExcelError> {
8420 self.lookup_index_cache.reset_counters();
8421 let _source_cache = self.source_cache_session();
8422 self.validate_deterministic_mode()?;
8423 if self.config.defer_graph_building {
8424 self.build_graph_all()?;
8426 }
8427 self.evaluate_all_coordinator()
8428 }
8429
8430 fn evaluate_all_coordinator(&mut self) -> Result<EvalResult, ExcelError> {
8435 if self.config.formula_plane_mode == FormulaPlaneMode::AuthoritativeExperimental {
8436 return self.evaluate_authoritative_formula_plane_all();
8437 }
8438 self.evaluate_all_legacy_impl()
8439 }
8440
8441 fn legacy_pass_apply_cycles(&mut self, schedule: &crate::engine::scheduler::Schedule) -> usize {
8442 let circ_error = LiteralValue::Error(
8443 ExcelError::new(ExcelErrorKind::Circ)
8444 .with_message("Circular dependency detected".to_string()),
8445 );
8446 for cycle in &schedule.cycles {
8447 for &vertex_id in cycle {
8448 self.graph
8449 .update_vertex_value(vertex_id, circ_error.clone());
8450 self.mirror_vertex_value_to_overlay(vertex_id, &circ_error);
8451 }
8452 }
8453 schedule.cycles.len()
8454 }
8455
8456 fn legacy_pass_run_layers(
8457 &mut self,
8458 schedule: &crate::engine::scheduler::Schedule,
8459 ) -> Result<usize, ExcelError> {
8460 let mut computed_vertices = 0;
8461 for layer in &schedule.layers {
8462 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
8463 computed_vertices += self.evaluate_layer_parallel(layer)?;
8464 } else {
8465 computed_vertices += self.evaluate_layer_sequential(layer)?;
8466 }
8467 }
8468 Ok(computed_vertices)
8469 }
8470
8471 fn evaluate_all_legacy_impl(&mut self) -> Result<EvalResult, ExcelError> {
8476 self.reset_virtual_dep_telemetry_if_disabled();
8477 #[cfg(feature = "tracing")]
8478 let _span_eval = tracing::info_span!("evaluate_all").entered();
8479 let start = crate::instant::FzInstant::now();
8480 let mut computed_vertices = 0;
8481 let mut cycle_errors = 0;
8482 let mut replan_iterations = 0;
8483 const MAX_REPLAN: usize = 5;
8484 let mut telemetry = self
8485 .config
8486 .enable_virtual_dep_telemetry
8487 .then(|| self.start_virtual_dep_telemetry());
8488
8489 loop {
8490 let to_evaluate = self.graph.get_evaluation_vertices();
8491 if to_evaluate.is_empty() {
8492 if let Some(t) = telemetry.as_mut()
8493 && t.bailout_reason.is_none()
8494 {
8495 t.bailout_reason = Some("no_work");
8496 }
8497 break;
8498 }
8499
8500 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
8501 if let Some(t) = telemetry.as_mut() {
8502 Self::accumulate_schedule_meta(t, &meta);
8503 }
8504
8505 cycle_errors += self.legacy_pass_apply_cycles(&schedule);
8506 computed_vertices += self.legacy_pass_run_layers(&schedule)?;
8507
8508 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
8510 if let Some(t) = telemetry.as_mut() {
8511 t.changed_vdeps_total += changed_vertices.len();
8512 }
8513
8514 self.graph.clear_dirty_flags(&to_evaluate);
8515 for v in &changed_vertices {
8516 self.graph.set_dirty(*v, true);
8517 }
8518
8519 if changed_vertices.is_empty() {
8520 if let Some(t) = telemetry.as_mut() {
8521 t.bailout_reason = Some("converged");
8522 }
8523 break;
8524 }
8525 if replan_iterations >= MAX_REPLAN {
8526 if let Some(t) = telemetry.as_mut() {
8527 t.bailout_reason = Some("max_replan");
8528 }
8529 break;
8530 }
8531
8532 replan_iterations += 1;
8533 }
8534
8535 if let Some(mut t) = telemetry {
8536 t.replan_iterations = replan_iterations;
8537 self.last_virtual_dep_telemetry = t;
8538 }
8539
8540 self.graph.redirty_volatiles();
8542
8543 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
8545
8546 Ok(EvalResult {
8547 computed_vertices,
8548 cycle_errors,
8549 elapsed: start.elapsed(),
8550 })
8551 }
8552
8553 pub fn evaluate_all_with_delta(&mut self) -> Result<(EvalResult, EvalDelta), ExcelError> {
8554 let mut collector = DeltaCollector::new(DeltaMode::Cells);
8555 let result = self.evaluate_all_with_delta_collector(&mut collector)?;
8556 Ok((result, collector.finish()))
8557 }
8558
8559 fn evaluate_all_with_delta_collector(
8560 &mut self,
8561 delta: &mut DeltaCollector,
8562 ) -> Result<EvalResult, ExcelError> {
8563 let _source_cache = self.source_cache_session();
8564 if self.config.defer_graph_building {
8565 self.build_graph_all()?;
8566 }
8567 if self.graph.formula_authority().active_span_count() > 0 {
8568 let _ = delta;
8569 return self.evaluate_authoritative_formula_plane_all();
8570 }
8571 self.reset_virtual_dep_telemetry_if_disabled();
8572 #[cfg(feature = "tracing")]
8573 let _span_eval = tracing::info_span!("evaluate_all_with_delta").entered();
8574 let start = crate::instant::FzInstant::now();
8575 let mut computed_vertices = 0;
8576 let mut cycle_errors = 0;
8577
8578 let mut replan_iterations = 0;
8579 const MAX_REPLAN: usize = 5;
8580 let mut telemetry = self
8581 .config
8582 .enable_virtual_dep_telemetry
8583 .then(|| self.start_virtual_dep_telemetry());
8584
8585 loop {
8586 let to_evaluate = self.graph.get_evaluation_vertices();
8587 if to_evaluate.is_empty() {
8588 if let Some(t) = telemetry.as_mut()
8589 && t.bailout_reason.is_none()
8590 {
8591 t.bailout_reason = Some("no_work");
8592 }
8593 break;
8594 }
8595
8596 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
8597 if let Some(t) = telemetry.as_mut() {
8598 Self::accumulate_schedule_meta(t, &meta);
8599 }
8600
8601 let circ_error = LiteralValue::Error(
8602 ExcelError::new(ExcelErrorKind::Circ)
8603 .with_message("Circular dependency detected".to_string()),
8604 );
8605 for cycle in &schedule.cycles {
8606 cycle_errors += 1;
8607 for &vertex_id in cycle {
8608 if delta.mode != DeltaMode::Off
8609 && let Some(cell) = self.graph.get_cell_ref_for_vertex(vertex_id)
8610 {
8611 let sheet_name = self.graph.sheet_name(cell.sheet_id);
8612 let old = self
8613 .read_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
8614 .unwrap_or(LiteralValue::Empty);
8615 if old != circ_error {
8616 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
8617 }
8618 }
8619 self.graph
8620 .update_vertex_value(vertex_id, circ_error.clone());
8621 self.mirror_vertex_value_to_overlay(vertex_id, &circ_error);
8622 }
8623 }
8624
8625 for layer in &schedule.layers {
8626 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
8627 computed_vertices += self.evaluate_layer_parallel_with_delta(layer, delta)?;
8628 } else {
8629 computed_vertices += self.evaluate_layer_sequential_with_delta(layer, delta)?;
8630 }
8631 }
8632
8633 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
8634 if let Some(t) = telemetry.as_mut() {
8635 t.changed_vdeps_total += changed_vertices.len();
8636 }
8637 self.graph.clear_dirty_flags(&to_evaluate);
8638 for v in &changed_vertices {
8639 self.graph.set_dirty(*v, true);
8640 }
8641
8642 if changed_vertices.is_empty() {
8643 if let Some(t) = telemetry.as_mut() {
8644 t.bailout_reason = Some("converged");
8645 }
8646 break;
8647 }
8648 if replan_iterations >= MAX_REPLAN {
8649 if let Some(t) = telemetry.as_mut() {
8650 t.bailout_reason = Some("max_replan");
8651 }
8652 break;
8653 }
8654 replan_iterations += 1;
8655 }
8656
8657 if let Some(mut t) = telemetry {
8658 t.replan_iterations = replan_iterations;
8659 self.last_virtual_dep_telemetry = t;
8660 }
8661
8662 self.graph.redirty_volatiles();
8663 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
8664
8665 Ok(EvalResult {
8666 computed_vertices,
8667 cycle_errors,
8668 elapsed: start.elapsed(),
8669 })
8670 }
8671
8672 pub fn evaluate_cell(
8682 &mut self,
8683 sheet: &str,
8684 row: u32,
8685 col: u32,
8686 ) -> Result<Option<LiteralValue>, ExcelError> {
8687 if row == 0 || col == 0 {
8688 return Err(ExcelError::new(ExcelErrorKind::Ref)
8689 .with_message("Row and column must be >= 1".to_string()));
8690 }
8691
8692 if self.config.defer_graph_building {
8700 self.build_graph_all()?;
8701 }
8702
8703 let result = self.evaluate_cells(&[(sheet, row, col)])?;
8704
8705 match result.len() {
8706 0 => Ok(None),
8707 1 => {
8708 let v = result.into_iter().next().unwrap();
8709 Ok(v)
8710 }
8711 _ => unreachable!("evaluate_cells returned unexpected length"),
8712 }
8713 }
8714
8715 pub fn evaluate_cells(
8722 &mut self,
8723 targets: &[(&str, u32, u32)],
8724 ) -> Result<Vec<Option<LiteralValue>>, ExcelError> {
8725 self.validate_deterministic_mode()?;
8726 if targets.is_empty() {
8727 return Ok(Vec::new());
8728 }
8729 if self.config.defer_graph_building {
8733 self.build_graph_all()?;
8734 }
8735 if self.graph.formula_authority().active_span_count() > 0 {
8736 let _ = self.evaluate_authoritative_formula_plane_all()?;
8737 } else {
8738 self.evaluate_until(targets)?;
8739 }
8740 Ok(targets
8741 .iter()
8742 .map(|(s, r, c)| self.get_cell_value(s, *r, *c))
8743 .collect())
8744 }
8745
8746 pub fn evaluate_cells_cancellable(
8747 &mut self,
8748 targets: &[(&str, u32, u32)],
8749 cancel_flag: Arc<AtomicBool>,
8750 ) -> Result<Vec<Option<LiteralValue>>, ExcelError> {
8751 self.active_cancel_flag = Some(cancel_flag.clone());
8752 let res = self.evaluate_cells_cancellable_impl(targets, &cancel_flag);
8753 self.active_cancel_flag = None;
8754 res
8755 }
8756
8757 fn evaluate_cells_cancellable_impl(
8758 &mut self,
8759 targets: &[(&str, u32, u32)],
8760 cancel_flag: &AtomicBool,
8761 ) -> Result<Vec<Option<LiteralValue>>, ExcelError> {
8762 self.validate_deterministic_mode()?;
8763 if targets.is_empty() {
8764 return Ok(Vec::new());
8765 }
8766 if self.config.defer_graph_building {
8770 self.build_graph_all()?;
8771 }
8772 if self.graph.formula_authority().active_span_count() > 0 {
8773 if cancel_flag.load(Ordering::Relaxed) {
8774 return Err(ExcelError::new(ExcelErrorKind::Cancelled).with_message(
8775 "Evaluation cancelled before FormulaPlane scheduling".to_string(),
8776 ));
8777 }
8778 let _ = self.evaluate_authoritative_formula_plane_all()?;
8779 return Ok(targets
8780 .iter()
8781 .map(|(s, r, c)| self.get_cell_value(s, *r, *c))
8782 .collect());
8783 }
8784
8785 let a1_targets: Vec<String> = targets
8788 .iter()
8789 .map(|(s, r, c)| {
8790 format!("{}!{}", s, col_letters_from_1based(*c).unwrap()) + &r.to_string()
8791 })
8792 .collect();
8793 let a1_refs: Vec<&str> = a1_targets.iter().map(|s| s.as_str()).collect();
8794
8795 self.evaluate_until_cancellable_impl(&a1_refs, cancel_flag)?;
8796
8797 Ok(targets
8798 .iter()
8799 .map(|(s, r, c)| self.get_cell_value(s, *r, *c))
8800 .collect())
8801 }
8802
8803 pub fn evaluate_cells_with_delta(
8804 &mut self,
8805 targets: &[(&str, u32, u32)],
8806 ) -> Result<(Vec<Option<LiteralValue>>, EvalDelta), ExcelError> {
8807 self.validate_deterministic_mode()?;
8808 if targets.is_empty() {
8809 return Ok((Vec::new(), EvalDelta::default()));
8810 }
8811 if self.config.defer_graph_building {
8812 let mut sheets: rustc_hash::FxHashSet<&str> = rustc_hash::FxHashSet::default();
8813 for (s, _, _) in targets.iter() {
8814 sheets.insert(*s);
8815 }
8816 self.build_graph_for_sheets(sheets.iter().cloned())?;
8817 }
8818 if self.graph.formula_authority().active_span_count() > 0 {
8819 let _ = self.evaluate_authoritative_formula_plane_all()?;
8820 let values = targets
8821 .iter()
8822 .map(|(s, r, c)| self.get_cell_value(s, *r, *c))
8823 .collect();
8824 return Ok((values, EvalDelta::default()));
8825 }
8826 let mut collector = DeltaCollector::new(DeltaMode::Cells);
8827 self.evaluate_until_with_delta_collector(targets, &mut collector)?;
8828 let values = targets
8829 .iter()
8830 .map(|(s, r, c)| self.get_cell_value(s, *r, *c))
8831 .collect();
8832 Ok((values, collector.finish()))
8833 }
8834
8835 pub fn get_eval_plan(&self, targets: &[(&str, u32, u32)]) -> Result<EvalPlan, ExcelError> {
8837 if targets.is_empty() {
8838 return Ok(EvalPlan {
8839 total_vertices_to_evaluate: 0,
8840 layers: Vec::new(),
8841 cycles_detected: 0,
8842 dirty_count: 0,
8843 volatile_count: 0,
8844 parallel_enabled: self.config.enable_parallel && self.thread_pool.is_some(),
8845 estimated_parallel_layers: 0,
8846 target_cells: Vec::new(),
8847 });
8848 }
8849 if self.config.defer_graph_building && self.has_staged_formulas() {
8850 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(
8851 "Evaluation plan requested with deferred graph; build first or call evaluate_*",
8852 ));
8853 }
8854
8855 let addresses: Vec<String> = targets
8857 .iter()
8858 .map(|(s, r, c)| format!("{}!{}{}", s, Self::col_to_letters(*c), r))
8859 .collect();
8860
8861 let mut target_addrs = Vec::new();
8863 for (sheet, row, col) in targets {
8864 if let Some(sheet_id) = self.graph.sheet_id(sheet) {
8865 let coord = Coord::from_excel(*row, *col, true, true);
8866 target_addrs.push(CellRef::new(sheet_id, coord));
8867 }
8868 }
8869
8870 let mut target_vertex_ids = Vec::new();
8872 for addr in &target_addrs {
8873 if let Some(vertex_id) = self.graph.get_vertex_id_for_address(addr) {
8874 target_vertex_ids.push(*vertex_id);
8875 }
8876 }
8877
8878 if target_vertex_ids.is_empty() {
8879 return Ok(EvalPlan {
8880 total_vertices_to_evaluate: 0,
8881 layers: Vec::new(),
8882 cycles_detected: 0,
8883 dirty_count: 0,
8884 volatile_count: 0,
8885 parallel_enabled: self.config.enable_parallel && self.thread_pool.is_some(),
8886 estimated_parallel_layers: 0,
8887 target_cells: addresses,
8888 });
8889 }
8890
8891 let (precedents_to_eval, vdeps) = self.build_demand_subgraph(&target_vertex_ids);
8893
8894 if precedents_to_eval.is_empty() {
8895 return Ok(EvalPlan {
8896 total_vertices_to_evaluate: 0,
8897 layers: Vec::new(),
8898 cycles_detected: 0,
8899 dirty_count: 0,
8900 volatile_count: 0,
8901 parallel_enabled: self.config.enable_parallel && self.thread_pool.is_some(),
8902 estimated_parallel_layers: 0,
8903 target_cells: addresses,
8904 });
8905 }
8906
8907 let mut dirty_count = 0;
8909 let mut volatile_count = 0;
8910 for &vertex_id in &precedents_to_eval {
8911 if self.graph.is_dirty(vertex_id) {
8912 dirty_count += 1;
8913 }
8914 if self.graph.is_volatile(vertex_id) {
8915 volatile_count += 1;
8916 }
8917 }
8918
8919 let scheduler = Scheduler::new(&self.graph);
8921 let schedule = scheduler.create_schedule_with_virtual(&precedents_to_eval, &vdeps)?;
8922
8923 let mut layers = Vec::new();
8925 let mut estimated_parallel_layers = 0;
8926 let parallel_enabled = self.config.enable_parallel && self.thread_pool.is_some();
8927
8928 for layer in &schedule.layers {
8929 let parallel_eligible = parallel_enabled && layer.vertices.len() > 1;
8930 if parallel_eligible {
8931 estimated_parallel_layers += 1;
8932 }
8933
8934 let sample_cells: Vec<String> = layer
8936 .vertices
8937 .iter()
8938 .take(5)
8939 .filter_map(|&vertex_id| {
8940 self.graph
8941 .get_cell_ref_for_vertex(vertex_id)
8942 .map(|cell_ref| {
8943 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
8944 format!(
8945 "{}!{}{}",
8946 sheet_name,
8947 Self::col_to_letters(cell_ref.coord.col()),
8948 cell_ref.coord.row() + 1
8949 )
8950 })
8951 })
8952 .collect();
8953
8954 layers.push(LayerInfo {
8955 vertex_count: layer.vertices.len(),
8956 parallel_eligible,
8957 sample_cells,
8958 });
8959 }
8960
8961 Ok(EvalPlan {
8962 total_vertices_to_evaluate: precedents_to_eval.len(),
8963 layers,
8964 cycles_detected: schedule.cycles.len(),
8965 dirty_count,
8966 volatile_count,
8967 parallel_enabled,
8968 estimated_parallel_layers,
8969 target_cells: addresses,
8970 })
8971 }
8972 fn create_evaluation_schedule(
8974 &mut self,
8975 to_evaluate: &[VertexId],
8976 ) -> Result<ScheduleBuildOutput, ExcelError> {
8977 if self.can_use_static_schedule_cache(to_evaluate) {
8978 if let Some(cached) = self.cached_static_schedule.as_ref()
8979 && cached.topology_epoch == self.topology_epoch
8980 && cached.candidate_vertices.as_slice() == to_evaluate
8981 {
8982 let meta = ScheduleBuildMeta {
8983 candidate_vertices: to_evaluate.len(),
8984 vdeps_vertices: 0,
8985 vdeps_edges: 0,
8986 builder_elapsed_ms: 0,
8987 used_virtual_schedule: false,
8988 schedule_cache_hit: true,
8989 schedule_cache_eligible: true,
8990 };
8991 return Ok((cached.schedule.clone(), FxHashMap::default(), meta));
8992 }
8993
8994 let (schedule, vdeps, mut meta) =
8995 self.create_evaluation_schedule_uncached(to_evaluate)?;
8996 meta.schedule_cache_hit = false;
8997 meta.schedule_cache_eligible = true;
8998 if vdeps.is_empty() {
8999 self.cached_static_schedule = Some(CachedScheduleEntry {
9000 topology_epoch: self.topology_epoch,
9001 candidate_vertices: to_evaluate.to_vec(),
9002 schedule: schedule.clone(),
9003 });
9004 }
9005 return Ok((schedule, vdeps, meta));
9006 }
9007
9008 let (schedule, vdeps, mut meta) = self.create_evaluation_schedule_uncached(to_evaluate)?;
9009 meta.schedule_cache_hit = false;
9010 meta.schedule_cache_eligible = false;
9011 Ok((schedule, vdeps, meta))
9012 }
9013
9014 fn create_evaluation_schedule_uncached(
9015 &self,
9016 to_evaluate: &[VertexId],
9017 ) -> Result<ScheduleBuildOutput, ExcelError> {
9018 let builder = VirtualDepBuilder::new(self);
9019 let (vdeps, augmented, builder_elapsed_ms, vdeps_edges) =
9020 if self.config.enable_virtual_dep_telemetry {
9021 let build_started = crate::instant::FzInstant::now();
9022 let (vdeps, augmented) = builder.build(to_evaluate);
9023 let builder_elapsed_ms = build_started.elapsed().as_millis();
9024 let vdeps_edges = vdeps.values().map(|deps| deps.len()).sum::<usize>();
9025 (vdeps, augmented, builder_elapsed_ms, vdeps_edges)
9026 } else {
9027 let (vdeps, augmented) = builder.build(to_evaluate);
9028 (vdeps, augmented, 0, 0)
9029 };
9030
9031 let mut final_evaluate = to_evaluate.to_vec();
9032 if !augmented.is_empty() {
9033 final_evaluate.extend(augmented);
9034 final_evaluate.sort_unstable();
9035 final_evaluate.dedup();
9036 }
9037
9038 let use_virtual = !vdeps.is_empty();
9039
9040 let scheduler = Scheduler::new(&self.graph);
9041 let schedule = if use_virtual {
9042 scheduler.create_schedule_with_virtual(&final_evaluate, &vdeps)?
9043 } else {
9044 scheduler.create_schedule(&final_evaluate)?
9045 };
9046
9047 let meta = ScheduleBuildMeta {
9048 candidate_vertices: to_evaluate.len(),
9049 vdeps_vertices: vdeps.len(),
9050 vdeps_edges,
9051 builder_elapsed_ms,
9052 used_virtual_schedule: use_virtual,
9053 schedule_cache_hit: false,
9054 schedule_cache_eligible: false,
9055 };
9056
9057 Ok((schedule, vdeps, meta))
9058 }
9059
9060 fn can_use_static_schedule_cache(&self, to_evaluate: &[VertexId]) -> bool {
9061 !to_evaluate.is_empty()
9062 && to_evaluate.iter().copied().all(|v| {
9063 !self.graph.is_dynamic(v) && self.graph.get_range_dependencies(v).is_none()
9064 })
9065 }
9066
9067 fn start_virtual_dep_telemetry(&self) -> VirtualDepTelemetry {
9068 VirtualDepTelemetry {
9069 fallback_mode_activations: self.virtual_dep_fallback_activations,
9070 ..VirtualDepTelemetry::default()
9071 }
9072 }
9073
9074 fn accumulate_schedule_meta(telemetry: &mut VirtualDepTelemetry, meta: &ScheduleBuildMeta) {
9075 telemetry.candidate_vertices_total += meta.candidate_vertices;
9076 telemetry.vdeps_vertices_total += meta.vdeps_vertices;
9077 telemetry.vdeps_edges_total += meta.vdeps_edges;
9078 telemetry.builder_elapsed_ms_total += meta.builder_elapsed_ms;
9079 if meta.schedule_cache_eligible {
9080 if meta.schedule_cache_hit {
9081 telemetry.schedule_cache_hits += 1;
9082 telemetry.reused_schedule_vertices_total += meta.candidate_vertices;
9083 } else {
9084 telemetry.schedule_cache_misses += 1;
9085 }
9086 }
9087 if meta.used_virtual_schedule {
9088 telemetry.schedule_virtual_passes += 1;
9089 } else {
9090 telemetry.schedule_static_passes += 1;
9091 }
9092 }
9093
9094 fn changed_virtual_dep_vertices(
9095 &self,
9096 to_evaluate: &[VertexId],
9097 old_vdeps: &FxHashMap<VertexId, Vec<VertexId>>,
9098 ) -> Vec<VertexId> {
9099 if !to_evaluate
9100 .iter()
9101 .copied()
9102 .any(|v| self.graph.is_dynamic(v))
9103 {
9104 return Vec::new();
9105 }
9106
9107 let builder = VirtualDepBuilder::new(self);
9108 let (new_vdeps, _) = builder.build(to_evaluate);
9109
9110 let mut candidates = FxHashSet::default();
9111 candidates.extend(old_vdeps.keys().copied());
9112 candidates.extend(new_vdeps.keys().copied());
9113
9114 let mut changed = Vec::new();
9115 for v in candidates {
9116 if old_vdeps.get(&v) != new_vdeps.get(&v) {
9117 changed.push(v);
9118 }
9119 }
9120 changed
9121 }
9122
9123 fn build_demand_subgraph(
9126 &self,
9127 target_vertices: &[VertexId],
9128 ) -> (
9129 Vec<VertexId>,
9130 rustc_hash::FxHashMap<VertexId, Vec<VertexId>>,
9131 ) {
9132 #[cfg(feature = "tracing")]
9133 let _span =
9134 tracing::info_span!("demand_subgraph", targets = target_vertices.len()).entered();
9135 use rustc_hash::{FxHashMap, FxHashSet};
9136
9137 let mut to_evaluate: FxHashSet<VertexId> = FxHashSet::default();
9138 let mut visited: FxHashSet<VertexId> = FxHashSet::default();
9139 let mut stack: Vec<VertexId> = Vec::new();
9140 let mut vdeps: FxHashMap<VertexId, Vec<VertexId>> = FxHashMap::default(); for &t in target_vertices {
9143 stack.push(t);
9144 }
9145
9146 while let Some(v) = stack.pop() {
9147 if !visited.insert(v) {
9148 continue;
9149 }
9150 if !self.graph.vertex_exists(v) {
9151 continue;
9152 }
9153 match self.graph.get_vertex_kind(v) {
9161 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
9162 if self.graph.is_dirty(v) || self.graph.is_volatile(v) {
9163 to_evaluate.insert(v);
9164 }
9165 }
9166 VertexKind::NamedScalar
9167 | VertexKind::NamedArray
9168 | VertexKind::Range
9169 | VertexKind::InfiniteRange => {
9170 to_evaluate.insert(v);
9171 }
9172 _ => {}
9173 }
9174
9175 if let Some(dependencies) = self.graph.dependencies_slice(v) {
9186 for &dep in dependencies {
9187 if self.graph.vertex_exists(dep) && !visited.contains(&dep) {
9188 stack.push(dep);
9189 }
9190 }
9191 } else {
9192 for dep in self.graph.get_dependencies(v) {
9193 if self.graph.vertex_exists(dep) && !visited.contains(&dep) {
9194 stack.push(dep);
9195 }
9196 }
9197 } let builder = VirtualDepBuilder::new(self);
9199 let (vdeps_map, _) = builder.build(&[v]);
9200 if let Some(deps) = vdeps_map.get(&v) {
9201 for &u in deps {
9202 vdeps.entry(v).or_default().push(u);
9203 if !visited.contains(&u) {
9204 stack.push(u);
9205 }
9206 }
9207 }
9208 }
9209
9210 let mut result: Vec<VertexId> = to_evaluate.into_iter().collect();
9211 result.sort_unstable();
9212 for deps in vdeps.values_mut() {
9214 deps.sort_unstable();
9215 deps.dedup();
9216 }
9217 (result, vdeps)
9218 }
9219
9220 fn col_to_letters(col: u32) -> String {
9222 col_letters_from_1based(col).expect("column index must be >= 1")
9223 }
9224
9225 pub fn evaluate_all_cancellable(
9227 &mut self,
9228 cancel_flag: Arc<AtomicBool>,
9229 ) -> Result<EvalResult, ExcelError> {
9230 self.active_cancel_flag = Some(cancel_flag.clone());
9231 let res = self.evaluate_all_cancellable_impl(&cancel_flag);
9232 self.active_cancel_flag = None;
9233 res
9234 }
9235
9236 fn evaluate_all_cancellable_impl(
9237 &mut self,
9238 cancel_flag: &AtomicBool,
9239 ) -> Result<EvalResult, ExcelError> {
9240 let _source_cache = self.source_cache_session();
9241 self.validate_deterministic_mode()?;
9242 if self.config.defer_graph_building {
9243 self.build_graph_all()?;
9244 }
9245 if self.graph.formula_authority().active_span_count() > 0 {
9246 if cancel_flag.load(Ordering::Relaxed) {
9247 return Err(ExcelError::new(ExcelErrorKind::Cancelled).with_message(
9248 "Evaluation cancelled before FormulaPlane scheduling".to_string(),
9249 ));
9250 }
9251 return self.evaluate_authoritative_formula_plane_all();
9252 }
9253 self.reset_virtual_dep_telemetry_if_disabled();
9254 let start = crate::instant::FzInstant::now();
9255 let mut computed_vertices = 0;
9256 let mut cycle_errors = 0;
9257
9258 let mut replan_iterations = 0;
9259 const MAX_REPLAN: usize = 5;
9260 let mut telemetry = self
9261 .config
9262 .enable_virtual_dep_telemetry
9263 .then(|| self.start_virtual_dep_telemetry());
9264
9265 loop {
9266 if cancel_flag.load(Ordering::Relaxed) {
9267 if let Some(mut t) = telemetry {
9268 t.bailout_reason = Some("cancelled");
9269 t.replan_iterations = replan_iterations;
9270 self.last_virtual_dep_telemetry = t;
9271 }
9272 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
9273 .with_message("Evaluation cancelled before scheduling".to_string()));
9274 }
9275
9276 let to_evaluate = self.graph.get_evaluation_vertices();
9277 if to_evaluate.is_empty() {
9278 if let Some(t) = telemetry.as_mut()
9279 && t.bailout_reason.is_none()
9280 {
9281 t.bailout_reason = Some("no_work");
9282 }
9283 break;
9284 }
9285
9286 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
9287 if let Some(t) = telemetry.as_mut() {
9288 Self::accumulate_schedule_meta(t, &meta);
9289 }
9290
9291 for cycle in &schedule.cycles {
9293 if cancel_flag.load(Ordering::Relaxed) {
9295 if let Some(mut t) = telemetry {
9296 t.bailout_reason = Some("cancelled");
9297 t.replan_iterations = replan_iterations;
9298 self.last_virtual_dep_telemetry = t;
9299 }
9300 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
9301 .with_message("Evaluation cancelled during cycle handling".to_string()));
9302 }
9303
9304 cycle_errors += 1;
9305 let circ_error = LiteralValue::Error(
9306 ExcelError::new(ExcelErrorKind::Circ)
9307 .with_message("Circular dependency detected".to_string()),
9308 );
9309 for &vertex_id in cycle {
9310 self.graph
9311 .update_vertex_value(vertex_id, circ_error.clone());
9312 self.mirror_vertex_value_to_overlay(vertex_id, &circ_error);
9313 }
9314 }
9315
9316 for layer in &schedule.layers {
9318 if cancel_flag.load(Ordering::Relaxed) {
9320 if let Some(mut t) = telemetry {
9321 t.bailout_reason = Some("cancelled");
9322 t.replan_iterations = replan_iterations;
9323 self.last_virtual_dep_telemetry = t;
9324 }
9325 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
9326 .with_message("Evaluation cancelled between layers".to_string()));
9327 }
9328
9329 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
9331 computed_vertices +=
9332 self.evaluate_layer_parallel_cancellable(layer, cancel_flag)?;
9333 } else {
9334 computed_vertices +=
9335 self.evaluate_layer_sequential_cancellable(layer, cancel_flag)?;
9336 }
9337 }
9338
9339 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
9340 if let Some(t) = telemetry.as_mut() {
9341 t.changed_vdeps_total += changed_vertices.len();
9342 }
9343 self.graph.clear_dirty_flags(&to_evaluate);
9344 for v in &changed_vertices {
9345 self.graph.set_dirty(*v, true);
9346 }
9347
9348 if changed_vertices.is_empty() {
9349 if let Some(t) = telemetry.as_mut() {
9350 t.bailout_reason = Some("converged");
9351 }
9352 break;
9353 }
9354 if replan_iterations >= MAX_REPLAN {
9355 if let Some(t) = telemetry.as_mut() {
9356 t.bailout_reason = Some("max_replan");
9357 }
9358 break;
9359 }
9360 replan_iterations += 1;
9361 }
9362
9363 if let Some(mut t) = telemetry {
9364 t.replan_iterations = replan_iterations;
9365 self.last_virtual_dep_telemetry = t;
9366 }
9367
9368 self.graph.redirty_volatiles();
9370 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
9371
9372 Ok(EvalResult {
9373 computed_vertices,
9374 cycle_errors,
9375 elapsed: start.elapsed(),
9376 })
9377 }
9378
9379 pub fn evaluate_until_cancellable(
9381 &mut self,
9382 targets: &[&str],
9383 cancel_flag: Arc<AtomicBool>,
9384 ) -> Result<EvalResult, ExcelError> {
9385 self.active_cancel_flag = Some(cancel_flag.clone());
9386 let res = self.evaluate_until_cancellable_impl(targets, &cancel_flag);
9387 self.active_cancel_flag = None;
9388 res
9389 }
9390
9391 fn evaluate_until_cancellable_impl(
9392 &mut self,
9393 targets: &[&str],
9394 cancel_flag: &AtomicBool,
9395 ) -> Result<EvalResult, ExcelError> {
9396 let start = crate::instant::FzInstant::now();
9397 if self.graph.formula_authority().active_span_count() > 0 {
9398 if cancel_flag.load(Ordering::Relaxed) {
9399 return Err(ExcelError::new(ExcelErrorKind::Cancelled).with_message(
9400 "Evaluation cancelled before FormulaPlane scheduling".to_string(),
9401 ));
9402 }
9403 return self.evaluate_authoritative_formula_plane_all();
9404 }
9405
9406 let mut target_addrs = Vec::new();
9408 for target in targets {
9409 let (sheet, row, col) = self.parse_a1_notation(target)?;
9410 let sheet_id = self.graph.sheet_id_mut(&sheet);
9411 let coord = Coord::from_excel(row, col, true, true);
9412 target_addrs.push(CellRef::new(sheet_id, coord));
9413 }
9414
9415 let mut target_vertex_ids = Vec::new();
9417 for addr in &target_addrs {
9418 if let Some(vertex_id) = self.graph.get_vertex_id_for_address(addr) {
9419 target_vertex_ids.push(*vertex_id);
9420 }
9421 }
9422
9423 if target_vertex_ids.is_empty() {
9424 return Ok(EvalResult {
9425 computed_vertices: 0,
9426 cycle_errors: 0,
9427 elapsed: start.elapsed(),
9428 });
9429 }
9430
9431 let (precedents_to_eval, vdeps) = self.build_demand_subgraph(&target_vertex_ids);
9433
9434 if precedents_to_eval.is_empty() {
9435 return Ok(EvalResult {
9436 computed_vertices: 0,
9437 cycle_errors: 0,
9438 elapsed: start.elapsed(),
9439 });
9440 }
9441
9442 let scheduler = Scheduler::new(&self.graph);
9444 let schedule = scheduler.create_schedule_with_virtual(&precedents_to_eval, &vdeps)?;
9445
9446 let mut cycle_errors = 0;
9448 for cycle in &schedule.cycles {
9449 if cancel_flag.load(Ordering::Relaxed) {
9451 return Err(ExcelError::new(ExcelErrorKind::Cancelled).with_message(
9452 "Demand-driven evaluation cancelled during cycle handling".to_string(),
9453 ));
9454 }
9455
9456 cycle_errors += 1;
9457 let circ_error = LiteralValue::Error(
9458 ExcelError::new(ExcelErrorKind::Circ)
9459 .with_message("Circular dependency detected".to_string()),
9460 );
9461 for &vertex_id in cycle {
9462 self.graph
9463 .update_vertex_value(vertex_id, circ_error.clone());
9464 self.mirror_vertex_value_to_overlay(vertex_id, &circ_error);
9465 }
9466 }
9467
9468 let mut computed_vertices = 0;
9470 for layer in &schedule.layers {
9471 if cancel_flag.load(Ordering::Relaxed) {
9473 return Err(ExcelError::new(ExcelErrorKind::Cancelled).with_message(
9474 "Demand-driven evaluation cancelled between layers".to_string(),
9475 ));
9476 }
9477
9478 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
9480 computed_vertices +=
9481 self.evaluate_layer_parallel_cancellable(layer, cancel_flag)?;
9482 } else {
9483 computed_vertices +=
9484 self.evaluate_layer_sequential_cancellable_demand_driven(layer, cancel_flag)?;
9485 }
9486 }
9487
9488 self.graph.clear_dirty_flags(&precedents_to_eval);
9490
9491 self.graph.redirty_volatiles();
9493
9494 Ok(EvalResult {
9495 computed_vertices,
9496 cycle_errors,
9497 elapsed: start.elapsed(),
9498 })
9499 }
9500
9501 fn parse_a1_notation(&self, address: &str) -> Result<(String, u32, u32), ExcelError> {
9502 let mut parts = address.splitn(2, '!');
9503 let first = parts.next().unwrap_or_default();
9504 let remainder = parts.next();
9505
9506 let (sheet, cell_part) = match remainder {
9507 Some(cell) => (first.to_string(), cell),
9508 None => (self.default_sheet_name().to_string(), first),
9509 };
9510
9511 let (row, col, _, _) = parse_a1_1based(cell_part).map_err(|err| {
9512 ExcelError::new(ExcelErrorKind::Ref)
9513 .with_message(format!("Invalid cell reference `{cell_part}`: {err}"))
9514 })?;
9515
9516 Ok((sheet, row, col))
9517 }
9518
9519 fn is_ast_volatile_with_provider(&self, ast: &ASTNode) -> bool {
9521 use formualizer_parse::parser::ASTNodeType;
9522 match &ast.node_type {
9523 ASTNodeType::Function { name, args, .. } => {
9524 if let Some(func) = self
9525 .get_function("", name)
9526 .or_else(|| crate::function_registry::get("", name))
9527 && func.caps().contains(crate::function::FnCaps::VOLATILE)
9528 {
9529 return true;
9530 }
9531 args.iter()
9532 .any(|arg| self.is_ast_volatile_with_provider(arg))
9533 }
9534 ASTNodeType::BinaryOp { left, right, .. } => {
9535 self.is_ast_volatile_with_provider(left)
9536 || self.is_ast_volatile_with_provider(right)
9537 }
9538 ASTNodeType::UnaryOp { expr, .. } => self.is_ast_volatile_with_provider(expr),
9539 ASTNodeType::Array(rows) => rows.iter().any(|row| {
9540 row.iter()
9541 .any(|cell| self.is_ast_volatile_with_provider(cell))
9542 }),
9543 _ => false,
9544 }
9545 }
9546
9547 fn find_dirty_precedents(&self, target_vertices: &[VertexId]) -> Vec<VertexId> {
9549 let mut to_evaluate = FxHashSet::default();
9550 let mut visited = FxHashSet::default();
9551 let mut stack = Vec::new();
9552
9553 for &target in target_vertices {
9555 stack.push(target);
9556 }
9557
9558 while let Some(vertex_id) = stack.pop() {
9559 if !visited.insert(vertex_id) {
9560 continue; }
9562
9563 if self.graph.vertex_exists(vertex_id) {
9564 let kind = self.graph.get_vertex_kind(vertex_id);
9566 let needs_eval = match kind {
9567 super::vertex::VertexKind::FormulaScalar
9568 | super::vertex::VertexKind::FormulaArray => {
9569 self.graph.is_dirty(vertex_id) || self.graph.is_volatile(vertex_id)
9570 }
9571 _ => false, };
9573
9574 if needs_eval {
9575 to_evaluate.insert(vertex_id);
9576 }
9577
9578 if let Some(dependencies) = self.graph.dependencies_slice(vertex_id) {
9580 for &dep_id in dependencies {
9581 if !visited.contains(&dep_id) {
9582 stack.push(dep_id);
9583 }
9584 }
9585 } else {
9586 let dependencies = self.graph.get_dependencies(vertex_id);
9587 for dep_id in dependencies {
9588 if !visited.contains(&dep_id) {
9589 stack.push(dep_id);
9590 }
9591 }
9592 }
9593 }
9594 }
9595
9596 let mut result: Vec<VertexId> = to_evaluate.into_iter().collect();
9597 result.sort_unstable();
9598 result
9599 }
9600
9601 fn evaluate_layer_sequential(
9603 &mut self,
9604 layer: &super::scheduler::Layer,
9605 ) -> Result<usize, ExcelError> {
9606 self.evaluate_layer_sequential_effects(layer)
9607 }
9608
9609 fn update_vertex_value_with_delta(
9610 &mut self,
9611 vertex_id: VertexId,
9612 new_value: LiteralValue,
9613 delta: &mut DeltaCollector,
9614 ) {
9615 if delta.mode != DeltaMode::Off
9616 && let Some(cell) = self.graph.get_cell_ref_for_vertex(vertex_id)
9617 {
9618 let sheet_name = self.graph.sheet_name(cell.sheet_id);
9619 let old = self
9620 .read_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
9621 .unwrap_or(LiteralValue::Empty);
9622 if old != new_value {
9623 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
9624 }
9625 }
9626 self.graph.update_vertex_value(vertex_id, new_value.clone());
9627 self.mirror_vertex_value_to_overlay(vertex_id, &new_value);
9628 }
9629
9630 fn evaluate_layer_sequential_with_delta(
9631 &mut self,
9632 layer: &super::scheduler::Layer,
9633 delta: &mut DeltaCollector,
9634 ) -> Result<usize, ExcelError> {
9635 self.evaluate_layer_sequential_with_delta_effects(layer, delta)
9636 }
9637
9638 fn evaluate_layer_sequential_cancellable(
9640 &mut self,
9641 layer: &super::scheduler::Layer,
9642 cancel_flag: &AtomicBool,
9643 ) -> Result<usize, ExcelError> {
9644 self.evaluate_layer_sequential_cancellable_effects(layer, cancel_flag)
9645 }
9646
9647 fn evaluate_layer_sequential_cancellable_demand_driven(
9649 &mut self,
9650 layer: &super::scheduler::Layer,
9651 cancel_flag: &AtomicBool,
9652 ) -> Result<usize, ExcelError> {
9653 self.evaluate_layer_sequential_cancellable_demand_driven_effects(layer, cancel_flag)
9654 }
9655
9656 fn evaluate_layer_parallel(
9658 &mut self,
9659 layer: &super::scheduler::Layer,
9660 ) -> Result<usize, ExcelError> {
9661 self.evaluate_layer_parallel_effects(layer)
9662 }
9663
9664 fn evaluate_layer_parallel_with_delta(
9665 &mut self,
9666 layer: &super::scheduler::Layer,
9667 delta: &mut DeltaCollector,
9668 ) -> Result<usize, ExcelError> {
9669 self.evaluate_layer_parallel_with_delta_effects(layer, delta)
9670 }
9671
9672 fn evaluate_layer_parallel_cancellable(
9674 &mut self,
9675 layer: &super::scheduler::Layer,
9676 cancel_flag: &AtomicBool,
9677 ) -> Result<usize, ExcelError> {
9678 self.evaluate_layer_parallel_cancellable_effects(layer, cancel_flag)
9679 }
9680
9681 fn apply_parallel_vertex_result(
9686 &mut self,
9687 vertex_id: VertexId,
9688 result: LiteralValue,
9689 mut delta: Option<&mut DeltaCollector>,
9690 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
9691 ) -> Result<(), ExcelError> {
9692 if let Some(cell) = self.graph.get_cell_ref(vertex_id)
9695 && let Some(owner) = self.graph.spill_registry_anchor_for_cell(cell)
9696 && owner != vertex_id
9697 {
9698 return Ok(());
9699 }
9700
9701 let kind = self.graph.get_vertex_kind(vertex_id);
9702
9703 let is_formula = matches!(kind, VertexKind::FormulaScalar | VertexKind::FormulaArray);
9705 if is_formula {
9706 match result {
9707 LiteralValue::Array(rows) => {
9708 self.apply_array_result_from_parallel(
9709 vertex_id,
9710 rows,
9711 delta.as_deref_mut(),
9712 overwritable_formulas,
9713 )?;
9714 }
9715 other => {
9716 self.apply_non_array_result_from_parallel(
9717 vertex_id,
9718 other,
9719 delta.as_deref_mut(),
9720 );
9721 }
9722 }
9723 return Ok(());
9724 }
9725
9726 if let Some(d) = delta {
9728 self.update_vertex_value_with_delta(vertex_id, result, d);
9729 } else {
9730 self.graph.update_vertex_value(vertex_id, result.clone());
9731 self.mirror_vertex_value_to_overlay(vertex_id, &result);
9732 }
9733 Ok(())
9734 }
9735
9736 fn apply_non_array_result_from_parallel(
9737 &mut self,
9738 vertex_id: VertexId,
9739 value: LiteralValue,
9740 delta: Option<&mut DeltaCollector>,
9741 ) {
9742 let spill_cells = self
9745 .graph
9746 .spill_cells_for_anchor(vertex_id)
9747 .map(|cells| cells.to_vec())
9748 .unwrap_or_default();
9749
9750 if let Some(d) = delta
9751 && d.mode != DeltaMode::Off
9752 && let Some(anchor) = self.graph.get_cell_ref_for_vertex(vertex_id)
9753 {
9754 if spill_cells.is_empty() {
9755 let old = self
9756 .read_cell_value(
9757 self.graph.sheet_name(anchor.sheet_id),
9758 anchor.coord.row() + 1,
9759 anchor.coord.col() + 1,
9760 )
9761 .unwrap_or(LiteralValue::Empty);
9762 if old != value {
9763 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
9764 }
9765 } else {
9766 for cell in spill_cells.iter() {
9767 let sheet_name = self.graph.sheet_name(cell.sheet_id);
9768 let old = self
9769 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
9770 .unwrap_or(LiteralValue::Empty);
9771 let new = if cell.sheet_id == anchor.sheet_id
9772 && cell.coord.row() == anchor.coord.row()
9773 && cell.coord.col() == anchor.coord.col()
9774 {
9775 value.clone()
9776 } else {
9777 LiteralValue::Empty
9778 };
9779 Self::record_cell_if_changed(d, cell, &old, &new);
9780 }
9781 }
9782 }
9783
9784 self.graph.clear_spill_region(vertex_id);
9785 if let Some(scope) = Self::formula_plane_region_from_cells(&spill_cells) {
9786 self.record_formula_plane_structural_change(scope);
9787 }
9788
9789 if self.config.arrow_storage_enabled
9790 && self.config.delta_overlay_enabled
9791 && self.config.write_formula_overlay_enabled
9792 {
9793 let empty = LiteralValue::Empty;
9794 for cell in spill_cells.iter() {
9795 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
9796 self.mirror_value_to_computed_overlay(
9797 &sheet_name,
9798 cell.coord.row() + 1,
9799 cell.coord.col() + 1,
9800 &empty,
9801 );
9802 }
9803 }
9804
9805 self.graph.update_vertex_value(vertex_id, value.clone());
9806 self.mirror_vertex_value_to_overlay(vertex_id, &value);
9807 }
9808
9809 fn apply_array_result_from_parallel(
9810 &mut self,
9811 vertex_id: VertexId,
9812 rows: Vec<Vec<LiteralValue>>,
9813 mut delta: Option<&mut DeltaCollector>,
9814 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
9815 ) -> Result<(), ExcelError> {
9816 self.graph
9818 .set_kind(vertex_id, crate::engine::vertex::VertexKind::FormulaArray);
9819
9820 let anchor = self
9821 .graph
9822 .get_cell_ref(vertex_id)
9823 .expect("cell ref for vertex");
9824 let sheet_id = anchor.sheet_id;
9825 let h = rows.len() as u32;
9826 let w = rows.first().map(|r| r.len()).unwrap_or(0) as u32;
9827
9828 let spill_cells = (h as u64).saturating_mul(w as u64);
9830 if spill_cells > self.config.spill.max_spill_cells as u64 {
9831 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
9832 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
9833 .with_message("SpillTooLarge")
9834 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
9835 expected_rows: h,
9836 expected_cols: w,
9837 });
9838 let spill_val = LiteralValue::Error(spill_err.clone());
9839 if let Some(d) = delta.as_deref_mut()
9840 && d.mode != DeltaMode::Off
9841 {
9842 let old = self
9843 .read_cell_value(
9844 self.graph.sheet_name(anchor.sheet_id),
9845 anchor.coord.row() + 1,
9846 anchor.coord.col() + 1,
9847 )
9848 .unwrap_or(LiteralValue::Empty);
9849 if old != spill_val {
9850 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
9851 }
9852 }
9853 self.graph.update_vertex_value(vertex_id, spill_val.clone());
9854 self.mirror_vertex_value_to_overlay(vertex_id, &spill_val);
9855 return Ok(());
9856 }
9857
9858 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);
9862 let end_col = anchor.coord.col().saturating_add(w).saturating_sub(1);
9863 if end_row > PACKED_MAX_ROW || end_col > PACKED_MAX_COL {
9864 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
9865 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
9866 .with_message("Spill exceeds sheet bounds")
9867 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
9868 expected_rows: h,
9869 expected_cols: w,
9870 });
9871 let spill_val = LiteralValue::Error(spill_err.clone());
9872 if let Some(d) = delta.as_deref_mut()
9873 && d.mode != DeltaMode::Off
9874 {
9875 let old = self
9876 .read_cell_value(
9877 self.graph.sheet_name(anchor.sheet_id),
9878 anchor.coord.row() + 1,
9879 anchor.coord.col() + 1,
9880 )
9881 .unwrap_or(LiteralValue::Empty);
9882 if old != spill_val {
9883 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
9884 }
9885 }
9886 self.graph.update_vertex_value(vertex_id, spill_val.clone());
9887 self.mirror_vertex_value_to_overlay(vertex_id, &spill_val);
9888 return Ok(());
9889 }
9890
9891 let mut targets = Vec::new();
9892 for r in 0..h {
9893 for c in 0..w {
9894 targets.push(self.graph.make_cell_ref_internal(
9895 sheet_id,
9896 anchor.coord.row() + r,
9897 anchor.coord.col() + c,
9898 ));
9899 }
9900 }
9901
9902 match self.spill_mgr.reserve(
9903 vertex_id,
9904 anchor,
9905 SpillShape { rows: h, cols: w },
9906 SpillMeta {
9907 epoch: self.recalc_epoch,
9908 config: self.config.spill,
9909 },
9910 ) {
9911 Ok(()) => {
9912 if let Err(e) = self.commit_spill_and_mirror(
9913 vertex_id,
9914 &targets,
9915 rows.clone(),
9916 delta.as_deref_mut(),
9917 overwritable_formulas,
9918 ) {
9919 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
9920 let err_val = LiteralValue::Error(e.clone());
9921 if let Some(d) = delta.as_deref_mut()
9922 && d.mode != DeltaMode::Off
9923 {
9924 let old = self
9925 .read_cell_value(
9926 self.graph.sheet_name(anchor.sheet_id),
9927 anchor.coord.row() + 1,
9928 anchor.coord.col() + 1,
9929 )
9930 .unwrap_or(LiteralValue::Empty);
9931 if old != err_val {
9932 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
9933 }
9934 }
9935 self.graph.update_vertex_value(vertex_id, err_val.clone());
9936 self.mirror_vertex_value_to_overlay(vertex_id, &err_val);
9937 return Ok(());
9938 }
9939
9940 let top_left = rows
9942 .first()
9943 .and_then(|r| r.first())
9944 .cloned()
9945 .unwrap_or(LiteralValue::Empty);
9946 self.graph.update_vertex_value(vertex_id, top_left.clone());
9947 self.mirror_vertex_value_to_overlay(vertex_id, &top_left);
9948 Ok(())
9949 }
9950 Err(e) => {
9951 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
9952 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
9953 .with_message(e.message.unwrap_or_else(|| "Spill blocked".to_string()))
9954 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
9955 expected_rows: h,
9956 expected_cols: w,
9957 });
9958 let spill_val = LiteralValue::Error(spill_err.clone());
9959 if let Some(d) = delta
9960 && d.mode != DeltaMode::Off
9961 {
9962 let old = self
9963 .read_cell_value(
9964 self.graph.sheet_name(anchor.sheet_id),
9965 anchor.coord.row() + 1,
9966 anchor.coord.col() + 1,
9967 )
9968 .unwrap_or(LiteralValue::Empty);
9969 if old != spill_val {
9970 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
9971 }
9972 }
9973 self.graph.update_vertex_value(vertex_id, spill_val.clone());
9974 self.mirror_vertex_value_to_overlay(vertex_id, &spill_val);
9975 Ok(())
9976 }
9977 }
9978 }
9979
9980 fn evaluate_vertex_immutable(&self, vertex_id: VertexId) -> Result<LiteralValue, ExcelError> {
9982 if !self.graph.vertex_exists(vertex_id) {
9984 return Err(ExcelError::new(formualizer_common::ExcelErrorKind::Ref)
9985 .with_message(format!("Vertex not found: {vertex_id:?}")));
9986 }
9987
9988 let kind = self.graph.get_vertex_kind(vertex_id);
9990 let sheet_id = self.graph.get_vertex_sheet_id(vertex_id);
9991
9992 let ast_id = match kind {
9993 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
9994 if let Some(ast_id) = self.graph.get_formula_id(vertex_id) {
9995 ast_id
9996 } else {
9997 return Ok(LiteralValue::Number(0.0));
9998 }
9999 }
10000 VertexKind::Empty | VertexKind::Cell => {
10001 if let Some(cell_ref) = self.graph.get_cell_ref(vertex_id) {
10002 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
10003 let row = cell_ref.coord.row() + 1;
10004 let col = cell_ref.coord.col() + 1;
10005 if let Some(v) = self.read_cell_value(sheet_name, row, col) {
10006 return Ok(v);
10007 }
10008 }
10009 return Ok(LiteralValue::Number(0.0));
10010 }
10011 VertexKind::NamedScalar => {
10012 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
10013 ExcelError::new(ExcelErrorKind::Name)
10014 .with_message("Named range metadata missing".to_string())
10015 })?;
10016
10017 return match &named_range.definition {
10018 NamedDefinition::Cell(cell_ref) => {
10019 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
10020 Ok(self
10021 .get_cell_value(
10022 sheet_name,
10023 cell_ref.coord.row() + 1,
10024 cell_ref.coord.col() + 1,
10025 )
10026 .unwrap_or(LiteralValue::Empty))
10027 }
10028 NamedDefinition::Literal(v) => Ok(v.clone()),
10029 NamedDefinition::Formula { ast, .. } => {
10030 let context_sheet = match named_range.scope {
10031 NameScope::Sheet(id) => id,
10032 NameScope::Workbook => sheet_id,
10033 };
10034 let sheet_name = self.graph.sheet_name(context_sheet);
10035 let cell_ref = self
10036 .graph
10037 .get_cell_ref(vertex_id)
10038 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
10039 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
10040 interpreter.evaluate_ast(ast).map(|cv| cv.into_literal())
10041 }
10042 NamedDefinition::Range(_) => Err(ExcelError::new(ExcelErrorKind::Value)
10043 .with_message("Range-valued name evaluated as scalar".to_string())),
10044 };
10045 }
10046 VertexKind::NamedArray => {
10047 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
10048 ExcelError::new(ExcelErrorKind::Name)
10049 .with_message("Named range metadata missing".to_string())
10050 })?;
10051
10052 return match &named_range.definition {
10053 NamedDefinition::Range(range_ref) => {
10054 if range_ref.start.sheet_id != range_ref.end.sheet_id {
10055 return Err(ExcelError::new(ExcelErrorKind::Ref)
10056 .with_message("Named range cannot span sheets".to_string()));
10057 }
10058 let sheet_name = self.graph.sheet_name(range_ref.start.sheet_id);
10059 let sr0 = range_ref.start.coord.row();
10060 let sc0 = range_ref.start.coord.col();
10061 let er0 = range_ref.end.coord.row();
10062 let ec0 = range_ref.end.coord.col();
10063 if sr0 > er0 || sc0 > ec0 {
10064 return Err(ExcelError::new(ExcelErrorKind::Ref)
10065 .with_message("Invalid named range bounds".to_string()));
10066 }
10067
10068 let h = (er0 - sr0 + 1) as usize;
10069 let w = (ec0 - sc0 + 1) as usize;
10070 let cell_count = (h as u64).saturating_mul(w as u64);
10071 if cell_count > self.config.spill.max_spill_cells as u64 {
10072 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
10073 "Named range too large to materialize as an array".to_string(),
10074 ));
10075 }
10076
10077 let mut rows = Vec::with_capacity(h);
10078 for r0 in sr0..=er0 {
10079 let mut row = Vec::with_capacity(w);
10080 for c0 in sc0..=ec0 {
10081 let v = self
10082 .get_cell_value(sheet_name, r0 + 1, c0 + 1)
10083 .unwrap_or(LiteralValue::Empty);
10084 row.push(v);
10085 }
10086 rows.push(row);
10087 }
10088 Ok(LiteralValue::Array(rows))
10089 }
10090 NamedDefinition::Cell(cell_ref) => {
10091 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
10092 let row = cell_ref.coord.row() + 1;
10093 let col = cell_ref.coord.col() + 1;
10094 let v = self
10095 .get_cell_value(sheet_name, row, col)
10096 .unwrap_or(LiteralValue::Empty);
10097 Ok(LiteralValue::Array(vec![vec![v]]))
10098 }
10099 NamedDefinition::Literal(v) => Ok(LiteralValue::Array(vec![vec![v.clone()]])),
10100 NamedDefinition::Formula { ast, .. } => {
10101 let context_sheet = match named_range.scope {
10102 NameScope::Sheet(id) => id,
10103 NameScope::Workbook => sheet_id,
10104 };
10105 let sheet_name = self.graph.sheet_name(context_sheet);
10106 let cell_ref = self
10107 .graph
10108 .get_cell_ref(vertex_id)
10109 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
10110 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
10111 match interpreter.evaluate_ast(ast) {
10112 Ok(cv) => {
10113 let v = cv.into_literal();
10114 match v {
10115 LiteralValue::Array(_) => Ok(v),
10116 other => Ok(LiteralValue::Array(vec![vec![other]])),
10117 }
10118 }
10119 Err(err) => Ok(LiteralValue::Error(err)),
10120 }
10121 }
10122 };
10123 }
10124 VertexKind::InfiniteRange
10125 | VertexKind::Range
10126 | VertexKind::External
10127 | VertexKind::Table => {
10128 return Ok(LiteralValue::Number(0.0));
10130 }
10131 };
10132
10133 let sheet_name = self.graph.sheet_name(sheet_id);
10135 let cell_ref = self
10136 .graph
10137 .get_cell_ref(vertex_id)
10138 .expect("cell ref for vertex");
10139 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
10140
10141 interpreter
10142 .evaluate_arena_ast(ast_id, self.graph.data_store(), self.graph.sheet_reg())
10143 .map(|cv| cv.into_literal())
10144 }
10145
10146 pub fn thread_pool(&self) -> Option<&Arc<rayon::ThreadPool>> {
10148 self.thread_pool.as_ref()
10149 }
10150}
10151
10152#[derive(Default)]
10153struct RowBoundsCache {
10154 snapshot: u64,
10155 map: rustc_hash::FxHashMap<(u32, usize), (Option<u32>, Option<u32>)>,
10157}
10158
10159impl RowBoundsCache {
10160 fn new(snapshot: u64) -> Self {
10161 Self {
10162 snapshot,
10163 map: Default::default(),
10164 }
10165 }
10166 fn get_row_bounds(
10167 &self,
10168 sheet_id: SheetId,
10169 col_idx: usize,
10170 snapshot: u64,
10171 ) -> Option<(Option<u32>, Option<u32>)> {
10172 if self.snapshot != snapshot {
10173 return None;
10174 }
10175 self.map.get(&(sheet_id as u32, col_idx)).copied()
10176 }
10177 fn put_row_bounds(
10178 &mut self,
10179 sheet_id: SheetId,
10180 col_idx: usize,
10181 snapshot: u64,
10182 bounds: (Option<u32>, Option<u32>),
10183 ) {
10184 if self.snapshot != snapshot {
10185 self.snapshot = snapshot;
10186 self.map.clear();
10187 }
10188 self.map.insert((sheet_id as u32, col_idx), bounds);
10189 }
10190}
10191
10192struct UsedAxisBoundsCache {
10193 snapshot: u64,
10194 row_bounds_by_col_span: rustc_hash::FxHashMap<(SheetId, u32, u32), Option<(u32, u32)>>,
10195 col_bounds_by_row_span: rustc_hash::FxHashMap<(SheetId, u32, u32), Option<(u32, u32)>>,
10196 #[cfg(test)]
10197 row_hits: std::sync::atomic::AtomicUsize,
10198 #[cfg(test)]
10199 row_misses: std::sync::atomic::AtomicUsize,
10200 #[cfg(test)]
10201 col_hits: std::sync::atomic::AtomicUsize,
10202 #[cfg(test)]
10203 col_misses: std::sync::atomic::AtomicUsize,
10204}
10205
10206impl UsedAxisBoundsCache {
10207 fn new(snapshot: u64) -> Self {
10208 Self {
10209 snapshot,
10210 row_bounds_by_col_span: Default::default(),
10211 col_bounds_by_row_span: Default::default(),
10212 #[cfg(test)]
10213 row_hits: std::sync::atomic::AtomicUsize::new(0),
10214 #[cfg(test)]
10215 row_misses: std::sync::atomic::AtomicUsize::new(0),
10216 #[cfg(test)]
10217 col_hits: std::sync::atomic::AtomicUsize::new(0),
10218 #[cfg(test)]
10219 col_misses: std::sync::atomic::AtomicUsize::new(0),
10220 }
10221 }
10222
10223 fn reset_for_snapshot(&mut self, snapshot: u64) {
10224 if self.snapshot != snapshot {
10225 self.snapshot = snapshot;
10226 self.row_bounds_by_col_span.clear();
10227 self.col_bounds_by_row_span.clear();
10228 }
10229 }
10230
10231 fn get_row_bounds(
10232 &self,
10233 sheet_id: SheetId,
10234 start_col: u32,
10235 end_col: u32,
10236 snapshot: u64,
10237 ) -> Option<Option<(u32, u32)>> {
10238 if self.snapshot != snapshot {
10239 return None;
10240 }
10241 let cached = self
10242 .row_bounds_by_col_span
10243 .get(&(sheet_id, start_col, end_col))
10244 .copied();
10245 #[cfg(test)]
10246 if cached.is_some() {
10247 self.row_hits.fetch_add(1, Ordering::Relaxed);
10248 }
10249 cached
10250 }
10251
10252 fn put_row_bounds(
10253 &mut self,
10254 sheet_id: SheetId,
10255 start_col: u32,
10256 end_col: u32,
10257 snapshot: u64,
10258 bounds: Option<(u32, u32)>,
10259 ) {
10260 self.reset_for_snapshot(snapshot);
10261 self.row_bounds_by_col_span
10262 .insert((sheet_id, start_col, end_col), bounds);
10263 #[cfg(test)]
10264 self.row_misses.fetch_add(1, Ordering::Relaxed);
10265 }
10266
10267 fn get_col_bounds(
10268 &self,
10269 sheet_id: SheetId,
10270 start_row: u32,
10271 end_row: u32,
10272 snapshot: u64,
10273 ) -> Option<Option<(u32, u32)>> {
10274 if self.snapshot != snapshot {
10275 return None;
10276 }
10277 let cached = self
10278 .col_bounds_by_row_span
10279 .get(&(sheet_id, start_row, end_row))
10280 .copied();
10281 #[cfg(test)]
10282 if cached.is_some() {
10283 self.col_hits.fetch_add(1, Ordering::Relaxed);
10284 }
10285 cached
10286 }
10287
10288 fn put_col_bounds(
10289 &mut self,
10290 sheet_id: SheetId,
10291 start_row: u32,
10292 end_row: u32,
10293 snapshot: u64,
10294 bounds: Option<(u32, u32)>,
10295 ) {
10296 self.reset_for_snapshot(snapshot);
10297 self.col_bounds_by_row_span
10298 .insert((sheet_id, start_row, end_row), bounds);
10299 #[cfg(test)]
10300 self.col_misses.fetch_add(1, Ordering::Relaxed);
10301 }
10302}
10303
10304#[derive(Default)]
10306pub struct ShimSpillManager {
10307 region_locks: RegionLockManager,
10308 pub(crate) active_locks: rustc_hash::FxHashMap<VertexId, u64>,
10309}
10310
10311impl ShimSpillManager {
10312 pub(crate) fn reserve(
10313 &mut self,
10314 owner: VertexId,
10315 anchor_cell: CellRef,
10316 shape: SpillShape,
10317 _meta: SpillMeta,
10318 ) -> Result<(), ExcelError> {
10319 let region = crate::engine::spill::Region {
10321 sheet_id: anchor_cell.sheet_id as u32,
10322 row_start: anchor_cell.coord.row(),
10323 row_end: anchor_cell
10324 .coord
10325 .row()
10326 .saturating_add(shape.rows)
10327 .saturating_sub(1),
10328 col_start: anchor_cell.coord.col(),
10329 col_end: anchor_cell
10330 .coord
10331 .col()
10332 .saturating_add(shape.cols)
10333 .saturating_sub(1),
10334 };
10335 match self.region_locks.reserve(region, owner) {
10336 Ok(id) => {
10337 if id != 0 {
10338 self.active_locks.insert(owner, id);
10339 }
10340 Ok(())
10341 }
10342 Err(e) => Err(e),
10343 }
10344 }
10345
10346 pub(crate) fn commit_array_with_value_probe<F>(
10347 &mut self,
10348 graph: &mut DependencyGraph,
10349 anchor_vertex: VertexId,
10350 targets: &[CellRef],
10351 rows: Vec<Vec<LiteralValue>>,
10352 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
10353 mut value_probe: F,
10354 ) -> Result<(), ExcelError>
10355 where
10356 F: FnMut(&DependencyGraph, &CellRef) -> Option<LiteralValue>,
10357 {
10358 use formualizer_common::{ExcelErrorExtra, ExcelErrorKind};
10359
10360 let plan_res = graph.plan_spill_region_allowing_formula_overwrite(
10364 anchor_vertex,
10365 targets,
10366 overwritable_formulas,
10367 );
10368 if let Err(e) = plan_res {
10369 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
10370 self.region_locks.release(id);
10371 }
10372 return Err(e);
10373 }
10374
10375 if !graph.value_cache_enabled() {
10376 let (expected_rows, expected_cols) = if targets.is_empty() {
10378 (0u32, 0u32)
10379 } else {
10380 let mut min_r = u32::MAX;
10381 let mut max_r = 0u32;
10382 let mut min_c = u32::MAX;
10383 let mut max_c = 0u32;
10384 for cell in targets {
10385 let r = cell.coord.row();
10386 let c = cell.coord.col();
10387 min_r = min_r.min(r);
10388 max_r = max_r.max(r);
10389 min_c = min_c.min(c);
10390 max_c = max_c.max(c);
10391 }
10392 (
10393 max_r.saturating_sub(min_r).saturating_add(1),
10394 max_c.saturating_sub(min_c).saturating_add(1),
10395 )
10396 };
10397
10398 let anchor_cell = graph
10399 .get_cell_ref(anchor_vertex)
10400 .expect("anchor cell ref for spill commit");
10401
10402 for cell in targets {
10403 if *cell == anchor_cell {
10405 continue;
10406 }
10407 if graph.spill_registry_anchor_for_cell(*cell).is_some() {
10409 continue;
10410 }
10411 if let Some(&vid) = graph.get_vertex_id_for_address(cell)
10413 && vid != anchor_vertex
10414 {
10415 match graph.get_vertex_kind(vid) {
10416 crate::engine::vertex::VertexKind::FormulaScalar
10417 | crate::engine::vertex::VertexKind::FormulaArray => {
10418 continue;
10420 }
10421 _ => {}
10422 }
10423 }
10424
10425 if let Some(v) = value_probe(graph, cell)
10426 && !matches!(v, LiteralValue::Empty)
10427 {
10428 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
10429 self.region_locks.release(id);
10430 }
10431 return Err(ExcelError::new(ExcelErrorKind::Spill)
10432 .with_message("BlockedByValue")
10433 .with_extra(ExcelErrorExtra::Spill {
10434 expected_rows,
10435 expected_cols,
10436 }));
10437 }
10438 }
10439 }
10440
10441 let commit_res = graph.commit_spill_region_atomic_with_fault(
10442 anchor_vertex,
10443 targets.to_vec(),
10444 rows,
10445 None,
10446 );
10447 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
10448 self.region_locks.release(id);
10449 }
10450 commit_res.map(|_| ())
10451 }
10452
10453 pub(crate) fn commit_array_with_overlay<R: EvaluationContext>(
10455 &mut self,
10456 engine: &mut Engine<R>,
10457 anchor_vertex: VertexId,
10458 targets: &[CellRef],
10459 rows: Vec<Vec<LiteralValue>>,
10460 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
10461 ) -> Result<(), ExcelError> {
10462 let plan_res = engine.graph.plan_spill_region_allowing_formula_overwrite(
10464 anchor_vertex,
10465 targets,
10466 overwritable_formulas,
10467 );
10468 if let Err(e) = plan_res {
10469 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
10470 self.region_locks.release(id);
10471 }
10472 return Err(e);
10473 }
10474
10475 let commit_res = engine.graph.commit_spill_region_atomic_with_fault(
10476 anchor_vertex,
10477 targets.to_vec(),
10478 rows.clone(),
10479 None,
10480 );
10481 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
10482 self.region_locks.release(id);
10483 }
10484 commit_res.map(|_| ())?;
10485
10486 if engine.config.arrow_storage_enabled
10488 && engine.config.delta_overlay_enabled
10489 && engine.config.write_formula_overlay_enabled
10490 {
10491 for (idx, cell) in targets.iter().enumerate() {
10493 let (r_off, c_off) = {
10494 if rows.is_empty() || rows[0].is_empty() {
10495 (0usize, 0usize)
10496 } else {
10497 let width = rows[0].len();
10498 (idx / width, idx % width)
10499 }
10500 };
10501 let v = rows
10502 .get(r_off)
10503 .and_then(|r| r.get(c_off))
10504 .cloned()
10505 .unwrap_or(LiteralValue::Empty);
10506 let sheet_name = engine.graph.sheet_name(cell.sheet_id).to_string();
10507 engine.mirror_value_to_computed_overlay(
10508 &sheet_name,
10509 cell.coord.row() + 1,
10510 cell.coord.col() + 1,
10511 &v,
10512 );
10513 }
10514 }
10515 Ok(())
10516 }
10517}
10518
10519impl<R> Engine<R>
10520where
10521 R: EvaluationContext,
10522{
10523 fn resolve_shared_ref(
10524 &self,
10525 reference: &ReferenceType,
10526 current_sheet: &str,
10527 ) -> Result<formualizer_common::SheetRef<'static>, ExcelError> {
10528 use formualizer_common::{
10529 SheetCellRef as SharedCellRef, SheetLocator, SheetRangeRef as SharedRangeRef,
10530 SheetRef as SharedRef,
10531 };
10532
10533 let sr = match reference {
10535 ReferenceType::Cell {
10536 sheet,
10537 row,
10538 col,
10539 row_abs,
10540 col_abs,
10541 } => {
10542 let row0 = row
10543 .checked_sub(1)
10544 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
10545 let col0 = col
10546 .checked_sub(1)
10547 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
10548 let sheet_loc = match sheet.as_deref() {
10549 Some(name) => SheetLocator::from_name(name),
10550 None => SheetLocator::Current,
10551 };
10552 let coord = formualizer_common::RelativeCoord::new(row0, col0, *row_abs, *col_abs);
10553 SharedRef::Cell(SharedCellRef::new(sheet_loc, coord))
10554 }
10555 ReferenceType::Range {
10556 sheet,
10557 start_row,
10558 start_col,
10559 end_row,
10560 end_col,
10561 start_row_abs,
10562 start_col_abs,
10563 end_row_abs,
10564 end_col_abs,
10565 } => {
10566 let sheet_loc = match sheet.as_deref() {
10567 Some(name) => SheetLocator::from_name(name),
10568 None => SheetLocator::Current,
10569 };
10570 let sr = start_row
10571 .map(|r| {
10572 r.checked_sub(1)
10573 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
10574 })
10575 .transpose()?;
10576 let sc = start_col
10577 .map(|c| {
10578 c.checked_sub(1)
10579 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
10580 })
10581 .transpose()?;
10582 let er = end_row
10583 .map(|r| {
10584 r.checked_sub(1)
10585 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
10586 })
10587 .transpose()?;
10588 let ec = end_col
10589 .map(|c| {
10590 c.checked_sub(1)
10591 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
10592 })
10593 .transpose()?;
10594 let range = SharedRangeRef::from_parts(
10595 sheet_loc,
10596 sr.map(|idx| formualizer_common::AxisBound::new(idx, *start_row_abs)),
10597 sc.map(|idx| formualizer_common::AxisBound::new(idx, *start_col_abs)),
10598 er.map(|idx| formualizer_common::AxisBound::new(idx, *end_row_abs)),
10599 ec.map(|idx| formualizer_common::AxisBound::new(idx, *end_col_abs)),
10600 )
10601 .map_err(|_| ExcelError::new(ExcelErrorKind::Ref))?;
10602 SharedRef::Range(range)
10603 }
10604 _ => return Err(ExcelError::new(ExcelErrorKind::Ref)),
10605 };
10606
10607 let current_id = self
10608 .graph
10609 .sheet_id(current_sheet)
10610 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
10611
10612 let resolve_loc = |loc: SheetLocator<'_>| -> Result<SheetLocator<'static>, ExcelError> {
10613 match loc {
10614 SheetLocator::Current => Ok(SheetLocator::Id(current_id)),
10615 SheetLocator::Id(id) => Ok(SheetLocator::Id(id)),
10616 SheetLocator::Name(name) => {
10617 let n = name.as_ref();
10618 self.graph
10619 .sheet_id(n)
10620 .map(SheetLocator::Id)
10621 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
10622 }
10623 }
10624 };
10625
10626 match sr {
10627 SharedRef::Cell(cell) => {
10628 let owned = cell.into_owned();
10629 let sheet = resolve_loc(owned.sheet)?;
10630 Ok(SharedRef::Cell(SharedCellRef::new(sheet, owned.coord)))
10631 }
10632 SharedRef::Range(range) => {
10633 let owned = range.into_owned();
10634 let sheet = resolve_loc(owned.sheet)?;
10635 Ok(SharedRef::Range(SharedRangeRef {
10636 sheet,
10637 start_row: owned.start_row,
10638 start_col: owned.start_col,
10639 end_row: owned.end_row,
10640 end_col: owned.end_col,
10641 }))
10642 }
10643 }
10644 }
10645}
10646
10647impl<R> crate::traits::ReferenceResolver for Engine<R>
10650where
10651 R: EvaluationContext,
10652{
10653 fn resolve_cell_reference(
10654 &self,
10655 sheet: Option<&str>,
10656 row: u32,
10657 col: u32,
10658 ) -> Result<LiteralValue, ExcelError> {
10659 let sheet_name = sheet.unwrap_or_else(|| self.default_sheet_name()); if let Some(v) = self.get_cell_value(sheet_name, row, col) {
10663 Ok(v)
10664 } else {
10665 Ok(LiteralValue::Number(0.0))
10667 }
10668 }
10669}
10670
10671impl<R> crate::traits::RangeResolver for Engine<R>
10672where
10673 R: EvaluationContext,
10674{
10675 fn resolve_range_reference(
10676 &self,
10677 sheet: Option<&str>,
10678 sr: Option<u32>,
10679 sc: Option<u32>,
10680 er: Option<u32>,
10681 ec: Option<u32>,
10682 ) -> Result<Box<dyn crate::traits::Range>, ExcelError> {
10683 self.resolver.resolve_range_reference(sheet, sr, sc, er, ec)
10686 }
10687}
10688
10689impl<R> crate::traits::NamedRangeResolver for Engine<R>
10690where
10691 R: EvaluationContext,
10692{
10693 fn resolve_named_range_reference(
10694 &self,
10695 name: &str,
10696 ) -> Result<Vec<Vec<LiteralValue>>, ExcelError> {
10697 self.resolver.resolve_named_range_reference(name)
10698 }
10699}
10700
10701impl<R> crate::traits::TableResolver for Engine<R>
10702where
10703 R: EvaluationContext,
10704{
10705 fn resolve_table_reference(
10706 &self,
10707 tref: &formualizer_parse::parser::TableReference,
10708 ) -> Result<Box<dyn crate::traits::Table>, ExcelError> {
10709 self.resolver.resolve_table_reference(tref)
10710 }
10711}
10712
10713impl<R> crate::traits::SourceResolver for Engine<R>
10714where
10715 R: EvaluationContext,
10716{
10717 fn source_scalar_version(&self, name: &str) -> Option<u64> {
10718 self.resolver.source_scalar_version(name)
10719 }
10720
10721 fn resolve_source_scalar(&self, name: &str) -> Result<LiteralValue, ExcelError> {
10722 self.resolver.resolve_source_scalar(name)
10723 }
10724
10725 fn source_table_version(&self, name: &str) -> Option<u64> {
10726 self.resolver.source_table_version(name)
10727 }
10728
10729 fn resolve_source_table(
10730 &self,
10731 name: &str,
10732 ) -> Result<Box<dyn crate::traits::Table>, ExcelError> {
10733 self.resolver.resolve_source_table(name)
10734 }
10735}
10736
10737impl<R> crate::traits::Resolver for Engine<R> where R: EvaluationContext {}
10739
10740impl<R> crate::traits::FunctionProvider for Engine<R>
10742where
10743 R: EvaluationContext,
10744{
10745 fn get_function(
10746 &self,
10747 prefix: &str,
10748 name: &str,
10749 ) -> Option<std::sync::Arc<dyn crate::function::Function>> {
10750 self.resolver.get_function(prefix, name)
10751 }
10752}
10753
10754impl<R> crate::traits::EvaluationContext for Engine<R>
10756where
10757 R: EvaluationContext,
10758{
10759 fn clock(&self) -> &dyn crate::timezone::ClockProvider {
10760 self.clock.as_ref()
10761 }
10762
10763 fn thread_pool(&self) -> Option<&Arc<rayon::ThreadPool>> {
10764 self.thread_pool.as_ref()
10765 }
10766
10767 fn cancellation_token(&self) -> Option<Arc<std::sync::atomic::AtomicBool>> {
10768 self.active_cancel_flag.clone()
10769 }
10770
10771 fn chunk_hint(&self) -> Option<usize> {
10772 let hint =
10774 (self.config.stripe_height as usize).saturating_mul(self.config.stripe_width as usize);
10775 Some(hint.clamp(1024, 1 << 20)) }
10777
10778 fn volatile_level(&self) -> crate::traits::VolatileLevel {
10779 self.config.volatile_level
10780 }
10781
10782 fn workbook_seed(&self) -> u64 {
10783 self.config.workbook_seed
10784 }
10785
10786 fn recalc_epoch(&self) -> u64 {
10787 self.recalc_epoch
10788 }
10789
10790 fn workbook_sheet_count(&self) -> Option<usize> {
10791 Some(self.graph.sheet_reg().active_len())
10792 }
10793
10794 fn sheet_index_by_name(&self, sheet: &str) -> Option<usize> {
10795 self.graph.sheet_reg().active_position(sheet)
10796 }
10797
10798 fn current_sheet_index(&self, current_sheet: &str) -> Option<usize> {
10799 self.sheet_index_by_name(current_sheet)
10800 }
10801
10802 fn inspect_reference(
10803 &self,
10804 reference: &ReferenceType,
10805 current_sheet: &str,
10806 ) -> Result<Option<ReferenceInfo>, ExcelError> {
10807 let sheet_info = |sheet_name: &str| -> Result<(SheetId, usize), ExcelError> {
10808 let sheet_id = self
10809 .graph
10810 .sheet_id(sheet_name)
10811 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
10812 let sheet_index = self
10813 .graph
10814 .sheet_reg()
10815 .active_position_by_id(sheet_id)
10816 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
10817 Ok((sheet_id, sheet_index))
10818 };
10819
10820 let cell_info =
10821 |sheet_name: &str, row: u32, col: u32| -> Result<ReferenceInfo, ExcelError> {
10822 let (sheet_id, sheet_index) = sheet_info(sheet_name)?;
10823 let row0 = row
10824 .checked_sub(1)
10825 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
10826 let col0 = col
10827 .checked_sub(1)
10828 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
10829 Ok(ReferenceInfo {
10830 first_sheet_index: Some(sheet_index),
10831 sheet_count: Some(1),
10832 first_cell: Some(CellRef::new(sheet_id, Coord::new(row0, col0, true, true))),
10833 })
10834 };
10835
10836 let range_info = |sheet_name: &str,
10837 start_row: Option<u32>,
10838 start_col: Option<u32>|
10839 -> Result<ReferenceInfo, ExcelError> {
10840 let (sheet_id, sheet_index) = sheet_info(sheet_name)?;
10841 let row = start_row.unwrap_or(1);
10842 let col = start_col.unwrap_or(1);
10843 let row0 = row
10844 .checked_sub(1)
10845 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
10846 let col0 = col
10847 .checked_sub(1)
10848 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
10849 Ok(ReferenceInfo {
10850 first_sheet_index: Some(sheet_index),
10851 sheet_count: Some(1),
10852 first_cell: Some(CellRef::new(sheet_id, Coord::new(row0, col0, true, true))),
10853 })
10854 };
10855
10856 let info = match reference {
10857 ReferenceType::Cell {
10858 sheet, row, col, ..
10859 } => {
10860 let sheet_name = sheet.as_deref().unwrap_or(current_sheet);
10861 cell_info(sheet_name, *row, *col)?
10862 }
10863 ReferenceType::Range {
10864 sheet,
10865 start_row,
10866 start_col,
10867 ..
10868 } => {
10869 let sheet_name = sheet.as_deref().unwrap_or(current_sheet);
10870 range_info(sheet_name, *start_row, *start_col)?
10871 }
10872 ReferenceType::Cell3D {
10873 sheet_first,
10874 sheet_last,
10875 row,
10876 col,
10877 ..
10878 } => {
10879 let first = cell_info(sheet_first, *row, *col)?;
10880 ReferenceInfo {
10881 first_sheet_index: first.first_sheet_index,
10882 sheet_count: self
10883 .graph
10884 .sheet_reg()
10885 .active_span_len(sheet_first, sheet_last),
10886 first_cell: first.first_cell,
10887 }
10888 }
10889 ReferenceType::Range3D {
10890 sheet_first,
10891 sheet_last,
10892 start_row,
10893 start_col,
10894 ..
10895 } => {
10896 let first = range_info(sheet_first, *start_row, *start_col)?;
10897 ReferenceInfo {
10898 first_sheet_index: first.first_sheet_index,
10899 sheet_count: self
10900 .graph
10901 .sheet_reg()
10902 .active_span_len(sheet_first, sheet_last),
10903 first_cell: first.first_cell,
10904 }
10905 }
10906 ReferenceType::NamedRange(name) => {
10907 let current_id = self
10908 .graph
10909 .sheet_id(current_sheet)
10910 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
10911 let named = self
10912 .graph
10913 .resolve_name_entry(name, current_id)
10914 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
10915 match &named.definition {
10916 NamedDefinition::Cell(cell) => ReferenceInfo {
10917 first_sheet_index: self
10918 .graph
10919 .sheet_reg()
10920 .active_position_by_id(cell.sheet_id),
10921 sheet_count: Some(1),
10922 first_cell: Some(*cell),
10923 },
10924 NamedDefinition::Range(range) => ReferenceInfo {
10925 first_sheet_index: self
10926 .graph
10927 .sheet_reg()
10928 .active_position_by_id(range.start.sheet_id),
10929 sheet_count: Some(1),
10930 first_cell: Some(range.start),
10931 },
10932 NamedDefinition::Literal(_) | NamedDefinition::Formula { .. } => {
10933 ReferenceInfo {
10934 first_sheet_index: None,
10935 sheet_count: None,
10936 first_cell: None,
10937 }
10938 }
10939 }
10940 }
10941 ReferenceType::Table(tref) => {
10942 let table = self
10943 .graph
10944 .resolve_table_entry(&tref.name)
10945 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
10946 ReferenceInfo {
10947 first_sheet_index: self
10948 .graph
10949 .sheet_reg()
10950 .active_position_by_id(table.range.start.sheet_id),
10951 sheet_count: Some(1),
10952 first_cell: Some(table.range.start),
10953 }
10954 }
10955 ReferenceType::External(_) => return Err(ExcelError::new(ExcelErrorKind::Ref)),
10956 };
10957
10958 Ok(Some(info))
10959 }
10960
10961 fn formula_text_at_cell(&self, cell: CellRef) -> Result<Option<String>, ExcelError> {
10962 let sheet_name = self.graph.sheet_name(cell.sheet_id);
10963 if sheet_name.is_empty() {
10964 return Err(ExcelError::new(ExcelErrorKind::Ref));
10965 }
10966 let row = cell.coord.row() + 1;
10967 let col = cell.coord.col() + 1;
10968
10969 if let Some(entries) = self.staged_formulas.get(sheet_name)
10970 && let Some((_, _, text)) = entries
10971 .iter()
10972 .rev()
10973 .find(|(r, c, _)| *r == row && *c == col)
10974 {
10975 return Ok(Some(if text.starts_with('=') {
10976 text.clone()
10977 } else {
10978 format!("={text}")
10979 }));
10980 }
10981
10982 let Some(vertex) = self.graph.get_vertex_for_cell(&cell) else {
10983 return Ok(None);
10984 };
10985 let Some(ast) = self.graph.get_formula(vertex) else {
10986 return Ok(None);
10987 };
10988 Ok(Some(formualizer_parse::pretty::canonical_formula(&ast)))
10989 }
10990
10991 fn used_rows_for_columns(
10992 &self,
10993 sheet: &str,
10994 start_col: u32,
10995 end_col: u32,
10996 ) -> Option<(u32, u32)> {
10997 let sheet_id = self.graph.sheet_id(sheet)?;
10999 let snap = self.data_snapshot_id();
11000 if let Some(cached) = self.used_axis_bounds_cache.read().ok().and_then(|guard| {
11001 guard
11002 .as_ref()
11003 .and_then(|cache| cache.get_row_bounds(sheet_id, start_col, end_col, snap))
11004 }) {
11005 return cached;
11006 }
11007
11008 let arrow_bounds = self
11009 .sheet_store()
11010 .sheet(sheet)
11011 .and_then(|_| self.arrow_used_row_bounds(sheet, start_col, end_col));
11012 let formula_bounds = self.formula_row_bounds_for_columns(sheet, start_col, end_col);
11013 let computed = if let Some(bounds) = Self::union_used_bounds(arrow_bounds, formula_bounds) {
11014 Some(bounds)
11015 } else {
11016 let sc0 = start_col.saturating_sub(1);
11017 let ec0 = end_col.saturating_sub(1);
11018 self.graph
11019 .used_row_bounds_for_columns(sheet_id, sc0, ec0)
11020 .map(|(a0, b0)| (a0 + 1, b0 + 1))
11021 };
11022
11023 if let Ok(mut guard) = self.used_axis_bounds_cache.write() {
11024 guard
11025 .get_or_insert_with(|| UsedAxisBoundsCache::new(snap))
11026 .put_row_bounds(sheet_id, start_col, end_col, snap, computed);
11027 }
11028
11029 computed
11030 }
11031
11032 fn used_cols_for_rows(&self, sheet: &str, start_row: u32, end_row: u32) -> Option<(u32, u32)> {
11033 let sheet_id = self.graph.sheet_id(sheet)?;
11035 let snap = self.data_snapshot_id();
11036 if let Some(cached) = self.used_axis_bounds_cache.read().ok().and_then(|guard| {
11037 guard
11038 .as_ref()
11039 .and_then(|cache| cache.get_col_bounds(sheet_id, start_row, end_row, snap))
11040 }) {
11041 return cached;
11042 }
11043
11044 let arrow_bounds = self
11045 .sheet_store()
11046 .sheet(sheet)
11047 .and_then(|_| self.arrow_used_col_bounds(sheet, start_row, end_row));
11048 let formula_bounds = self.formula_col_bounds_for_rows(sheet, start_row, end_row);
11049 let computed = if let Some(bounds) = Self::union_used_bounds(arrow_bounds, formula_bounds) {
11050 Some(bounds)
11051 } else {
11052 let sr0 = start_row.saturating_sub(1);
11053 let er0 = end_row.saturating_sub(1);
11054 self.graph
11055 .used_col_bounds_for_rows(sheet_id, sr0, er0)
11056 .map(|(a0, b0)| (a0 + 1, b0 + 1))
11057 };
11058
11059 if let Ok(mut guard) = self.used_axis_bounds_cache.write() {
11060 guard
11061 .get_or_insert_with(|| UsedAxisBoundsCache::new(snap))
11062 .put_col_bounds(sheet_id, start_row, end_row, snap, computed);
11063 }
11064
11065 computed
11066 }
11067
11068 fn sheet_bounds(&self, sheet: &str) -> Option<(u32, u32)> {
11069 let _ = self.graph.sheet_id(sheet)?;
11070 Some((1_048_576, 16_384)) }
11074
11075 fn data_snapshot_id(&self) -> u64 {
11076 self.snapshot_id.load(std::sync::atomic::Ordering::Relaxed)
11077 }
11078
11079 fn backend_caps(&self) -> crate::traits::BackendCaps {
11080 crate::traits::BackendCaps {
11081 streaming: true,
11082 used_region: true,
11083 write: false,
11084 tables: false,
11085 async_stream: false,
11086 }
11087 }
11088
11089 fn build_lookup_index(
11090 &self,
11091 view: &RangeView<'_>,
11092 axis: LookupAxis,
11093 ) -> Option<Arc<LookupIndex>> {
11094 self.build_lookup_index_impl(view, axis)
11095 }
11096
11097 fn date_system(&self) -> crate::engine::DateSystem {
11100 self.config.date_system
11101 }
11102 fn resolve_range_view<'c>(
11104 &'c self,
11105 reference: &ReferenceType,
11106 current_sheet: &str,
11107 ) -> Result<RangeView<'c>, ExcelError> {
11108 match reference {
11109 ReferenceType::External(ext) => {
11110 let name = ext.raw.as_str();
11111 match ext.kind {
11112 formualizer_parse::parser::ExternalRefKind::Cell { .. } => {
11113 let Some(source) = self.graph.resolve_source_scalar_entry(name) else {
11114 return Err(ExcelError::new(ExcelErrorKind::Name)
11115 .with_message(format!("Undefined name: {name}")));
11116 };
11117 let version = source
11118 .version
11119 .or_else(|| self.resolver.source_scalar_version(name));
11120 let v = self.resolve_source_scalar_cached(name, version)?;
11121 Ok(RangeView::from_owned_rows(
11122 vec![vec![v]],
11123 self.config.date_system,
11124 ))
11125 }
11126 formualizer_parse::parser::ExternalRefKind::Range { .. } => {
11127 let Some(source) = self.graph.resolve_source_table_entry(name) else {
11128 return Err(ExcelError::new(ExcelErrorKind::Name)
11129 .with_message(format!("Undefined table: {name}")));
11130 };
11131 let version = source
11132 .version
11133 .or_else(|| self.resolver.source_table_version(name));
11134 let table = self.resolve_source_table_cached(name, version)?;
11135 let spec = Some(formualizer_parse::parser::TableSpecifier::Data);
11136 self.source_table_to_range_view(table.as_ref(), &spec)
11137 }
11138 }
11139 }
11140 ReferenceType::Range { .. } => {
11141 let shared = self.resolve_shared_ref(reference, current_sheet)?;
11142 let formualizer_common::SheetRef::Range(range) = shared else {
11143 return Err(ExcelError::new(ExcelErrorKind::Ref));
11144 };
11145 let sheet_id = match range.sheet {
11146 formualizer_common::SheetLocator::Id(id) => id,
11147 _ => return Err(ExcelError::new(ExcelErrorKind::Ref)),
11148 };
11149 let sheet_name = self.graph.sheet_name(sheet_id);
11150
11151 let bounded_range = if range.start_row.is_some()
11152 && range.start_col.is_some()
11153 && range.end_row.is_some()
11154 && range.end_col.is_some()
11155 {
11156 Some(RangeRef::try_from_shared(range.as_ref())?)
11157 } else {
11158 None
11159 };
11160
11161 let mut sr = bounded_range
11162 .as_ref()
11163 .map(|r| r.start.coord.row() + 1)
11164 .or_else(|| range.start_row.map(|b| b.index + 1));
11165 let mut sc = bounded_range
11166 .as_ref()
11167 .map(|r| r.start.coord.col() + 1)
11168 .or_else(|| range.start_col.map(|b| b.index + 1));
11169 let mut er = bounded_range
11170 .as_ref()
11171 .map(|r| r.end.coord.row() + 1)
11172 .or_else(|| range.end_row.map(|b| b.index + 1));
11173 let mut ec = bounded_range
11174 .as_ref()
11175 .map(|r| r.end.coord.col() + 1)
11176 .or_else(|| range.end_col.map(|b| b.index + 1));
11177
11178 if sr.is_none() && er.is_none() {
11179 let scv = sc.unwrap_or(1);
11181 let ecv = ec.unwrap_or(scv);
11182 sr = Some(1);
11183 if let Some((_, max_r)) = self.used_rows_for_columns(sheet_name, scv, ecv) {
11184 er = Some(max_r);
11185 } else if let Some((max_rows, _)) = self.sheet_bounds(sheet_name) {
11186 er = Some(self.config.max_open_ended_rows);
11187 }
11188 }
11189 if sc.is_none() && ec.is_none() {
11190 let srv = sr.unwrap_or(1);
11192 let erv = er.unwrap_or(srv);
11193 sc = Some(1);
11194 if let Some((_, max_c)) = self.used_cols_for_rows(sheet_name, srv, erv) {
11195 ec = Some(max_c);
11196 } else if let Some((_, max_cols)) = self.sheet_bounds(sheet_name) {
11197 ec = Some(self.config.max_open_ended_cols);
11198 }
11199 }
11200 if sr.is_some() && er.is_none() {
11201 let scv = sc.unwrap_or(1);
11202 let ecv = ec.unwrap_or(scv);
11203 if let Some((_, max_r)) = self.used_rows_for_columns(sheet_name, scv, ecv) {
11204 er = Some(max_r);
11205 } else if let Some((max_rows, _)) = self.sheet_bounds(sheet_name) {
11206 er = Some(self.config.max_open_ended_rows);
11207 }
11208 }
11209 if er.is_some() && sr.is_none() {
11210 sr = Some(1);
11212 }
11213 if sc.is_some() && ec.is_none() {
11214 let srv = sr.unwrap_or(1);
11215 let erv = er.unwrap_or(srv);
11216 if let Some((_, max_c)) = self.used_cols_for_rows(sheet_name, srv, erv) {
11217 ec = Some(max_c);
11218 } else if let Some((_, max_cols)) = self.sheet_bounds(sheet_name) {
11219 ec = Some(self.config.max_open_ended_cols);
11220 }
11221 }
11222 if ec.is_some() && sc.is_none() {
11223 sc = Some(1);
11225 }
11226
11227 let sr = sr.unwrap_or(1);
11228 let sc = sc.unwrap_or(1);
11229 let er = er.unwrap_or(sr.saturating_sub(1));
11230 let ec = ec.unwrap_or(sc.saturating_sub(1));
11231
11232 if self.force_materialize_range_views {
11233 if er < sr || ec < sc {
11234 return Ok(RangeView::from_owned_rows(
11235 Vec::new(),
11236 self.config.date_system,
11237 ));
11238 }
11239 let h = (er - sr + 1) as u64;
11240 let w = (ec - sc + 1) as u64;
11241 let cell_count = h.saturating_mul(w);
11242 if cell_count <= self.config.spill.max_spill_cells as u64 {
11243 let mut rows: Vec<Vec<LiteralValue>> = Vec::with_capacity(h as usize);
11244 for r in sr..=er {
11245 let mut rowv: Vec<LiteralValue> = Vec::with_capacity(w as usize);
11246 for c in sc..=ec {
11247 rowv.push(
11248 self.get_cell_value(sheet_name, r, c)
11249 .unwrap_or(LiteralValue::Empty),
11250 );
11251 }
11252 rows.push(rowv);
11253 }
11254 return Ok(RangeView::from_owned_rows(rows, self.config.date_system));
11255 }
11256 }
11257
11258 let Some(asheet) = self.sheet_store().sheet(sheet_name) else {
11259 return Ok(RangeView::from_owned_rows(
11260 Vec::new(),
11261 self.config.date_system,
11262 ));
11263 };
11264
11265 let rv = if er < sr || ec < sc {
11266 asheet.range_view(1, 1, 0, 0)
11267 } else {
11268 let sr0 = sr.saturating_sub(1) as usize;
11269 let sc0 = sc.saturating_sub(1) as usize;
11270 let er0 = er.saturating_sub(1) as usize;
11271 let ec0 = ec.saturating_sub(1) as usize;
11272 asheet.range_view(sr0, sc0, er0, ec0)
11273 };
11274
11275 Ok(rv)
11276 }
11277 ReferenceType::Cell { .. } => {
11278 let shared = self.resolve_shared_ref(reference, current_sheet)?;
11279 let formualizer_common::SheetRef::Cell(cell) = shared else {
11280 return Err(ExcelError::new(ExcelErrorKind::Ref));
11281 };
11282 let addr = CellRef::try_from_shared(cell)?;
11283 let sheet_id = addr.sheet_id;
11284 let sheet_name = self.graph.sheet_name(sheet_id);
11285 let row = addr.coord.row() + 1;
11286 let col = addr.coord.col() + 1;
11287
11288 if self.force_materialize_range_views {
11289 let v = self
11290 .get_cell_value(sheet_name, row, col)
11291 .unwrap_or(LiteralValue::Empty);
11292 return Ok(RangeView::from_owned_rows(
11293 vec![vec![v]],
11294 self.config.date_system,
11295 ));
11296 }
11297
11298 if let Some(asheet) = self.sheet_store().sheet(sheet_name) {
11299 let r0 = row.saturating_sub(1) as usize;
11300 let c0 = col.saturating_sub(1) as usize;
11301 let rv = asheet.range_view(r0, c0, r0, c0);
11302 Ok(rv)
11303 } else {
11304 let v = self
11305 .get_cell_value(sheet_name, row, col)
11306 .unwrap_or(LiteralValue::Empty);
11307 Ok(RangeView::from_owned_rows(
11308 vec![vec![v]],
11309 self.config.date_system,
11310 ))
11311 }
11312 }
11313 ReferenceType::NamedRange(name) => {
11314 if let Some(current_id) = self.graph.sheet_id(current_sheet)
11315 && let Some(named) = self.graph.resolve_name_entry(name, current_id)
11316 {
11317 match &named.definition {
11318 NamedDefinition::Cell(cell_ref) => {
11319 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
11320 if self.force_materialize_range_views {
11321 let v = self
11322 .get_cell_value(
11323 sheet_name,
11324 cell_ref.coord.row() + 1,
11325 cell_ref.coord.col() + 1,
11326 )
11327 .unwrap_or(LiteralValue::Empty);
11328 return Ok(RangeView::from_owned_rows(
11329 vec![vec![v]],
11330 self.config.date_system,
11331 ));
11332 } else {
11333 let asheet = self
11334 .sheet_store()
11335 .sheet(sheet_name)
11336 .expect("Arrow sheet missing for named cell");
11337 let r0 = cell_ref.coord.row() as usize;
11338 let c0 = cell_ref.coord.col() as usize;
11339 let rv = asheet.range_view(r0, c0, r0, c0);
11340 return Ok(rv);
11341 }
11342 }
11343 NamedDefinition::Range(range_ref) => {
11344 let sheet_name = self.graph.sheet_name(range_ref.start.sheet_id);
11345 let sr = range_ref.start.coord.row() + 1;
11346 let sc = range_ref.start.coord.col() + 1;
11347 let er = range_ref.end.coord.row() + 1;
11348 let ec = range_ref.end.coord.col() + 1;
11349 if self.force_materialize_range_views {
11350 let h = (er.saturating_sub(sr) + 1) as u64;
11351 let w = (ec.saturating_sub(sc) + 1) as u64;
11352 let cell_count = h.saturating_mul(w);
11353 if cell_count <= self.config.spill.max_spill_cells as u64 {
11354 let mut rows: Vec<Vec<LiteralValue>> =
11355 Vec::with_capacity(h as usize);
11356 for r in sr..=er {
11357 let mut rowv: Vec<LiteralValue> =
11358 Vec::with_capacity(w as usize);
11359 for c in sc..=ec {
11360 rowv.push(
11361 self.get_cell_value(sheet_name, r, c)
11362 .unwrap_or(LiteralValue::Empty),
11363 );
11364 }
11365 rows.push(rowv);
11366 }
11367 return Ok(RangeView::from_owned_rows(
11368 rows,
11369 self.config.date_system,
11370 ));
11371 }
11372 }
11373 let asheet = self
11374 .sheet_store()
11375 .sheet(sheet_name)
11376 .expect("Arrow sheet missing for named range");
11377 let sr0 = range_ref.start.coord.row() as usize;
11378 let sc0 = range_ref.start.coord.col() as usize;
11379 let er0 = range_ref.end.coord.row() as usize;
11380 let ec0 = range_ref.end.coord.col() as usize;
11381 let rv = asheet.range_view(sr0, sc0, er0, ec0);
11382 return Ok(rv);
11383 }
11384 NamedDefinition::Literal(v) => {
11385 return Ok(RangeView::from_owned_rows(
11386 vec![vec![v.clone()]],
11387 self.config.date_system,
11388 ));
11389 }
11390 NamedDefinition::Formula { .. } => {
11391 if let Some(value) = self.graph.get_value(named.vertex) {
11392 return Ok(RangeView::from_owned_rows(
11393 vec![vec![value]],
11394 self.config.date_system,
11395 ));
11396 }
11397 }
11398 }
11399 }
11400
11401 if let Some(source) = self.graph.resolve_source_scalar_entry(name) {
11402 let version = source
11403 .version
11404 .or_else(|| self.resolver.source_scalar_version(name));
11405 let v = self.resolve_source_scalar_cached(name, version)?;
11406 return Ok(RangeView::from_owned_rows(
11407 vec![vec![v]],
11408 self.config.date_system,
11409 ));
11410 }
11411
11412 let data = self.resolver.resolve_named_range_reference(name)?;
11413 Ok(RangeView::from_owned_rows(data, self.config.date_system))
11414 }
11415 ReferenceType::Table(tref) => {
11416 if let Some(table) = self.graph.resolve_table_entry(&tref.name) {
11417 let sheet_name = self.graph.sheet_name(table.range.start.sheet_id);
11418 let asheet = self
11419 .sheet_store()
11420 .sheet(sheet_name)
11421 .expect("Arrow sheet missing for table reference");
11422
11423 let sr0 = table.range.start.coord.row() as usize;
11424 let sc0 = table.range.start.coord.col() as usize;
11425 let er0 = table.range.end.coord.row() as usize;
11426 let ec0 = table.range.end.coord.col() as usize;
11427
11428 let has_totals = table.totals_row;
11429 let has_headers = table.header_row;
11430 let data_sr = if has_headers {
11431 sr0.saturating_add(1)
11432 } else {
11433 sr0
11434 };
11435 let data_er = if has_totals {
11436 er0.saturating_sub(1)
11437 } else {
11438 er0
11439 };
11440
11441 let select = |sr: usize, sc: usize, er: usize, ec: usize| {
11442 if sr > er || sc > ec {
11443 asheet.range_view(1, 1, 0, 0)
11444 } else {
11445 asheet.range_view(sr, sc, er, ec)
11446 }
11447 };
11448
11449 let av = match &tref.specifier {
11450 None => {
11451 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
11452 "Table reference without specifier is unsupported".to_string(),
11453 ));
11454 }
11455 Some(formualizer_parse::parser::TableSpecifier::Column(col)) => {
11456 let Some(idx) = table.col_index(col) else {
11457 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
11458 "Column refers to unknown table column".to_string(),
11459 ));
11460 };
11461 let c0 = sc0 + idx;
11462 select(data_sr, c0, data_er, c0)
11463 }
11464 Some(formualizer_parse::parser::TableSpecifier::ColumnRange(
11465 start,
11466 end,
11467 )) => {
11468 let Some(si) = table.col_index(start) else {
11469 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
11470 "Column range refers to unknown column(s)".to_string(),
11471 ));
11472 };
11473 let Some(ei) = table.col_index(end) else {
11474 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
11475 "Column range refers to unknown column(s)".to_string(),
11476 ));
11477 };
11478 let (mut a, mut b) = (si, ei);
11479 if a > b {
11480 std::mem::swap(&mut a, &mut b);
11481 }
11482 let c_start = sc0 + a;
11483 let c_end = sc0 + b;
11484 select(data_sr, c_start, data_er, c_end)
11485 }
11486 Some(formualizer_parse::parser::TableSpecifier::All)
11487 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
11488 formualizer_parse::parser::SpecialItem::All,
11489 )) => select(sr0, sc0, er0, ec0),
11490 Some(formualizer_parse::parser::TableSpecifier::Data)
11491 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
11492 formualizer_parse::parser::SpecialItem::Data,
11493 )) => select(data_sr, sc0, data_er, ec0),
11494 Some(formualizer_parse::parser::TableSpecifier::Headers)
11495 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
11496 formualizer_parse::parser::SpecialItem::Headers,
11497 )) => {
11498 if !has_headers {
11499 asheet.range_view(1, 1, 0, 0)
11500 } else {
11501 select(sr0, sc0, sr0, ec0)
11502 }
11503 }
11504 Some(formualizer_parse::parser::TableSpecifier::Totals)
11505 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
11506 formualizer_parse::parser::SpecialItem::Totals,
11507 )) => {
11508 if !has_totals {
11509 asheet.range_view(1, 1, 0, 0)
11510 } else {
11511 select(er0, sc0, er0, ec0)
11512 }
11513 }
11514 Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
11515 formualizer_parse::parser::SpecialItem::ThisRow,
11516 )) => {
11517 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
11518 "@ (This Row) requires table-aware context; not yet supported"
11519 .to_string(),
11520 ));
11521 }
11522 Some(formualizer_parse::parser::TableSpecifier::Row(_))
11523 | Some(formualizer_parse::parser::TableSpecifier::Combination(_)) => {
11524 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
11525 "Complex structured references not yet supported".to_string(),
11526 ));
11527 }
11528 };
11529
11530 return Ok(av);
11531 }
11532
11533 if let Some(source) = self.graph.resolve_source_table_entry(&tref.name) {
11534 let version = source
11535 .version
11536 .or_else(|| self.resolver.source_table_version(&tref.name));
11537 let table = self.resolve_source_table_cached(&tref.name, version)?;
11538 return self.source_table_to_range_view(table.as_ref(), &tref.specifier);
11539 }
11540
11541 let boxed = self.resolve_range_like(&ReferenceType::Table(tref.clone()))?;
11543 let owned = boxed.materialise().into_owned();
11544 Ok(RangeView::from_owned_rows(owned, self.config.date_system))
11545 }
11546 ReferenceType::Cell3D { .. } | ReferenceType::Range3D { .. } => {
11547 Err(ExcelError::new(ExcelErrorKind::NImpl)
11548 .with_message("3D references are not yet supported".to_string()))
11549 }
11550 }
11551 }
11552
11553 fn resolve_cell_reference_value(
11554 &self,
11555 sheet: Option<&str>,
11556 row: u32,
11557 col: u32,
11558 current_sheet: &str,
11559 ) -> Result<LiteralValue, ExcelError> {
11560 let sheet_name = sheet.unwrap_or(current_sheet);
11561 if self.graph.sheet_id(sheet_name).is_none() {
11562 return Err(ExcelError::new(ExcelErrorKind::Ref));
11563 }
11564 Ok(self
11565 .get_cell_value(sheet_name, row, col)
11566 .unwrap_or(LiteralValue::Empty))
11567 }
11568
11569 fn build_criteria_mask(
11570 &self,
11571 view: &RangeView<'_>,
11572 col_in_view: usize,
11573 pred: &crate::args::CriteriaPredicate,
11574 ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
11575 if view.dims().1 == 0 {
11576 return None;
11577 }
11578 let sheet_rows = view.sheet().nrows as usize;
11581 if sheet_rows == 0 || view.start_row() >= sheet_rows {
11582 return Some(std::sync::Arc::new(arrow_array::BooleanArray::new_null(0)));
11583 }
11584 compute_criteria_mask(view, col_in_view, pred)
11585 }
11586
11587 fn build_row_visibility_mask(
11588 &self,
11589 view: &RangeView<'_>,
11590 mode: VisibilityMaskMode,
11591 ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
11592 self.build_row_visibility_mask_for_view(view, mode)
11593 }
11594}
11595
11596impl<R> Engine<R>
11597where
11598 R: EvaluationContext,
11599{
11600 fn clear_spill_projection_and_mirror(
11601 &mut self,
11602 anchor_vertex: VertexId,
11603 delta: Option<&mut DeltaCollector>,
11604 ) {
11605 let spill_cells = self
11606 .graph
11607 .spill_cells_for_anchor(anchor_vertex)
11608 .map(|cells| cells.to_vec())
11609 .unwrap_or_default();
11610 if spill_cells.is_empty() {
11611 return;
11612 }
11613
11614 if let Some(delta) = delta
11615 && delta.mode != DeltaMode::Off
11616 {
11617 let empty = LiteralValue::Empty;
11618 for cell in spill_cells.iter() {
11619 let sheet_name = self.graph.sheet_name(cell.sheet_id);
11620 let old = self
11621 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
11622 .unwrap_or(LiteralValue::Empty);
11623 if old != empty {
11624 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
11625 }
11626 }
11627 }
11628
11629 self.graph.clear_spill_region(anchor_vertex);
11630 if let Some(scope) = Self::formula_plane_region_from_cells(&spill_cells) {
11631 self.record_formula_plane_structural_change(scope);
11632 }
11633
11634 if self.config.arrow_storage_enabled
11635 && self.config.delta_overlay_enabled
11636 && self.config.write_formula_overlay_enabled
11637 {
11638 let empty = LiteralValue::Empty;
11639 for cell in spill_cells.iter() {
11640 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
11641 self.mirror_value_to_computed_overlay(
11642 &sheet_name,
11643 cell.coord.row() + 1,
11644 cell.coord.col() + 1,
11645 &empty,
11646 );
11647 }
11648 }
11649 }
11650
11651 fn commit_spill_and_mirror(
11653 &mut self,
11654 anchor_vertex: VertexId,
11655 targets: &[CellRef],
11656 rows: Vec<Vec<LiteralValue>>,
11657 delta: Option<&mut DeltaCollector>,
11658 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
11659 ) -> Result<(), ExcelError> {
11660 let prev_spill_cells = self
11661 .graph
11662 .spill_cells_for_anchor(anchor_vertex)
11663 .map(|cells| cells.to_vec())
11664 .unwrap_or_default();
11665
11666 if let Some(delta) = delta
11667 && delta.mode != DeltaMode::Off
11668 {
11669 let target_set: std::collections::HashSet<CellRef, CoordBuildHasher> =
11670 targets.iter().copied().collect();
11671 let empty = LiteralValue::Empty;
11672
11673 for cell in prev_spill_cells.iter() {
11675 if target_set.contains(cell) {
11676 continue;
11677 }
11678 let sheet_name = self.graph.sheet_name(cell.sheet_id);
11679 let old = self
11680 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
11681 .unwrap_or(LiteralValue::Empty);
11682 if old != empty {
11683 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
11684 }
11685 }
11686
11687 if !targets.is_empty() && !rows.is_empty() && !rows[0].is_empty() {
11689 let width = rows[0].len();
11690 for (idx, cell) in targets.iter().enumerate() {
11691 let r_off = idx / width;
11692 let c_off = idx % width;
11693 let new = rows
11694 .get(r_off)
11695 .and_then(|r| r.get(c_off))
11696 .cloned()
11697 .unwrap_or(LiteralValue::Empty);
11698 let sheet_name = self.graph.sheet_name(cell.sheet_id);
11699 let old = self
11700 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
11701 .unwrap_or(LiteralValue::Empty);
11702 if old != new {
11703 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
11704 }
11705 }
11706 } else {
11707 for cell in targets.iter() {
11709 let sheet_name = self.graph.sheet_name(cell.sheet_id);
11710 let old = self
11711 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
11712 .unwrap_or(LiteralValue::Empty);
11713 if !matches!(old, LiteralValue::Empty) {
11714 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
11715 }
11716 }
11717 }
11718 }
11719
11720 let arrow_sheets = &self.arrow_sheets;
11723 self.spill_mgr.commit_array_with_value_probe(
11724 &mut self.graph,
11725 anchor_vertex,
11726 targets,
11727 rows.clone(),
11728 overwritable_formulas,
11729 |g, cell| {
11730 let sheet_name = g.sheet_name(cell.sheet_id);
11731 let asheet = arrow_sheets.sheet(sheet_name)?;
11732 let r0 = cell.coord.row() as usize;
11733 let c0 = cell.coord.col() as usize;
11734 let v = asheet.get_cell_value(r0, c0);
11735 if matches!(v, LiteralValue::Empty) {
11736 None
11737 } else {
11738 Some(v)
11739 }
11740 },
11741 )?;
11742
11743 if let Some(scope) = Self::formula_plane_region_from_cells(&prev_spill_cells) {
11744 self.record_formula_plane_structural_change(scope);
11745 }
11746 if let Some(scope) = Self::formula_plane_region_from_cells(targets) {
11747 self.record_formula_plane_structural_change(scope);
11748 }
11749
11750 if self.config.arrow_storage_enabled
11751 && self.config.delta_overlay_enabled
11752 && self.config.write_formula_overlay_enabled
11753 {
11754 if !prev_spill_cells.is_empty() {
11755 let target_set: std::collections::HashSet<CellRef, CoordBuildHasher> =
11756 targets.iter().copied().collect();
11757 let empty = LiteralValue::Empty;
11758 for cell in prev_spill_cells.iter() {
11759 if !target_set.contains(cell) {
11760 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
11761 self.mirror_value_to_computed_overlay(
11762 &sheet_name,
11763 cell.coord.row() + 1,
11764 cell.coord.col() + 1,
11765 &empty,
11766 );
11767 }
11768 }
11769 }
11770
11771 for (idx, cell) in targets.iter().enumerate() {
11772 if rows.is_empty() || rows[0].is_empty() {
11773 break;
11774 }
11775 let width = rows[0].len();
11776 let r_off = idx / width;
11777 let c_off = idx % width;
11778 let v = rows[r_off][c_off].clone();
11779 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
11780 self.mirror_value_to_computed_overlay(
11781 &sheet_name,
11782 cell.coord.row() + 1,
11783 cell.coord.col() + 1,
11784 &v,
11785 );
11786 }
11787 }
11788 Ok(())
11789 }
11790}
11791
11792use crate::engine::effects::Effect;
11797use crate::engine::graph::editor::change_log::{ChangeEvent, ChangeLog, SpillSnapshot};
11798
11799impl<R> Engine<R>
11800where
11801 R: EvaluationContext,
11802{
11803 pub(crate) fn plan_vertex_effects(
11810 &mut self,
11811 vertex_id: VertexId,
11812 computed_value: LiteralValue,
11813 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
11814 ) -> Result<Vec<Effect>, ExcelError> {
11815 let kind = self.graph.get_vertex_kind(vertex_id);
11816 let is_formula = matches!(kind, VertexKind::FormulaScalar | VertexKind::FormulaArray);
11817
11818 if !is_formula {
11822 if let Some(cell) = self.graph.get_cell_ref(vertex_id)
11823 && let Some(owner) = self.graph.spill_registry_anchor_for_cell(cell)
11824 && owner != vertex_id
11825 {
11826 return Ok(Vec::new());
11827 }
11828 return Ok(vec![Effect::WriteCell {
11830 vertex_id,
11831 value: computed_value,
11832 }]);
11833 }
11834
11835 match computed_value {
11836 LiteralValue::Array(rows) => {
11837 self.plan_array_effects(vertex_id, rows, overwritable_formulas)
11838 }
11839 other => self.plan_scalar_effects(vertex_id, other),
11840 }
11841 }
11842
11843 fn plan_scalar_effects(
11845 &self,
11846 vertex_id: VertexId,
11847 value: LiteralValue,
11848 ) -> Result<Vec<Effect>, ExcelError> {
11849 let has_spill = self
11850 .graph
11851 .spill_cells_for_anchor(vertex_id)
11852 .is_some_and(|c| !c.is_empty());
11853
11854 let mut effects = Vec::new();
11855 if has_spill {
11856 effects.push(Effect::SpillClear {
11857 anchor_vertex: vertex_id,
11858 });
11859 }
11860 effects.push(Effect::WriteCell { vertex_id, value });
11861 Ok(effects)
11862 }
11863
11864 fn plan_array_effects(
11866 &mut self,
11867 vertex_id: VertexId,
11868 rows: Vec<Vec<LiteralValue>>,
11869 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
11870 ) -> Result<Vec<Effect>, ExcelError> {
11871 self.graph.set_kind(vertex_id, VertexKind::FormulaArray);
11873
11874 let anchor = self
11875 .graph
11876 .get_cell_ref(vertex_id)
11877 .expect("cell ref for vertex");
11878 let sheet_id = anchor.sheet_id;
11879 let h = rows.len() as u32;
11880 let w = rows.first().map(|r| r.len()).unwrap_or(0) as u32;
11881
11882 let spill_cells = (h as u64).saturating_mul(w as u64);
11884 if spill_cells > self.config.spill.max_spill_cells as u64 {
11885 return self.plan_spill_error_effects(vertex_id, "SpillTooLarge", h, w);
11886 }
11887
11888 const PACKED_MAX_ROW: u32 = 1_048_575;
11890 const PACKED_MAX_COL: u32 = 16_383;
11891 let end_row = anchor.coord.row().saturating_add(h).saturating_sub(1);
11892 let end_col = anchor.coord.col().saturating_add(w).saturating_sub(1);
11893 if end_row > PACKED_MAX_ROW || end_col > PACKED_MAX_COL {
11894 return self.plan_spill_error_effects(vertex_id, "Spill exceeds sheet bounds", h, w);
11895 }
11896
11897 let mut targets = Vec::new();
11898 for r in 0..h {
11899 for c in 0..w {
11900 targets.push(self.graph.make_cell_ref_internal(
11901 sheet_id,
11902 anchor.coord.row() + r,
11903 anchor.coord.col() + c,
11904 ));
11905 }
11906 }
11907
11908 match self.spill_mgr.reserve(
11910 vertex_id,
11911 anchor,
11912 SpillShape { rows: h, cols: w },
11913 SpillMeta {
11914 epoch: self.recalc_epoch,
11915 config: self.config.spill,
11916 },
11917 ) {
11918 Ok(()) => {
11919 if let Err(_e) = self.graph.plan_spill_region_allowing_formula_overwrite(
11921 vertex_id,
11922 &targets,
11923 overwritable_formulas,
11924 ) {
11925 return self.plan_spill_error_effects(vertex_id, "Spill blocked", h, w);
11926 }
11927
11928 if !self.graph.value_cache_enabled() {
11932 let sheet_name = self.graph.sheet_name(sheet_id);
11933 if let Some(asheet) = self.sheet_store().sheet(sheet_name) {
11934 for cell in targets.iter() {
11935 if *cell == anchor {
11937 continue;
11938 }
11939 if self.graph.spill_registry_anchor_for_cell(*cell).is_some() {
11941 continue;
11942 }
11943 if let Some(&vid) = self.graph.get_vertex_id_for_address(cell)
11945 && vid != vertex_id
11946 {
11947 match self.graph.get_vertex_kind(vid) {
11948 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
11949 continue;
11950 }
11951 _ => {}
11952 }
11953 }
11954
11955 let v = asheet.get_cell_value(
11956 cell.coord.row() as usize,
11957 cell.coord.col() as usize,
11958 );
11959 if !matches!(v, LiteralValue::Empty) {
11960 return self.plan_spill_error_effects(
11961 vertex_id,
11962 "BlockedByValue",
11963 h,
11964 w,
11965 );
11966 }
11967 }
11968 }
11969 }
11970
11971 let top_left = rows
11972 .first()
11973 .and_then(|r| r.first())
11974 .cloned()
11975 .unwrap_or(LiteralValue::Empty);
11976
11977 let mut effects = Vec::new();
11978 let has_prev = self
11980 .graph
11981 .spill_cells_for_anchor(vertex_id)
11982 .is_some_and(|c| !c.is_empty());
11983 if has_prev {
11984 effects.push(Effect::SpillClear {
11985 anchor_vertex: vertex_id,
11986 });
11987 }
11988 effects.push(Effect::SpillCommit {
11989 anchor_vertex: vertex_id,
11990 anchor_cell: anchor,
11991 target_cells: targets,
11992 values: rows,
11993 });
11994 effects.push(Effect::WriteCell {
11995 vertex_id,
11996 value: top_left,
11997 });
11998 Ok(effects)
11999 }
12000 Err(e) => {
12001 let msg = e.message.unwrap_or_else(|| "Spill blocked".to_string());
12002 self.plan_spill_error_effects(vertex_id, &msg, h, w)
12003 }
12004 }
12005 }
12006
12007 fn plan_spill_error_effects(
12009 &self,
12010 vertex_id: VertexId,
12011 message: &str,
12012 expected_rows: u32,
12013 expected_cols: u32,
12014 ) -> Result<Vec<Effect>, ExcelError> {
12015 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
12016 .with_message(message)
12017 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
12018 expected_rows,
12019 expected_cols,
12020 });
12021 let spill_val = LiteralValue::Error(spill_err);
12022
12023 let effects = vec![
12024 Effect::SpillClear {
12025 anchor_vertex: vertex_id,
12026 },
12027 Effect::WriteCell {
12028 vertex_id,
12029 value: spill_val,
12030 },
12031 ];
12032 Ok(effects)
12033 }
12034
12035 pub(crate) fn apply_effect(
12037 &mut self,
12038 effect: &Effect,
12039 delta: Option<&mut DeltaCollector>,
12040 log: Option<&mut ChangeLog>,
12041 ) -> Result<(), ExcelError> {
12042 self.apply_effect_with_computed_writes(effect, delta, log, None)
12043 }
12044
12045 fn apply_effect_with_computed_writes(
12046 &mut self,
12047 effect: &Effect,
12048 delta: Option<&mut DeltaCollector>,
12049 log: Option<&mut ChangeLog>,
12050 computed_writes: Option<&mut ComputedWriteBuffer>,
12051 ) -> Result<(), ExcelError> {
12052 match effect {
12053 Effect::WriteCell { vertex_id, value } => {
12054 self.apply_write_cell(*vertex_id, value, delta, computed_writes)?;
12055 }
12056 Effect::SpillClear { anchor_vertex } => {
12057 self.apply_spill_clear(*anchor_vertex, delta, log, computed_writes)?;
12058 }
12059 Effect::SpillCommit {
12060 anchor_vertex,
12061 anchor_cell: _,
12062 target_cells,
12063 values,
12064 } => {
12065 self.apply_spill_commit(
12066 *anchor_vertex,
12067 target_cells,
12068 values.clone(),
12069 delta,
12070 log,
12071 computed_writes,
12072 )?;
12073 }
12074 }
12075 Ok(())
12076 }
12077
12078 fn apply_write_cell(
12080 &mut self,
12081 vertex_id: VertexId,
12082 value: &LiteralValue,
12083 delta: Option<&mut DeltaCollector>,
12084 mut computed_writes: Option<&mut ComputedWriteBuffer>,
12085 ) -> Result<(), ExcelError> {
12086 if let Some(d) = delta
12087 && d.mode != DeltaMode::Off
12088 {
12089 if let Some(buffer) = computed_writes.as_deref_mut() {
12090 self.flush_computed_write_buffer(buffer)?;
12091 }
12092 if let Some(cell) = self.graph.get_cell_ref_for_vertex(vertex_id) {
12093 let sheet_name = self.graph.sheet_name(cell.sheet_id);
12094 let old = self
12095 .read_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
12096 .unwrap_or(LiteralValue::Empty);
12097 if old != *value {
12098 d.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
12099 }
12100 }
12101 }
12102 self.graph.update_vertex_value(vertex_id, value.clone());
12103 self.record_vertex_value_to_overlay(vertex_id, value, computed_writes)?;
12104 Ok(())
12105 }
12106
12107 fn apply_spill_clear(
12109 &mut self,
12110 anchor_vertex: VertexId,
12111 delta: Option<&mut DeltaCollector>,
12112 log: Option<&mut ChangeLog>,
12113 computed_writes: Option<&mut ComputedWriteBuffer>,
12114 ) -> Result<(), ExcelError> {
12115 if let Some(buffer) = computed_writes {
12116 self.flush_computed_write_buffer(buffer)?;
12117 }
12118
12119 let spill_cells = self
12120 .graph
12121 .spill_cells_for_anchor(anchor_vertex)
12122 .map(|cells| cells.to_vec())
12123 .unwrap_or_default();
12124 if spill_cells.is_empty() {
12125 return Ok(());
12126 }
12127
12128 let snapshot = if log.is_some() {
12130 self.snapshot_spill_for_anchor(anchor_vertex)
12131 } else {
12132 None
12133 };
12134
12135 if let Some(d) = delta
12137 && d.mode != DeltaMode::Off
12138 {
12139 let empty = LiteralValue::Empty;
12140 for cell in spill_cells.iter() {
12141 let sheet_name = self.graph.sheet_name(cell.sheet_id);
12142 let old = self
12143 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
12144 .unwrap_or(LiteralValue::Empty);
12145 if old != empty {
12146 d.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
12147 }
12148 }
12149 }
12150
12151 self.graph.clear_spill_region(anchor_vertex);
12152 if let Some(scope) = Self::formula_plane_region_from_cells(&spill_cells) {
12153 self.record_formula_plane_structural_change(scope);
12154 }
12155
12156 if self.config.arrow_storage_enabled
12158 && self.config.delta_overlay_enabled
12159 && self.config.write_formula_overlay_enabled
12160 {
12161 let empty = LiteralValue::Empty;
12162 for cell in spill_cells.iter() {
12163 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
12164 self.mirror_value_to_computed_overlay(
12165 &sheet_name,
12166 cell.coord.row() + 1,
12167 cell.coord.col() + 1,
12168 &empty,
12169 );
12170 }
12171 }
12172
12173 if let Some(log) = log
12175 && let Some(old) = snapshot
12176 {
12177 log.record(ChangeEvent::SpillCleared {
12178 anchor: anchor_vertex,
12179 old,
12180 });
12181 }
12182 Ok(())
12183 }
12184
12185 fn apply_spill_commit(
12187 &mut self,
12188 anchor_vertex: VertexId,
12189 target_cells: &[CellRef],
12190 values: Vec<Vec<LiteralValue>>,
12191 delta: Option<&mut DeltaCollector>,
12192 log: Option<&mut ChangeLog>,
12193 computed_writes: Option<&mut ComputedWriteBuffer>,
12194 ) -> Result<(), ExcelError> {
12195 if let Some(buffer) = computed_writes {
12196 self.flush_computed_write_buffer(buffer)?;
12197 }
12198
12199 let old_snapshot = if log.is_some() {
12201 self.snapshot_spill_for_anchor(anchor_vertex)
12202 } else {
12203 None
12204 };
12205
12206 self.commit_spill_and_mirror(
12208 anchor_vertex,
12209 target_cells,
12210 values.clone(),
12211 delta,
12212 None, )?;
12214
12215 if let Some(log) = log {
12217 log.record(ChangeEvent::SpillCommitted {
12218 anchor: anchor_vertex,
12219 old: old_snapshot,
12220 new: SpillSnapshot {
12221 target_cells: target_cells.to_vec(),
12222 values,
12223 },
12224 });
12225 }
12226 Ok(())
12227 }
12228
12229 fn snapshot_spill_for_anchor(&self, anchor: VertexId) -> Option<SpillSnapshot> {
12234 let cells = self.graph.spill_cells_for_anchor(anchor)?.to_vec();
12235 if cells.is_empty() {
12236 return None;
12237 }
12238
12239 let max = self.config.spill.max_spill_cells as usize;
12240 let mut cells = cells;
12241 if cells.len() > max {
12242 cells.truncate(max);
12243 }
12244
12245 let first = *cells.first().expect("non-empty spill cells");
12246 let sheet_name = self.graph.sheet_name(first.sheet_id).to_string();
12247 let row0 = first.coord.row();
12248 let col0 = first.coord.col();
12249
12250 let mut max_row = row0;
12251 let mut max_col = col0;
12252 let mut by_coord: FxHashMap<(u32, u32), LiteralValue> = FxHashMap::default();
12253 for cell in &cells {
12254 max_row = max_row.max(cell.coord.row());
12255 max_col = max_col.max(cell.coord.col());
12256 let v = self
12257 .get_cell_value(&sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
12258 .unwrap_or(LiteralValue::Empty);
12259 by_coord.insert((cell.coord.row(), cell.coord.col()), v);
12260 }
12261
12262 let rows = (max_row - row0 + 1) as usize;
12263 let cols = (max_col - col0 + 1) as usize;
12264 let mut values: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows);
12265 for r in 0..rows {
12266 let mut row: Vec<LiteralValue> = Vec::with_capacity(cols);
12267 for c in 0..cols {
12268 row.push(
12269 by_coord
12270 .get(&(row0 + r as u32, col0 + c as u32))
12271 .cloned()
12272 .unwrap_or(LiteralValue::Empty),
12273 );
12274 }
12275 values.push(row);
12276 }
12277
12278 Some(SpillSnapshot {
12279 target_cells: cells,
12280 values,
12281 })
12282 }
12283
12284 fn flush_before_range_dependent_vertex(
12285 &mut self,
12286 vertex_id: VertexId,
12287 computed_writes: &mut ComputedWriteBuffer,
12288 ) -> Result<(), ExcelError> {
12289 if self.graph.get_range_dependencies(vertex_id).is_some() {
12290 self.flush_computed_write_buffer(computed_writes)?;
12291 }
12292 Ok(())
12293 }
12294
12295 fn plan_vertex_effects_with_computed_flush(
12296 &mut self,
12297 vertex_id: VertexId,
12298 computed_value: LiteralValue,
12299 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
12300 computed_writes: &mut ComputedWriteBuffer,
12301 ) -> Result<Vec<Effect>, ExcelError> {
12302 if matches!(&computed_value, LiteralValue::Array(_)) {
12303 self.flush_computed_write_buffer(computed_writes)?;
12304 }
12305 self.plan_vertex_effects(vertex_id, computed_value, overwritable_formulas)
12306 }
12307
12308 fn evaluate_small_layer_direct_effects(
12311 &mut self,
12312 layer: &super::scheduler::Layer,
12313 mut delta: Option<&mut DeltaCollector>,
12314 mut log: Option<&mut ChangeLog>,
12315 cancel_flag: Option<&AtomicBool>,
12316 cancel_check_every: usize,
12317 cancel_message: &'static str,
12318 ) -> Result<usize, ExcelError> {
12319 for (i, &vertex_id) in layer.vertices.iter().enumerate() {
12320 if cancel_check_every > 0
12321 && i % cancel_check_every == 0
12322 && cancel_flag.is_some_and(|flag| flag.load(Ordering::Relaxed))
12323 {
12324 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
12325 .with_message(cancel_message.to_string()));
12326 }
12327 let value = match self.evaluate_vertex_immutable(vertex_id) {
12328 Ok(v) => v,
12329 Err(e) => LiteralValue::Error(e),
12330 };
12331 let effects = self.plan_vertex_effects(vertex_id, value, None)?;
12332 for effect in &effects {
12333 self.apply_effect_with_computed_writes(
12334 effect,
12335 delta.as_deref_mut(),
12336 log.as_deref_mut(),
12337 None,
12338 )?;
12339 }
12340 }
12341 Ok(layer.vertices.len())
12342 }
12343
12344 fn evaluate_layer_sequential_effects(
12346 &mut self,
12347 layer: &super::scheduler::Layer,
12348 ) -> Result<usize, ExcelError> {
12349 if layer.vertices.len() < COMPUTED_WRITE_COALESCING_MIN_LAYER_WIDTH {
12350 return self.evaluate_small_layer_direct_effects(
12351 layer,
12352 None,
12353 None,
12354 None,
12355 0,
12356 "Evaluation cancelled within layer",
12357 );
12358 }
12359
12360 let mut computed_writes = ComputedWriteBuffer::default();
12361 for &vertex_id in &layer.vertices {
12362 self.flush_before_range_dependent_vertex(vertex_id, &mut computed_writes)?;
12363 let value = match self.evaluate_vertex_immutable(vertex_id) {
12364 Ok(v) => v,
12365 Err(e) => LiteralValue::Error(e),
12366 };
12367 let effects = match self.plan_vertex_effects_with_computed_flush(
12368 vertex_id,
12369 value,
12370 None,
12371 &mut computed_writes,
12372 ) {
12373 Ok(effects) => effects,
12374 Err(e) => {
12375 self.flush_computed_write_buffer(&mut computed_writes)?;
12376 return Err(e);
12377 }
12378 };
12379 for effect in &effects {
12380 if let Err(e) = self.apply_effect_with_computed_writes(
12381 effect,
12382 None,
12383 None,
12384 Some(&mut computed_writes),
12385 ) {
12386 self.flush_computed_write_buffer(&mut computed_writes)?;
12387 return Err(e);
12388 }
12389 }
12390 }
12391 self.flush_computed_write_buffer(&mut computed_writes)?;
12392 Ok(layer.vertices.len())
12393 }
12394
12395 fn evaluate_layer_sequential_with_delta_effects(
12397 &mut self,
12398 layer: &super::scheduler::Layer,
12399 delta: &mut DeltaCollector,
12400 ) -> Result<usize, ExcelError> {
12401 if layer.vertices.len() < COMPUTED_WRITE_COALESCING_MIN_LAYER_WIDTH {
12402 return self.evaluate_small_layer_direct_effects(
12403 layer,
12404 Some(delta),
12405 None,
12406 None,
12407 0,
12408 "Evaluation cancelled within layer",
12409 );
12410 }
12411
12412 let mut computed_writes = ComputedWriteBuffer::default();
12413 for &vertex_id in &layer.vertices {
12414 self.flush_before_range_dependent_vertex(vertex_id, &mut computed_writes)?;
12415 let value = match self.evaluate_vertex_immutable(vertex_id) {
12416 Ok(v) => v,
12417 Err(e) => LiteralValue::Error(e),
12418 };
12419 let effects = match self.plan_vertex_effects_with_computed_flush(
12420 vertex_id,
12421 value,
12422 None,
12423 &mut computed_writes,
12424 ) {
12425 Ok(effects) => effects,
12426 Err(e) => {
12427 self.flush_computed_write_buffer(&mut computed_writes)?;
12428 return Err(e);
12429 }
12430 };
12431 for effect in &effects {
12432 if let Err(e) = self.apply_effect_with_computed_writes(
12433 effect,
12434 Some(delta),
12435 None,
12436 Some(&mut computed_writes),
12437 ) {
12438 self.flush_computed_write_buffer(&mut computed_writes)?;
12439 return Err(e);
12440 }
12441 }
12442 }
12443 self.flush_computed_write_buffer(&mut computed_writes)?;
12444 Ok(layer.vertices.len())
12445 }
12446
12447 fn evaluate_layer_sequential_cancellable_effects(
12449 &mut self,
12450 layer: &super::scheduler::Layer,
12451 cancel_flag: &AtomicBool,
12452 ) -> Result<usize, ExcelError> {
12453 if layer.vertices.len() < COMPUTED_WRITE_COALESCING_MIN_LAYER_WIDTH {
12454 return self.evaluate_small_layer_direct_effects(
12455 layer,
12456 None,
12457 None,
12458 Some(cancel_flag),
12459 256,
12460 "Evaluation cancelled within layer",
12461 );
12462 }
12463
12464 let mut computed_writes = ComputedWriteBuffer::default();
12465 for (i, &vertex_id) in layer.vertices.iter().enumerate() {
12466 if i % 256 == 0 && cancel_flag.load(Ordering::Relaxed) {
12467 self.flush_computed_write_buffer(&mut computed_writes)?;
12468 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
12469 .with_message("Evaluation cancelled within layer".to_string()));
12470 }
12471 self.flush_before_range_dependent_vertex(vertex_id, &mut computed_writes)?;
12472 let value = match self.evaluate_vertex_immutable(vertex_id) {
12473 Ok(v) => v,
12474 Err(e) => LiteralValue::Error(e),
12475 };
12476 let effects = match self.plan_vertex_effects_with_computed_flush(
12477 vertex_id,
12478 value,
12479 None,
12480 &mut computed_writes,
12481 ) {
12482 Ok(effects) => effects,
12483 Err(e) => {
12484 self.flush_computed_write_buffer(&mut computed_writes)?;
12485 return Err(e);
12486 }
12487 };
12488 for effect in &effects {
12489 if let Err(e) = self.apply_effect_with_computed_writes(
12490 effect,
12491 None,
12492 None,
12493 Some(&mut computed_writes),
12494 ) {
12495 self.flush_computed_write_buffer(&mut computed_writes)?;
12496 return Err(e);
12497 }
12498 }
12499 }
12500 self.flush_computed_write_buffer(&mut computed_writes)?;
12501 Ok(layer.vertices.len())
12502 }
12503
12504 fn evaluate_layer_sequential_cancellable_demand_driven_effects(
12506 &mut self,
12507 layer: &super::scheduler::Layer,
12508 cancel_flag: &AtomicBool,
12509 ) -> Result<usize, ExcelError> {
12510 if layer.vertices.len() < COMPUTED_WRITE_COALESCING_MIN_LAYER_WIDTH {
12511 return self.evaluate_small_layer_direct_effects(
12512 layer,
12513 None,
12514 None,
12515 Some(cancel_flag),
12516 128,
12517 "Demand-driven evaluation cancelled within layer",
12518 );
12519 }
12520
12521 let mut computed_writes = ComputedWriteBuffer::default();
12522 for (i, &vertex_id) in layer.vertices.iter().enumerate() {
12523 if i % 128 == 0 && cancel_flag.load(Ordering::Relaxed) {
12524 self.flush_computed_write_buffer(&mut computed_writes)?;
12525 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
12526 .with_message("Demand-driven evaluation cancelled within layer".to_string()));
12527 }
12528 self.flush_before_range_dependent_vertex(vertex_id, &mut computed_writes)?;
12529 let value = match self.evaluate_vertex_immutable(vertex_id) {
12530 Ok(v) => v,
12531 Err(e) => LiteralValue::Error(e),
12532 };
12533 let effects = match self.plan_vertex_effects_with_computed_flush(
12534 vertex_id,
12535 value,
12536 None,
12537 &mut computed_writes,
12538 ) {
12539 Ok(effects) => effects,
12540 Err(e) => {
12541 self.flush_computed_write_buffer(&mut computed_writes)?;
12542 return Err(e);
12543 }
12544 };
12545 for effect in &effects {
12546 if let Err(e) = self.apply_effect_with_computed_writes(
12547 effect,
12548 None,
12549 None,
12550 Some(&mut computed_writes),
12551 ) {
12552 self.flush_computed_write_buffer(&mut computed_writes)?;
12553 return Err(e);
12554 }
12555 }
12556 }
12557 self.flush_computed_write_buffer(&mut computed_writes)?;
12558 Ok(layer.vertices.len())
12559 }
12560
12561 fn evaluate_layer_parallel_effects(
12563 &mut self,
12564 layer: &super::scheduler::Layer,
12565 ) -> Result<usize, ExcelError> {
12566 use rayon::prelude::*;
12567
12568 let thread_pool = self.thread_pool.as_ref().unwrap().clone();
12569
12570 let mut phase1: Vec<VertexId> = Vec::new();
12571 let mut phase2: Vec<VertexId> = Vec::new();
12572 for &vid in &layer.vertices {
12573 if self.graph.get_range_dependencies(vid).is_some() {
12574 phase2.push(vid);
12575 } else {
12576 phase1.push(vid);
12577 }
12578 }
12579
12580 let inflight: rustc_hash::FxHashSet<VertexId> = layer.vertices.iter().copied().collect();
12581 let mut applied = 0usize;
12582
12583 for group in [&phase1[..], &phase2[..]] {
12584 if group.is_empty() {
12585 continue;
12586 }
12587 let mut computed_writes = ComputedWriteBuffer::default();
12588
12589 let results: Result<Vec<(VertexId, LiteralValue)>, ExcelError> =
12590 thread_pool.install(|| {
12591 group
12592 .par_iter()
12593 .map(
12594 |&vertex_id| match self.evaluate_vertex_immutable(vertex_id) {
12595 Ok(v) => Ok((vertex_id, v)),
12596 Err(e) => Ok((vertex_id, LiteralValue::Error(e))),
12597 },
12598 )
12599 .collect()
12600 });
12601
12602 match results {
12603 Ok(vertex_results) => {
12604 let mut arrays: Vec<(VertexId, LiteralValue)> = Vec::new();
12607 let mut others: Vec<(VertexId, LiteralValue)> = Vec::new();
12608 for (vertex_id, result) in vertex_results {
12609 if matches!(result, LiteralValue::Array(_)) {
12610 arrays.push((vertex_id, result));
12611 } else {
12612 others.push((vertex_id, result));
12613 }
12614 }
12615 for (vertex_id, result) in arrays {
12616 let effects = match self.plan_vertex_effects_with_computed_flush(
12617 vertex_id,
12618 result,
12619 Some(&inflight),
12620 &mut computed_writes,
12621 ) {
12622 Ok(effects) => effects,
12623 Err(e) => {
12624 self.flush_computed_write_buffer(&mut computed_writes)?;
12625 return Err(e);
12626 }
12627 };
12628 for effect in &effects {
12629 if let Err(e) = self.apply_effect_with_computed_writes(
12630 effect,
12631 None,
12632 None,
12633 Some(&mut computed_writes),
12634 ) {
12635 self.flush_computed_write_buffer(&mut computed_writes)?;
12636 return Err(e);
12637 }
12638 }
12639 applied = applied.saturating_add(1);
12640 }
12641 self.flush_computed_write_buffer(&mut computed_writes)?;
12643 for (vertex_id, result) in others {
12644 let effects = match self.plan_vertex_effects_with_computed_flush(
12645 vertex_id,
12646 result,
12647 Some(&inflight),
12648 &mut computed_writes,
12649 ) {
12650 Ok(effects) => effects,
12651 Err(e) => {
12652 self.flush_computed_write_buffer(&mut computed_writes)?;
12653 return Err(e);
12654 }
12655 };
12656 for effect in &effects {
12657 if let Err(e) = self.apply_effect_with_computed_writes(
12658 effect,
12659 None,
12660 None,
12661 Some(&mut computed_writes),
12662 ) {
12663 self.flush_computed_write_buffer(&mut computed_writes)?;
12664 return Err(e);
12665 }
12666 }
12667 applied = applied.saturating_add(1);
12668 }
12669 self.flush_computed_write_buffer(&mut computed_writes)?;
12671 }
12672 Err(e) => {
12673 self.flush_computed_write_buffer(&mut computed_writes)?;
12674 return Err(e);
12675 }
12676 }
12677 }
12678
12679 Ok(applied)
12680 }
12681
12682 fn evaluate_layer_parallel_with_delta_effects(
12684 &mut self,
12685 layer: &super::scheduler::Layer,
12686 delta: &mut DeltaCollector,
12687 ) -> Result<usize, ExcelError> {
12688 use rayon::prelude::*;
12689
12690 let thread_pool = self.thread_pool.as_ref().unwrap().clone();
12691
12692 let mut phase1: Vec<VertexId> = Vec::new();
12693 let mut phase2: Vec<VertexId> = Vec::new();
12694 for &vid in &layer.vertices {
12695 if self.graph.get_range_dependencies(vid).is_some() {
12696 phase2.push(vid);
12697 } else {
12698 phase1.push(vid);
12699 }
12700 }
12701
12702 let inflight: rustc_hash::FxHashSet<VertexId> = layer.vertices.iter().copied().collect();
12703 let mut applied = 0usize;
12704
12705 for group in [&phase1[..], &phase2[..]] {
12706 if group.is_empty() {
12707 continue;
12708 }
12709 let mut computed_writes = ComputedWriteBuffer::default();
12710 let results: Result<Vec<(VertexId, LiteralValue)>, ExcelError> =
12711 thread_pool.install(|| {
12712 group
12713 .par_iter()
12714 .map(
12715 |&vertex_id| match self.evaluate_vertex_immutable(vertex_id) {
12716 Ok(v) => Ok((vertex_id, v)),
12717 Err(e) => Ok((vertex_id, LiteralValue::Error(e))),
12718 },
12719 )
12720 .collect()
12721 });
12722
12723 match results {
12724 Ok(vertex_results) => {
12725 let mut arrays: Vec<(VertexId, LiteralValue)> = Vec::new();
12726 let mut others: Vec<(VertexId, LiteralValue)> = Vec::new();
12727 for (vertex_id, result) in vertex_results {
12728 if matches!(result, LiteralValue::Array(_)) {
12729 arrays.push((vertex_id, result));
12730 } else {
12731 others.push((vertex_id, result));
12732 }
12733 }
12734 for (vertex_id, result) in arrays {
12735 let effects = match self.plan_vertex_effects_with_computed_flush(
12736 vertex_id,
12737 result,
12738 Some(&inflight),
12739 &mut computed_writes,
12740 ) {
12741 Ok(effects) => effects,
12742 Err(e) => {
12743 self.flush_computed_write_buffer(&mut computed_writes)?;
12744 return Err(e);
12745 }
12746 };
12747 for effect in &effects {
12748 if let Err(e) = self.apply_effect_with_computed_writes(
12749 effect,
12750 Some(delta),
12751 None,
12752 Some(&mut computed_writes),
12753 ) {
12754 self.flush_computed_write_buffer(&mut computed_writes)?;
12755 return Err(e);
12756 }
12757 }
12758 applied = applied.saturating_add(1);
12759 }
12760 self.flush_computed_write_buffer(&mut computed_writes)?;
12761 for (vertex_id, result) in others {
12762 let effects = match self.plan_vertex_effects_with_computed_flush(
12763 vertex_id,
12764 result,
12765 Some(&inflight),
12766 &mut computed_writes,
12767 ) {
12768 Ok(effects) => effects,
12769 Err(e) => {
12770 self.flush_computed_write_buffer(&mut computed_writes)?;
12771 return Err(e);
12772 }
12773 };
12774 for effect in &effects {
12775 if let Err(e) = self.apply_effect_with_computed_writes(
12776 effect,
12777 Some(delta),
12778 None,
12779 Some(&mut computed_writes),
12780 ) {
12781 self.flush_computed_write_buffer(&mut computed_writes)?;
12782 return Err(e);
12783 }
12784 }
12785 applied = applied.saturating_add(1);
12786 }
12787 self.flush_computed_write_buffer(&mut computed_writes)?;
12788 }
12789 Err(e) => {
12790 self.flush_computed_write_buffer(&mut computed_writes)?;
12791 return Err(e);
12792 }
12793 }
12794 }
12795
12796 Ok(applied)
12797 }
12798
12799 fn evaluate_layer_parallel_cancellable_effects(
12801 &mut self,
12802 layer: &super::scheduler::Layer,
12803 cancel_flag: &AtomicBool,
12804 ) -> Result<usize, ExcelError> {
12805 use rayon::prelude::*;
12806
12807 let thread_pool = self.thread_pool.as_ref().unwrap().clone();
12808
12809 if cancel_flag.load(Ordering::Relaxed) {
12810 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
12811 .with_message("Parallel evaluation cancelled before starting".to_string()));
12812 }
12813
12814 let mut phase1: Vec<VertexId> = Vec::new();
12815 let mut phase2: Vec<VertexId> = Vec::new();
12816 for &vid in &layer.vertices {
12817 if self.graph.get_range_dependencies(vid).is_some() {
12818 phase2.push(vid);
12819 } else {
12820 phase1.push(vid);
12821 }
12822 }
12823
12824 let inflight: rustc_hash::FxHashSet<VertexId> = layer.vertices.iter().copied().collect();
12825 let mut applied = 0usize;
12826
12827 for group in [&phase1[..], &phase2[..]] {
12828 if group.is_empty() {
12829 continue;
12830 }
12831 let mut computed_writes = ComputedWriteBuffer::default();
12832
12833 let results: Result<Vec<(VertexId, LiteralValue)>, ExcelError> =
12834 thread_pool.install(|| {
12835 group
12836 .par_iter()
12837 .map(|&vertex_id| {
12838 if cancel_flag.load(Ordering::Relaxed) {
12839 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
12840 .with_message(
12841 "Parallel evaluation cancelled during execution"
12842 .to_string(),
12843 ));
12844 }
12845 match self.evaluate_vertex_immutable(vertex_id) {
12846 Ok(v) => Ok((vertex_id, v)),
12847 Err(e) => Ok((vertex_id, LiteralValue::Error(e))),
12848 }
12849 })
12850 .collect()
12851 });
12852
12853 match results {
12854 Ok(vertex_results) => {
12855 let mut arrays: Vec<(VertexId, LiteralValue)> = Vec::new();
12856 let mut others: Vec<(VertexId, LiteralValue)> = Vec::new();
12857 for (vertex_id, result) in vertex_results {
12858 if matches!(result, LiteralValue::Array(_)) {
12859 arrays.push((vertex_id, result));
12860 } else {
12861 others.push((vertex_id, result));
12862 }
12863 }
12864 for (vertex_id, result) in arrays {
12865 let effects = match self.plan_vertex_effects_with_computed_flush(
12866 vertex_id,
12867 result,
12868 Some(&inflight),
12869 &mut computed_writes,
12870 ) {
12871 Ok(effects) => effects,
12872 Err(e) => {
12873 self.flush_computed_write_buffer(&mut computed_writes)?;
12874 return Err(e);
12875 }
12876 };
12877 for effect in &effects {
12878 if let Err(e) = self.apply_effect_with_computed_writes(
12879 effect,
12880 None,
12881 None,
12882 Some(&mut computed_writes),
12883 ) {
12884 self.flush_computed_write_buffer(&mut computed_writes)?;
12885 return Err(e);
12886 }
12887 }
12888 applied = applied.saturating_add(1);
12889 }
12890 self.flush_computed_write_buffer(&mut computed_writes)?;
12891 for (vertex_id, result) in others {
12892 let effects = match self.plan_vertex_effects_with_computed_flush(
12893 vertex_id,
12894 result,
12895 Some(&inflight),
12896 &mut computed_writes,
12897 ) {
12898 Ok(effects) => effects,
12899 Err(e) => {
12900 self.flush_computed_write_buffer(&mut computed_writes)?;
12901 return Err(e);
12902 }
12903 };
12904 for effect in &effects {
12905 if let Err(e) = self.apply_effect_with_computed_writes(
12906 effect,
12907 None,
12908 None,
12909 Some(&mut computed_writes),
12910 ) {
12911 self.flush_computed_write_buffer(&mut computed_writes)?;
12912 return Err(e);
12913 }
12914 }
12915 applied = applied.saturating_add(1);
12916 }
12917 self.flush_computed_write_buffer(&mut computed_writes)?;
12918 }
12919 Err(e) => {
12920 self.flush_computed_write_buffer(&mut computed_writes)?;
12921 return Err(e);
12922 }
12923 }
12924 }
12925
12926 Ok(applied)
12927 }
12928
12929 pub fn evaluate_all_logged(&mut self, log: &mut ChangeLog) -> Result<EvalResult, ExcelError> {
12936 let _source_cache = self.source_cache_session();
12937 self.validate_deterministic_mode()?;
12938 if self.config.defer_graph_building {
12939 self.build_graph_all()?;
12940 }
12941 if self.graph.formula_authority().active_span_count() > 0 {
12942 return self.evaluate_authoritative_formula_plane_all();
12943 }
12944 self.reset_virtual_dep_telemetry_if_disabled();
12945 let start = crate::instant::FzInstant::now();
12946 let mut computed_vertices = 0;
12947 let mut cycle_errors = 0;
12948
12949 let mut replan_iterations = 0;
12950 const MAX_REPLAN: usize = 5;
12951 let mut telemetry = self
12952 .config
12953 .enable_virtual_dep_telemetry
12954 .then(|| self.start_virtual_dep_telemetry());
12955
12956 log.begin_compound(format!("evaluate_all(epoch={})", self.recalc_epoch));
12957
12958 loop {
12959 let to_evaluate = self.graph.get_evaluation_vertices();
12960 if to_evaluate.is_empty() {
12961 if let Some(t) = telemetry.as_mut()
12962 && t.bailout_reason.is_none()
12963 {
12964 t.bailout_reason = Some("no_work");
12965 }
12966 break;
12967 }
12968
12969 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
12970 if let Some(t) = telemetry.as_mut() {
12971 Self::accumulate_schedule_meta(t, &meta);
12972 }
12973
12974 let circ_error = LiteralValue::Error(
12976 ExcelError::new(ExcelErrorKind::Circ)
12977 .with_message("Circular dependency detected".to_string()),
12978 );
12979 for cycle in &schedule.cycles {
12980 cycle_errors += 1;
12981 for &vertex_id in cycle {
12982 self.graph
12983 .update_vertex_value(vertex_id, circ_error.clone());
12984 self.mirror_vertex_value_to_overlay(vertex_id, &circ_error);
12985 }
12986 }
12987
12988 for layer in &schedule.layers {
12990 computed_vertices += self.evaluate_layer_logged(layer, log)?;
12991 }
12992
12993 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
12994 if let Some(t) = telemetry.as_mut() {
12995 t.changed_vdeps_total += changed_vertices.len();
12996 }
12997 self.graph.clear_dirty_flags(&to_evaluate);
12998 for v in &changed_vertices {
12999 self.graph.set_dirty(*v, true);
13000 }
13001
13002 if changed_vertices.is_empty() {
13003 if let Some(t) = telemetry.as_mut() {
13004 t.bailout_reason = Some("converged");
13005 }
13006 break;
13007 }
13008 if replan_iterations >= MAX_REPLAN {
13009 if let Some(t) = telemetry.as_mut() {
13010 t.bailout_reason = Some("max_replan");
13011 }
13012 break;
13013 }
13014 replan_iterations += 1;
13015 }
13016
13017 if let Some(mut t) = telemetry {
13018 t.replan_iterations = replan_iterations;
13019 self.last_virtual_dep_telemetry = t;
13020 }
13021
13022 log.end_compound();
13023
13024 self.graph.redirty_volatiles();
13025 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
13026
13027 Ok(EvalResult {
13028 computed_vertices,
13029 cycle_errors,
13030 elapsed: start.elapsed(),
13031 })
13032 }
13033
13034 fn evaluate_layer_logged(
13036 &mut self,
13037 layer: &super::scheduler::Layer,
13038 log: &mut ChangeLog,
13039 ) -> Result<usize, ExcelError> {
13040 let mut computed_writes = ComputedWriteBuffer::default();
13041 for &vertex_id in &layer.vertices {
13042 self.flush_before_range_dependent_vertex(vertex_id, &mut computed_writes)?;
13043 let value = match self.evaluate_vertex_immutable(vertex_id) {
13044 Ok(v) => v,
13045 Err(e) => LiteralValue::Error(e),
13046 };
13047 let effects = match self.plan_vertex_effects_with_computed_flush(
13048 vertex_id,
13049 value,
13050 None,
13051 &mut computed_writes,
13052 ) {
13053 Ok(effects) => effects,
13054 Err(e) => {
13055 self.flush_computed_write_buffer(&mut computed_writes)?;
13056 return Err(e);
13057 }
13058 };
13059 for effect in &effects {
13060 if let Err(e) = self.apply_effect_with_computed_writes(
13061 effect,
13062 None,
13063 Some(log),
13064 Some(&mut computed_writes),
13065 ) {
13066 self.flush_computed_write_buffer(&mut computed_writes)?;
13067 return Err(e);
13068 }
13069 }
13070 }
13071 self.flush_computed_write_buffer(&mut computed_writes)?;
13072 Ok(layer.vertices.len())
13073 }
13074}