1use crate::SheetId;
2use crate::engine::TombstoneRegistry;
3use crate::engine::named_range::{NameScope, NamedDefinition, NamedRange};
4use crate::engine::sheet_registry::SheetRegistry;
5use formualizer_common::{
6 CoordBuildHasher, ExcelError, ExcelErrorKind, LiteralValue, PackedSheetCell,
7};
8use formualizer_parse::parser::{ASTNode, ASTNodeType, ReferenceType};
9use rustc_hash::{FxHashMap, FxHashSet};
10
11#[cfg(debug_assertions)]
12use std::sync::atomic::{AtomicU64, Ordering};
13
14#[cfg(test)]
15#[derive(Debug, Default, Clone)]
16pub struct GraphInstrumentation {
17 pub edges_added: u64,
18 pub stripe_inserts: u64,
19 pub stripe_removes: u64,
20 pub dependents_scan_fallback_calls: u64,
21 pub dependents_scan_vertices_scanned: u64,
22}
23
24mod ast_utils;
25pub(crate) mod authority_host;
26pub mod editor;
27mod extent_record;
28mod formula_analysis;
29#[cfg(test)]
30mod formula_analysis_legacy_tests;
31mod formula_dirty;
32mod names;
33pub(crate) mod prepared_legacy_graph;
34mod range_deps;
35pub(crate) use range_deps::{StructuralEdit, StructuralOccupancy};
36
37mod sheets;
38pub mod snapshot;
39mod sources;
40mod structural_runs;
41mod tables;
42pub(crate) mod virtual_members;
43
44pub(crate) use structural_runs::ShiftedRun;
45pub(crate) use tables::TableEntry;
46
47use super::addr::{GridAddr, SymbolAddr, VertexAddr};
48use super::arena::{AstNodeId, DataStore, ValueRef};
49#[cfg(any(test, feature = "legacy_oracle"))]
50use super::delta_edges::CsrMutableEdges;
51use super::ingest_pipeline::{DependencyPlanRow, FormulaAstInput};
52use super::sheet_index::SheetIndex;
53use super::vertex::{VertexId, VertexKind};
54use super::vertex_store::{FIRST_NORMAL_VERTEX, VertexStore};
55#[cfg(any(test, feature = "legacy_oracle"))]
56use crate::engine::topo::{
57 GraphAdapter,
58 pk::{DynamicTopo, PkConfig},
59};
60use crate::reference::{CellRef, Coord, SharedRangeRef, SharedRef, SharedSheetLocator};
61use formualizer_common::Coord as AbsCoord;
62use formula_dirty::FormulaDirtyState;
63struct RegistryFunctionProvider;
66
67impl crate::traits::FunctionProvider for RegistryFunctionProvider {
68 fn planning_semantic_revision(&self) -> Option<u64> {
69 Some(0)
70 }
71
72 fn get_function(
73 &self,
74 ns: &str,
75 name: &str,
76 ) -> Option<std::sync::Arc<dyn crate::function::Function>> {
77 crate::function_registry::get(ns, name)
78 }
79
80 fn get_function_for_planning(
81 &self,
82 ns: &str,
83 name: &str,
84 ) -> Option<std::sync::Arc<dyn crate::function::Function>> {
85 crate::function_registry::get_for_planning(ns, name)
86 }
87}
88
89#[inline]
90fn normalize_stored_literal(value: LiteralValue) -> LiteralValue {
91 match value {
92 LiteralValue::Int(i) => LiteralValue::Number(i as f64),
94 other => other,
95 }
96}
97
98pub use editor::change_log::{ChangeEvent, ChangeLog};
99
100#[derive(Debug, Clone, PartialEq, Eq, Hash)]
104pub enum DependencyRef {
105 Cell(VertexId),
107 Range {
109 sheet: String,
110 start_row: u32,
111 start_col: u32,
112 end_row: u32, end_col: u32, },
115 WholeColumn { sheet: String, col: u32 },
117 WholeRow { sheet: String, row: u32 },
119}
120
121#[derive(Debug, Clone, Hash, PartialEq, Eq)]
123pub struct StripeKey {
124 pub sheet_id: SheetId,
125 pub stripe_type: StripeType,
126 pub index: u32, }
128
129#[derive(Debug, Clone, Hash, PartialEq, Eq)]
130pub enum StripeType {
131 Row,
132 Column,
133 Block, }
135
136const BLOCK_H: u32 = 256;
138const BLOCK_W: u32 = 256;
139
140pub fn block_index(row: u32, col: u32) -> u32 {
141 (row / BLOCK_H) << 16 | (col / BLOCK_W)
142}
143
144#[derive(Debug, Clone)]
147pub struct OperationSummary {
148 pub affected_vertices: Vec<VertexId>,
150 pub created_placeholders: Vec<CellRef>,
152}
153
154#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
159pub struct GraphBaselineStats {
160 pub graph_vertex_count: usize,
161 pub graph_formula_vertex_count: usize,
162 pub graph_edge_count: usize,
163 pub dirty_vertex_count: usize,
164 pub evaluation_vertex_count: usize,
165 pub formula_ast_root_count: usize,
166 pub formula_ast_node_count: usize,
167}
168
169#[derive(Clone, Copy, Debug, PartialEq, Eq)]
171pub(crate) enum FormulaRef {
172 Own(AstNodeId),
174 Member {
178 template: AstNodeId,
179 anchor: (u32, u32),
180 },
181}
182
183impl FormulaRef {
184 #[inline]
187 pub(crate) fn root(self) -> AstNodeId {
188 match self {
189 FormulaRef::Own(id) | FormulaRef::Member { template: id, .. } => id,
190 }
191 }
192
193 #[inline]
195 pub(crate) fn of_ingested(ast_id: AstNodeId, member_anchor: Option<(u32, u32)>) -> Self {
196 match member_anchor {
197 None => FormulaRef::Own(ast_id),
198 Some(anchor) => FormulaRef::Member {
199 template: ast_id,
200 anchor,
201 },
202 }
203 }
204
205 #[inline]
207 pub(crate) fn own(self) -> Option<AstNodeId> {
208 match self {
209 FormulaRef::Own(id) => Some(id),
210 FormulaRef::Member { .. } => None,
211 }
212 }
213}
214
215#[derive(Debug, Default)]
222pub(crate) struct FormulaMap {
223 map: FxHashMap<VertexId, FormulaRef>,
224 virt: virtual_members::VirtualMembers,
225 touched: Vec<VertexId>,
226}
227
228impl FormulaMap {
229 #[inline]
231 pub(crate) fn get(&self, vertex: &VertexId) -> Option<FormulaRef> {
232 match self.map.get(vertex) {
233 Some(&f) => Some(f),
234 None => self.virt.by_vertex(*vertex).map(|m| m.formula),
235 }
236 }
237
238 #[inline]
239 pub(crate) fn contains_key(&self, vertex: &VertexId) -> bool {
240 self.map.contains_key(vertex) || self.virt.contains_vertex(*vertex)
241 }
242
243 #[inline]
245 pub(crate) fn len(&self) -> usize {
246 self.map.len() + self.virt.len()
247 }
248
249 pub(crate) fn iter(&self) -> impl Iterator<Item = (VertexId, FormulaRef)> + '_ {
252 self.map
253 .iter()
254 .map(|(&v, &f)| (v, f))
255 .chain(self.virt.iter().map(|m| (m.vertex, m.formula)))
256 }
257
258 pub(crate) fn keys(&self) -> impl Iterator<Item = VertexId> + '_ {
259 self.iter().map(|(v, _)| v)
260 }
261
262 pub(crate) fn roots(&self) -> impl Iterator<Item = AstNodeId> + '_ {
266 self.map
267 .values()
268 .map(|f| f.root())
269 .chain(self.virt.runs().map(|r| r.template))
270 }
271
272 pub(crate) fn map_iter(&self) -> impl Iterator<Item = (VertexId, FormulaRef)> + '_ {
273 self.map.iter().map(|(&v, &f)| (v, f))
274 }
275
276 #[inline]
277 pub(crate) fn virtual_members(&self) -> &virtual_members::VirtualMembers {
278 &self.virt
279 }
280
281 #[inline]
282 pub(crate) fn virtual_members_mut(&mut self) -> &mut virtual_members::VirtualMembers {
283 &mut self.virt
284 }
285
286 #[inline]
289 pub(crate) fn materialize(
290 &mut self,
291 vertex: VertexId,
292 ) -> Option<virtual_members::VirtualMember> {
293 let m = self.virt.take(vertex)?;
294 self.map.insert(vertex, m.formula);
295 Some(m)
296 }
297
298 pub(crate) fn forget_materialized(&mut self, vertex: &VertexId) {
301 self.map.remove(vertex);
302 }
303
304 #[inline]
306 pub(crate) fn restore(&mut self, vertex: VertexId, formula: FormulaRef) {
307 self.map.insert(vertex, formula);
308 }
309
310 #[inline]
311 pub(crate) fn insert(&mut self, vertex: VertexId, ast: AstNodeId) -> Option<FormulaRef> {
312 self.insert_ref(vertex, FormulaRef::Own(ast))
313 }
314
315 #[inline]
317 pub(crate) fn insert_ref(
318 &mut self,
319 vertex: VertexId,
320 formula: FormulaRef,
321 ) -> Option<FormulaRef> {
322 debug_assert!(
323 !self.virt.contains_vertex(vertex),
324 "formula write to a virtual family member (materialize it first)"
325 );
326 self.touched.push(vertex);
327 self.map.insert(vertex, formula)
328 }
329
330 #[inline]
332 pub(crate) fn compress(&mut self, vertex: VertexId, template: AstNodeId, anchor: (u32, u32)) {
333 if let Some(slot) = self.map.get_mut(&vertex) {
334 *slot = FormulaRef::Member { template, anchor };
335 }
336 }
337
338 #[inline]
341 pub(crate) fn decompress(&mut self, vertex: VertexId, own: AstNodeId) {
342 debug_assert!(!self.virt.contains_vertex(vertex));
343 if let Some(slot) = self.map.get_mut(&vertex) {
344 *slot = FormulaRef::Own(own);
345 }
346 }
347
348 pub(crate) fn remap(&mut self, map: &impl Fn(AstNodeId) -> AstNodeId) {
350 for slot in self.map.values_mut() {
351 *slot = match *slot {
352 FormulaRef::Own(id) => FormulaRef::Own(map(id)),
353 FormulaRef::Member { template, anchor } => FormulaRef::Member {
354 template: map(template),
355 anchor,
356 },
357 };
358 }
359 self.virt.remap(map);
360 }
361
362 #[inline]
363 pub(crate) fn remove(&mut self, vertex: &VertexId) -> Option<FormulaRef> {
364 debug_assert!(
365 !self.virt.contains_vertex(*vertex),
366 "formula removal of a virtual family member (materialize it first)"
367 );
368 let old = self.map.remove(vertex);
369 if old.is_some() {
370 self.touched.push(*vertex);
371 }
372 old
373 }
374
375 #[inline]
376 pub(crate) fn reserve(&mut self, additional: usize) {
377 self.map.reserve(additional);
378 }
379
380 pub(crate) fn drop_virtual_from_map(&mut self, is_virtual: impl Fn(VertexId) -> bool) {
384 debug_assert!(
385 self.map
386 .keys()
387 .all(|&v| is_virtual(v) == self.virt.contains_vertex(v))
388 );
389 self.map.retain(|&v, _| !is_virtual(v));
390 self.map.shrink_to_fit();
391 }
392
393 pub(crate) fn map_capacity(&self) -> usize {
394 self.map.capacity()
395 }
396
397 pub(crate) fn take_touched(&mut self) -> Vec<VertexId> {
399 std::mem::take(&mut self.touched)
400 }
401
402 pub(crate) fn touch(&mut self, vertex: VertexId) {
405 self.touched.push(vertex);
406 }
407
408 pub(crate) fn touched(&self) -> &[VertexId] {
411 &self.touched
412 }
413
414 pub(crate) fn has_touched(&self) -> bool {
415 !self.touched.is_empty()
416 }
417}
418
419#[derive(Clone, Copy, Debug, PartialEq, Eq)]
427#[non_exhaustive]
428pub struct FormulaView {
429 pub template: AstNodeId,
430 pub row_delta: i64,
431 pub col_delta: i64,
432}
433
434#[derive(Debug)]
436pub struct DependencyGraph {
437 store: VertexStore,
439
440 #[cfg(any(test, feature = "legacy_oracle"))]
442 edges: CsrMutableEdges,
443 dep_edge_total: usize,
447 range_reader_count: usize,
449 renamed_sheet_aliases: FxHashMap<String, SheetId>,
453
454 data_store: DataStore,
456 vertex_values: FxHashMap<VertexId, ValueRef>,
457 vertex_formulas: FormulaMap,
458
459 value_cache_enabled: bool,
464
465 #[cfg(debug_assertions)]
468 graph_value_read_attempts: AtomicU64,
469
470 cell_to_vertex: std::collections::HashMap<CellRef, VertexId, CoordBuildHasher>,
474 load_packed_to_vertex: std::collections::HashMap<PackedSheetCell, VertexId, CoordBuildHasher>,
475
476 vertex_journal: crate::engine::authority::history::IdJournal,
479
480 formula_dirty: FormulaDirtyState,
483 volatile_vertices: FxHashSet<VertexId>,
484
485 dirty_propagation_visits: u64,
490
491 deferred_dirty_depth: u32,
497 deferred_dirty_pending: Vec<VertexId>,
499 retired_ids: std::collections::BTreeMap<(SheetId, u32, u32), VertexId>,
504 retired_id_set: FxHashSet<VertexId>,
507 retired_dropped: Vec<RetiredBatch>,
512 retired_dropped_by_undo: Vec<RetiredBatch>,
515 extent_record: extent_record::ExtentRecord,
518 extent_dropped: Vec<Vec<extent_record::ExtentRun>>,
523 extent_undone: Vec<(u8, SheetId, u32, u32)>,
527 deferred_dirty_pending_rects: Vec<(u16, crate::engine::authority::geom::Rect)>,
529
530 ref_error_vertices: FxHashSet<VertexId>,
536
537 #[cfg(any(test, feature = "legacy_oracle"))]
540 formula_to_range_deps: FxHashMap<VertexId, Vec<SharedRangeRef<'static>>>,
541
542 #[cfg(any(test, feature = "legacy_oracle"))]
545 stripe_to_dependents: FxHashMap<StripeKey, FxHashSet<VertexId>>,
546
547 sheet_indexes: FxHashMap<SheetId, SheetIndex>,
550
551 sheet_reg: SheetRegistry,
553 default_sheet_id: SheetId,
554
555 named_ranges: FxHashMap<String, NamedRange>,
558
559 named_ranges_lookup: FxHashMap<String, String>,
564
565 sheet_named_ranges: FxHashMap<(SheetId, String), NamedRange>,
567
568 sheet_named_ranges_lookup: FxHashMap<(SheetId, String), String>,
573
574 #[cfg(any(test, feature = "legacy_oracle"))]
576 vertex_to_names: FxHashMap<VertexId, Vec<VertexId>>,
577
578 name_vertex_lookup: FxHashMap<VertexId, (NameScope, String)>,
580
581 pending_name_links: FxHashMap<String, FxHashSet<(SheetId, VertexId)>>,
586
587 vertex_to_pending_names: FxHashMap<VertexId, FxHashSet<String>>,
590
591 tables: FxHashMap<String, tables::TableEntry>,
593 tables_lookup: FxHashMap<String, String>,
595 table_vertex_lookup: FxHashMap<VertexId, String>,
596
597 source_scalars: FxHashMap<String, sources::SourceScalarEntry>,
599 source_tables: FxHashMap<String, sources::SourceTableEntry>,
600 source_vertex_lookup: FxHashMap<VertexId, String>,
601
602 symbol_vertex_seq: u32,
608
609 #[cfg(any(test, feature = "legacy_oracle"))]
611 cell_to_name_dependents: FxHashMap<VertexId, FxHashSet<VertexId>>,
612 #[cfg(any(test, feature = "legacy_oracle"))]
614 name_to_cell_dependencies: FxHashMap<VertexId, Vec<VertexId>>,
615 #[cfg(any(test, feature = "legacy_oracle"))]
619 oracle_vertexless_readers: FxHashMap<(SheetId, u32, u32), Vec<VertexId>>,
620 #[cfg(any(test, feature = "legacy_oracle"))]
621 oracle_vertexless_of: FxHashMap<VertexId, Vec<(SheetId, u32, u32)>>,
622
623 config: super::EvalConfig,
625 topology_revision: u64,
627 symbol_revision: u64,
629
630 authority: crate::engine::authority::host::AuthorityHost,
633
634 #[cfg(any(test, feature = "legacy_oracle"))]
636 pk_order: Option<DynamicTopo<VertexId>>,
637
638 spill_anchor_to_cells: FxHashMap<VertexId, Vec<CellRef>>,
642 pub(crate) fixed_single_arrays: FxHashSet<VertexId>,
644 pub(crate) fixed_array_shapes: FxHashMap<VertexId, (u32, u32)>,
645 spill_cell_to_anchor: std::collections::HashMap<CellRef, VertexId, CoordBuildHasher>,
646 spill_cells_by_sheet: FxHashMap<SheetId, std::collections::BTreeMap<(u32, u32), VertexId>>,
647 spill_intruded_anchors: FxHashSet<VertexId>,
658
659 declared_dynamic_anchors: FxHashMap<VertexId, CellRef>,
665
666 admission_budget_override: Option<crate::engine::EvaluationBudgets>,
668
669 first_load_assume_new: bool,
671 ensure_touched_sheets: FxHashSet<SheetId>,
672
673 pub tombstone_registry: TombstoneRegistry,
675
676 #[cfg(test)]
677 instr: std::sync::Mutex<GraphInstrumentation>,
678 #[cfg(test)]
679 prepared_legacy_graph_failure_for_test: bool,
680}
681
682impl Default for DependencyGraph {
683 fn default() -> Self {
684 Self::new()
685 }
686}
687
688impl DependencyGraph {
689 pub fn range_expansion_limit(&self) -> usize {
691 self.config.range_expansion_limit
692 }
693
694 pub fn get_config(&self) -> &super::EvalConfig {
695 &self.config
696 }
697
698 pub(crate) fn formula_vertex_count(&self) -> usize {
700 self.vertex_formulas.len()
701 }
702
703 pub(crate) fn clear_formula_vertex_dirty(&mut self, vertex_id: VertexId) {
704 self.store.set_dirty(vertex_id, false);
705 self.formula_dirty.legacy_remove(&vertex_id);
706 }
707
708 pub fn baseline_stats(&self) -> GraphBaselineStats {
710 let data_stats = self.data_store.memory_usage();
711 GraphBaselineStats {
712 graph_vertex_count: self.store.len(),
713 graph_formula_vertex_count: self.vertex_formulas.len(),
714 graph_edge_count: self.dep_edge_total,
715 dirty_vertex_count: self.formula_dirty.legacy_len(),
716 evaluation_vertex_count: self.get_evaluation_vertices().len(),
717 formula_ast_root_count: self.vertex_formulas.len(),
718 formula_ast_node_count: data_stats.total_ast_nodes,
719 }
720 }
721
722 #[inline]
723 pub(crate) fn value_cache_enabled(&self) -> bool {
724 self.value_cache_enabled
725 }
726
727 #[cfg(test)]
731 pub fn debug_graph_value_read_attempts(&self) -> u64 {
732 #[cfg(debug_assertions)]
733 {
734 self.graph_value_read_attempts.load(Ordering::Relaxed)
735 }
736 #[cfg(not(debug_assertions))]
737 {
738 0
739 }
740 }
741
742 pub fn plan_dependencies<'a, I>(
744 &mut self,
745 items: I,
746 policy: &formualizer_parse::parser::CollectPolicy,
747 volatile: Option<&[bool]>,
748 ) -> Result<crate::engine::plan::DependencyPlan, formualizer_common::ExcelError>
749 where
750 I: IntoIterator<Item = (&'a str, u32, u32, &'a formualizer_parse::parser::ASTNode)>,
751 {
752 crate::engine::plan::build_dependency_plan(
753 &mut self.sheet_reg,
754 items.into_iter(),
755 policy,
756 volatile,
757 )
758 }
759
760 pub fn plan_dependencies_mixed<'a, I>(
761 &mut self,
762 items: I,
763 policy: &formualizer_parse::parser::CollectPolicy,
764 volatile: Option<&[bool]>,
765 ) -> Result<crate::engine::plan::DependencyPlan, formualizer_common::ExcelError>
766 where
767 I: IntoIterator<
768 Item = (
769 &'a str,
770 u32,
771 u32,
772 crate::engine::plan::DependencyPlanAst<'a>,
773 ),
774 >,
775 {
776 crate::engine::plan::build_dependency_plan_mixed(
777 &mut self.sheet_reg,
778 &self.data_store,
779 items.into_iter(),
780 policy,
781 volatile,
782 )
783 }
784
785 pub fn ensure_vertices_batch(
788 &mut self,
789 coords: &[(SheetId, AbsCoord)],
790 ) -> Vec<(VertexAddr, u32)> {
791 self.ensure_vertices_batch_ordered(coords).1
792 }
793
794 pub fn ensure_vertices_batch_packed_ordered(
798 &mut self,
799 packed_cells: &[PackedSheetCell],
800 ) -> (Vec<VertexId>, Vec<(VertexAddr, u32)>) {
801 let mut unmapped = vec![false; packed_cells.len()];
802 self.ensure_vertices_batch_packed_ordered_unmapped(packed_cells, &mut unmapped)
803 }
804
805 pub(crate) fn ensure_vertices_batch_packed_ordered_unmapped(
812 &mut self,
813 packed_cells: &[PackedSheetCell],
814 unmapped: &mut [bool],
815 ) -> (Vec<VertexId>, Vec<(VertexAddr, u32)>) {
816 debug_assert_eq!(unmapped.len(), packed_cells.len());
817 #[cfg(feature = "perf_instrumentation")]
818 use crate::instant::FzInstant as PerfInstant;
819 use rustc_hash::FxHashMap;
820
821 #[cfg(feature = "perf_instrumentation")]
822 let debug = std::env::var("FZ_DEBUG_LOAD")
823 .ok()
824 .is_some_and(|v| v != "0");
825 #[cfg(feature = "perf_instrumentation")]
826 let t0 = PerfInstant::now();
827
828 let mut ordered: Vec<Option<VertexId>> = vec![None; packed_cells.len()];
829 if packed_cells.is_empty() {
830 return (Vec::new(), Vec::new());
831 }
832
833 let first_sid = packed_cells[0].sheet_id();
834 let single_sheet = packed_cells.iter().all(|cell| cell.sheet_id() == first_sid);
835 let mut add_batch: Vec<(VertexAddr, u32)> = Vec::new();
836
837 #[cfg(feature = "perf_instrumentation")]
838 let mut packed_hits = 0usize;
839 #[cfg(feature = "perf_instrumentation")]
840 let mut generic_hits = 0usize;
841 #[cfg(feature = "perf_instrumentation")]
842 let mut missing = 0usize;
843 #[cfg(feature = "perf_instrumentation")]
844 let mut t_packed_lookup_us = 0u128;
845 #[cfg(feature = "perf_instrumentation")]
846 let mut t_generic_lookup_us = 0u128;
847 #[cfg(feature = "perf_instrumentation")]
848 let mut t_alloc_us = 0u128;
849 #[cfg(feature = "perf_instrumentation")]
850 let mut t_map_insert_us = 0u128;
851 #[cfg(feature = "perf_instrumentation")]
852 let mut t_index_insert_us = 0u128;
853 #[cfg(feature = "perf_instrumentation")]
854 let mut t_edge_register_us = 0u128;
855
856 if single_sheet {
857 let sid = first_sid;
858 let mut missing_items: Vec<(usize, PackedSheetCell)> =
859 Vec::with_capacity(packed_cells.len());
860
861 for (idx, packed) in packed_cells.iter().copied().enumerate() {
862 #[cfg(feature = "perf_instrumentation")]
863 let tl0 = PerfInstant::now();
864 if self.first_load_assume_new
865 && let Some(&existing) = self.load_packed_to_vertex.get(&packed)
866 {
867 ordered[idx] = Some(existing);
868 unmapped[idx] = false;
869 #[cfg(feature = "perf_instrumentation")]
870 {
871 packed_hits += 1;
872 t_packed_lookup_us += tl0.elapsed().as_micros();
873 }
874 continue;
875 }
876 #[cfg(feature = "perf_instrumentation")]
877 {
878 t_packed_lookup_us += tl0.elapsed().as_micros();
879 }
880
881 let pc = AbsCoord::new(packed.row0(), packed.col0());
882 let addr = CellRef::new(sid, Coord::new(pc.row(), pc.col(), true, true));
883 #[cfg(feature = "perf_instrumentation")]
884 let tg0 = PerfInstant::now();
885 if let Some(existing) = self.cell_vertex(&addr) {
886 ordered[idx] = Some(existing);
887 unmapped[idx] = false;
888 if self.first_load_assume_new && !self.is_virtual_member(existing) {
890 self.load_packed_to_vertex.insert(packed, existing);
891 }
892 #[cfg(feature = "perf_instrumentation")]
893 {
894 generic_hits += 1;
895 }
896 } else {
897 missing_items.push((idx, packed));
898 #[cfg(feature = "perf_instrumentation")]
899 {
900 missing += 1;
901 }
902 }
903 #[cfg(feature = "perf_instrumentation")]
904 {
905 t_generic_lookup_us += tg0.elapsed().as_micros();
906 }
907 }
908
909 if !missing_items.is_empty() {
910 self.ensure_touched_sheets.insert(sid);
911
912 let mut pcs: Vec<VertexAddr> = Vec::with_capacity(missing_items.len());
913 for (_, packed) in &missing_items {
914 pcs.push(GridAddr::new(packed.row0(), packed.col0()).into());
915 }
916
917 #[cfg(feature = "perf_instrumentation")]
918 let ta0 = PerfInstant::now();
919 let vids = self.store.allocate_contiguous(sid, &pcs, 0x00);
920 #[cfg(feature = "perf_instrumentation")]
921 {
922 t_alloc_us += ta0.elapsed().as_micros();
923 }
924 add_batch.reserve(missing_items.len());
925
926 match self.config.sheet_index_mode {
927 crate::engine::SheetIndexMode::Eager
928 | crate::engine::SheetIndexMode::FastBatch => {
929 for ((input_idx, packed), vid) in missing_items.into_iter().zip(vids) {
930 let pc = AbsCoord::new(packed.row0(), packed.col0());
931 ordered[input_idx] = Some(vid);
932 add_batch.push((VertexAddr::grid(GridAddr::from_coord(pc)), vid.0));
933 if unmapped[input_idx] {
934 continue;
935 }
936
937 #[cfg(feature = "perf_instrumentation")]
938 let tm0 = PerfInstant::now();
939 if self.first_load_assume_new {
940 self.load_packed_to_vertex.insert(packed, vid);
941 } else {
942 let addr =
943 CellRef::new(sid, Coord::new(pc.row(), pc.col(), true, true));
944 self.cell_to_vertex.insert(addr, vid);
945 }
946 #[cfg(feature = "perf_instrumentation")]
947 {
948 t_map_insert_us += tm0.elapsed().as_micros();
949 }
950
951 #[cfg(feature = "perf_instrumentation")]
952 let ti0 = PerfInstant::now();
953 self.sheet_index_mut(sid)
954 .add_vertex(GridAddr::from_coord(pc), vid);
955 #[cfg(feature = "perf_instrumentation")]
956 {
957 t_index_insert_us += ti0.elapsed().as_micros();
958 }
959 }
960 }
961 crate::engine::SheetIndexMode::Lazy => {
962 for ((input_idx, packed), vid) in missing_items.into_iter().zip(vids) {
963 let pc = AbsCoord::new(packed.row0(), packed.col0());
964 ordered[input_idx] = Some(vid);
965 add_batch.push((VertexAddr::grid(GridAddr::from_coord(pc)), vid.0));
966 if unmapped[input_idx] {
967 continue;
968 }
969
970 #[cfg(feature = "perf_instrumentation")]
971 let tm0 = PerfInstant::now();
972 if self.first_load_assume_new {
973 self.load_packed_to_vertex.insert(packed, vid);
974 } else {
975 let addr =
976 CellRef::new(sid, Coord::new(pc.row(), pc.col(), true, true));
977 self.cell_to_vertex.insert(addr, vid);
978 }
979 #[cfg(feature = "perf_instrumentation")]
980 {
981 t_map_insert_us += tm0.elapsed().as_micros();
982 }
983 }
984 }
985 }
986 }
987 } else {
988 let mut grouped: FxHashMap<SheetId, Vec<(usize, PackedSheetCell)>> =
989 FxHashMap::default();
990
991 for (idx, packed) in packed_cells.iter().copied().enumerate() {
992 #[cfg(feature = "perf_instrumentation")]
993 let tl0 = PerfInstant::now();
994 if self.first_load_assume_new
995 && let Some(&existing) = self.load_packed_to_vertex.get(&packed)
996 {
997 ordered[idx] = Some(existing);
998 unmapped[idx] = false;
999 #[cfg(feature = "perf_instrumentation")]
1000 {
1001 packed_hits += 1;
1002 t_packed_lookup_us += tl0.elapsed().as_micros();
1003 }
1004 continue;
1005 }
1006 #[cfg(feature = "perf_instrumentation")]
1007 {
1008 t_packed_lookup_us += tl0.elapsed().as_micros();
1009 }
1010
1011 let sid = packed.sheet_id();
1012 let pc = AbsCoord::new(packed.row0(), packed.col0());
1013 let addr = CellRef::new(sid, Coord::new(pc.row(), pc.col(), true, true));
1014 #[cfg(feature = "perf_instrumentation")]
1015 let tg0 = PerfInstant::now();
1016 if let Some(existing) = self.cell_vertex(&addr) {
1017 ordered[idx] = Some(existing);
1018 unmapped[idx] = false;
1019 if self.first_load_assume_new && !self.is_virtual_member(existing) {
1021 self.load_packed_to_vertex.insert(packed, existing);
1022 }
1023 #[cfg(feature = "perf_instrumentation")]
1024 {
1025 generic_hits += 1;
1026 }
1027 } else {
1028 grouped.entry(sid).or_default().push((idx, packed));
1029 #[cfg(feature = "perf_instrumentation")]
1030 {
1031 missing += 1;
1032 }
1033 }
1034 #[cfg(feature = "perf_instrumentation")]
1035 {
1036 t_generic_lookup_us += tg0.elapsed().as_micros();
1037 }
1038 }
1039
1040 for (sid, items) in grouped {
1041 if items.is_empty() {
1042 continue;
1043 }
1044 self.ensure_touched_sheets.insert(sid);
1045
1046 let mut pcs: Vec<VertexAddr> = Vec::with_capacity(items.len());
1047 for (_, packed) in &items {
1048 pcs.push(GridAddr::new(packed.row0(), packed.col0()).into());
1049 }
1050
1051 #[cfg(feature = "perf_instrumentation")]
1052 let ta0 = PerfInstant::now();
1053 let vids = self.store.allocate_contiguous(sid, &pcs, 0x00);
1054 #[cfg(feature = "perf_instrumentation")]
1055 {
1056 t_alloc_us += ta0.elapsed().as_micros();
1057 }
1058
1059 for ((input_idx, packed), vid) in items.into_iter().zip(vids) {
1060 let pc = AbsCoord::new(packed.row0(), packed.col0());
1061 ordered[input_idx] = Some(vid);
1062 add_batch.push((VertexAddr::grid(GridAddr::from_coord(pc)), vid.0));
1063 if unmapped[input_idx] {
1064 continue;
1065 }
1066
1067 #[cfg(feature = "perf_instrumentation")]
1068 let tm0 = PerfInstant::now();
1069 if self.first_load_assume_new {
1070 self.load_packed_to_vertex.insert(packed, vid);
1071 } else {
1072 let addr = CellRef::new(sid, Coord::new(pc.row(), pc.col(), true, true));
1073 self.cell_to_vertex.insert(addr, vid);
1074 }
1075 #[cfg(feature = "perf_instrumentation")]
1076 {
1077 t_map_insert_us += tm0.elapsed().as_micros();
1078 }
1079
1080 match self.config.sheet_index_mode {
1081 crate::engine::SheetIndexMode::Eager
1082 | crate::engine::SheetIndexMode::FastBatch => {
1083 #[cfg(feature = "perf_instrumentation")]
1084 let ti0 = PerfInstant::now();
1085 self.sheet_index_mut(sid)
1086 .add_vertex(GridAddr::from_coord(pc), vid);
1087 #[cfg(feature = "perf_instrumentation")]
1088 {
1089 t_index_insert_us += ti0.elapsed().as_micros();
1090 }
1091 }
1092 crate::engine::SheetIndexMode::Lazy => {
1093 }
1095 }
1096 }
1097 }
1098 }
1099
1100 if !add_batch.is_empty() {
1101 #[cfg(feature = "perf_instrumentation")]
1102 let te0 = PerfInstant::now();
1103 #[cfg(any(test, feature = "legacy_oracle"))]
1104 {
1105 self.edges.add_vertices_batch(&add_batch);
1106 let created: FxHashSet<u32> = if self.oracle_vertexless_readers.is_empty() {
1107 FxHashSet::default()
1108 } else {
1109 add_batch.iter().map(|&(_, raw)| raw).collect()
1110 };
1111 for (i, packed) in packed_cells.iter().enumerate() {
1112 if let Some(v) = ordered[i]
1113 && created.contains(&v.0)
1114 {
1115 self.oracle_cell_vertex_created(
1116 (packed.sheet_id(), packed.row0(), packed.col0()),
1117 v,
1118 );
1119 }
1120 }
1121 }
1122 #[cfg(feature = "perf_instrumentation")]
1123 {
1124 t_edge_register_us += te0.elapsed().as_micros();
1125 }
1126 }
1127
1128 #[cfg(feature = "perf_instrumentation")]
1129 if debug {
1130 eprintln!(
1131 "[fz][ensure] cells={} single_sheet={} packed_hits={} generic_hits={} missing={} packed_lookup={}us generic_lookup={}us alloc={}us map_insert={}us index_insert={}us edge_register={}us total={}ms",
1132 packed_cells.len(),
1133 single_sheet,
1134 packed_hits,
1135 generic_hits,
1136 missing,
1137 t_packed_lookup_us,
1138 t_generic_lookup_us,
1139 t_alloc_us,
1140 t_map_insert_us,
1141 t_index_insert_us,
1142 t_edge_register_us,
1143 t0.elapsed().as_millis(),
1144 );
1145 }
1146
1147 let ordered = ordered
1148 .into_iter()
1149 .map(|vid| vid.expect("ensure_vertices_batch_packed_ordered must resolve every coord"))
1150 .collect();
1151 (ordered, add_batch)
1152 }
1153
1154 pub fn ensure_vertices_batch_ordered(
1157 &mut self,
1158 coords: &[(SheetId, AbsCoord)],
1159 ) -> (Vec<VertexId>, Vec<(VertexAddr, u32)>) {
1160 let mut packed: Vec<PackedSheetCell> = Vec::with_capacity(coords.len());
1161 for &(sid, coord) in coords {
1162 packed.push(Self::packed_cell_key(sid, coord));
1163 }
1164 self.ensure_vertices_batch_packed_ordered(&packed)
1165 }
1166
1167 #[inline]
1168 fn packed_cell_key(sheet_id: SheetId, coord: AbsCoord) -> PackedSheetCell {
1169 PackedSheetCell::try_new(sheet_id, coord.row(), coord.col())
1170 .expect("graph coordinate must fit PackedSheetCell")
1171 }
1172
1173 fn flush_load_packed_mappings(&mut self) {
1174 if self.load_packed_to_vertex.is_empty() {
1175 return;
1176 }
1177 let debug = std::env::var("FZ_DEBUG_LOAD")
1178 .ok()
1179 .is_some_and(|v| v != "0");
1180 let t0 = crate::instant::FzInstant::now();
1181 let count = self.load_packed_to_vertex.len();
1182 self.cell_to_vertex.reserve(count);
1183 let packed_mappings = std::mem::replace(
1187 &mut self.load_packed_to_vertex,
1188 std::collections::HashMap::with_hasher(CoordBuildHasher),
1189 );
1190 for (packed, vid) in packed_mappings {
1191 let coord = AbsCoord::new(packed.row0(), packed.col0());
1192 let addr = CellRef::new(
1193 packed.sheet_id(),
1194 Coord::new(coord.row(), coord.col(), true, true),
1195 );
1196 self.cell_to_vertex.insert(addr, vid);
1197 }
1198 if debug {
1199 eprintln!(
1200 "[fz][load] flush_load_packed_mappings: {} entries in {:.1} ms",
1201 count,
1202 t0.elapsed().as_secs_f64() * 1000.0,
1203 );
1204 }
1205 }
1206
1207 pub fn set_first_load_assume_new(&mut self, enabled: bool) {
1212 let leaving = self.first_load_assume_new && !enabled;
1213 if leaving {
1214 self.flush_load_packed_mappings();
1215 self.store.shrink_to_fit();
1218 self.extent_record.fold_pending();
1222 } else if enabled {
1223 self.load_packed_to_vertex.clear();
1224 }
1225 self.first_load_assume_new = enabled;
1226 if leaving {
1227 self.authority_sync();
1228 }
1229 }
1230
1231 pub(crate) fn formula_compression_enabled(&self) -> bool {
1233 self.config.formula_compression
1234 }
1235
1236 #[doc(hidden)]
1237 pub fn first_load_assume_new(&self) -> bool {
1238 self.first_load_assume_new
1239 }
1240
1241 pub fn reset_ensure_touched(&mut self) {
1243 self.ensure_touched_sheets.clear();
1244 }
1245
1246 pub fn store_ast(&mut self, ast: &formualizer_parse::parser::ASTNode) -> AstNodeId {
1248 self.data_store.store_ast(ast, &self.sheet_reg)
1249 }
1250
1251 pub fn store_asts_batch<'a, I>(&mut self, asts: I) -> Vec<AstNodeId>
1253 where
1254 I: IntoIterator<Item = &'a formualizer_parse::parser::ASTNode>,
1255 {
1256 self.data_store.store_asts_batch(asts, &self.sheet_reg)
1257 }
1258
1259 pub fn reserve_formula_metadata(&mut self, additional: usize) {
1261 self.vertex_formulas.reserve(additional);
1262 self.formula_dirty.legacy_reserve(additional);
1263 self.volatile_vertices.reserve(additional);
1264 }
1265
1266 pub fn vid_for_sid_pc(&self, sid: SheetId, pc: AbsCoord) -> Option<VertexId> {
1268 let addr = CellRef::new(sid, Coord::new(pc.row(), pc.col(), true, true));
1269 self.cell_vertex(&addr)
1270 }
1271
1272 pub fn vid_for_plan_idx(
1274 &self,
1275 plan: &crate::engine::plan::DependencyPlan,
1276 idx: u32,
1277 ) -> Option<VertexId> {
1278 let (sid, pc) = plan.global_cells.get(idx as usize).copied()?;
1279 self.vid_for_sid_pc(sid, pc)
1280 }
1281 pub fn assign_formula_vertex(
1283 &mut self,
1284 vid: VertexId,
1285 ast_id: AstNodeId,
1286 volatile: bool,
1287 dynamic: bool,
1288 ) {
1289 self.assign_formula_ref(vid, FormulaRef::Own(ast_id), volatile, dynamic);
1290 }
1291
1292 pub(crate) fn assign_formula_ref(
1294 &mut self,
1295 vid: VertexId,
1296 formula: FormulaRef,
1297 volatile: bool,
1298 dynamic: bool,
1299 ) {
1300 self.materialize_vertex(vid);
1301 self.forget_declared_dynamic_anchor(vid);
1302 if self.vertex_formulas.contains_key(&vid) {
1303 self.remove_dependent_edges(vid);
1304 }
1305 self.store
1306 .set_kind(vid, crate::engine::vertex::VertexKind::FormulaScalar);
1307 self.vertex_values.remove(&vid);
1308 self.vertex_formulas.insert_ref(vid, formula);
1309 self.mark_volatile(vid, volatile);
1310 self.store.set_dynamic(vid, dynamic);
1311
1312 self.mark_vertex_dirty(vid);
1314 }
1315
1316 pub fn assign_formula_vertex_load_fast(
1319 &mut self,
1320 vid: VertexId,
1321 ast_id: AstNodeId,
1322 volatile: bool,
1323 dynamic: bool,
1324 ) {
1325 self.assign_formula_ref_load_fast(vid, FormulaRef::Own(ast_id), volatile, dynamic);
1326 }
1327
1328 pub(crate) fn assign_formula_ref_load_fast(
1331 &mut self,
1332 vid: VertexId,
1333 formula: FormulaRef,
1334 volatile: bool,
1335 dynamic: bool,
1336 ) {
1337 self.materialize_vertex(vid);
1338 debug_assert!(
1339 !self.vertex_formulas.contains_key(&vid),
1340 "load-fast formula assignment expects fresh/non-formula vertices"
1341 );
1342 self.forget_declared_dynamic_anchor(vid);
1343 self.store
1344 .set_kind(vid, crate::engine::vertex::VertexKind::FormulaScalar);
1345 self.vertex_values.remove(&vid);
1346 self.vertex_formulas.insert_ref(vid, formula);
1347 self.mark_volatile(vid, volatile);
1348 self.store.set_dynamic(vid, dynamic);
1349 }
1350
1351 pub(crate) fn assign_unmapped_member_load_fast(&mut self, vid: VertexId) {
1356 self.store
1357 .set_kind(vid, crate::engine::vertex::VertexKind::FormulaScalar);
1358 self.store.set_dynamic(vid, false);
1359 self.vertex_formulas.touch(vid);
1360 }
1361
1362 pub(crate) fn preallocate_load_targets(
1370 &mut self,
1371 sheet: SheetId,
1372 mut targets: Vec<(u32, u32, Option<FormulaRef>)>,
1373 ) -> usize {
1374 if targets.is_empty() {
1375 return 0;
1376 }
1377 targets.sort_by_key(|&(r, c, _)| (c, r));
1379 let mut dedup: Vec<(u32, u32, Option<FormulaRef>)> = Vec::with_capacity(targets.len());
1380 for t in targets {
1381 match dedup.last_mut() {
1382 Some(last) if (last.0, last.1) == (t.0, t.1) => *last = t,
1383 _ => dedup.push(t),
1384 }
1385 }
1386 let compress = self.config.formula_compression;
1387 let mut packed = Vec::with_capacity(dedup.len());
1388 let mut unmapped = Vec::with_capacity(dedup.len());
1389 for &(r, c, f) in &dedup {
1390 let Some(p) = PackedSheetCell::try_from_excel_1based(sheet, r, c) else {
1391 continue;
1393 };
1394 packed.push(p);
1395 unmapped.push(compress && matches!(f, Some(FormulaRef::Member { .. })));
1396 }
1397 let formulas: Vec<Option<FormulaRef>> = dedup
1398 .iter()
1399 .filter(|&&(r, c, _)| PackedSheetCell::try_from_excel_1based(sheet, r, c).is_some())
1400 .map(|&(_, _, f)| f)
1401 .collect();
1402 let (vids, created) =
1403 self.ensure_vertices_batch_packed_ordered_unmapped(&packed, &mut unmapped);
1404 let mut members = Vec::new();
1405 for (i, &v) in vids.iter().enumerate() {
1406 if unmapped[i]
1407 && let Some(f) = formulas[i]
1408 {
1409 members.push((v, sheet, packed[i].row0(), packed[i].col0(), f));
1410 }
1411 }
1412 self.install_preallocated_members(members);
1413 created.len()
1414 }
1415
1416 fn install_preallocated_members(
1420 &mut self,
1421 members: Vec<(VertexId, SheetId, u32, u32, FormulaRef)>,
1422 ) {
1423 let mut i = 0;
1424 while i < members.len() {
1425 let (v, sheet, row, col, f) = members[i];
1426 let mut len = 1usize;
1427 while let Some(&(v2, s2, r2, c2, f2)) = members.get(i + len)
1428 && v2.0 == v.0 + len as u32
1429 && s2 == sheet
1430 && c2 == col
1431 && r2 == row + len as u32
1432 && f2 == f
1433 {
1434 len += 1;
1435 }
1436 match f {
1437 FormulaRef::Member { template, anchor } if len >= 2 => {
1438 let run = virtual_members::MemberRun {
1439 sheet,
1440 col,
1441 row0: row,
1442 len: len as u32,
1443 first: v.0,
1444 template,
1445 anchor,
1446 };
1447 for (m, _) in run.members() {
1448 self.store
1449 .set_kind(m, crate::engine::vertex::VertexKind::FormulaScalar);
1450 self.store.set_virtual(m, true);
1451 self.vertex_formulas.touch(m);
1452 }
1453 self.vertex_formulas.virtual_members_mut().insert(run);
1454 }
1455 _ => {
1456 for &(m, s, r, c, _) in &members[i..i + len] {
1457 self.map_load_vertex(m, s, r, c);
1458 }
1459 }
1460 }
1461 i += len;
1462 }
1463 }
1464
1465 fn map_load_vertex(&mut self, v: VertexId, sheet: SheetId, row: u32, col: u32) {
1467 if self.first_load_assume_new {
1468 let packed = Self::packed_cell_key(sheet, AbsCoord::new(row, col));
1469 self.load_packed_to_vertex.insert(packed, v);
1470 } else {
1471 self.cell_to_vertex
1472 .insert(CellRef::new(sheet, Coord::new(row, col, true, true)), v);
1473 }
1474 if self.config.sheet_index_mode != crate::engine::SheetIndexMode::Lazy {
1475 self.sheet_index_mut(sheet)
1476 .add_vertex(GridAddr::new(row, col), v);
1477 }
1478 }
1479
1480 pub(crate) fn assign_preallocated_member(
1485 &mut self,
1486 vid: VertexId,
1487 formula: FormulaRef,
1488 volatile: bool,
1489 dynamic: bool,
1490 ) {
1491 if !volatile && !dynamic && self.vertex_formulas.get(&vid) == Some(formula) {
1492 return;
1493 }
1494 self.materialize_vertex(vid);
1495 self.vertex_formulas.forget_materialized(&vid);
1496 self.assign_formula_ref_load_fast(vid, formula, volatile, dynamic);
1497 }
1498
1499 pub(crate) fn install_load_members(
1504 &mut self,
1505 mut members: Vec<(VertexId, SheetId, u32, u32, FormulaRef)>,
1506 ) {
1507 if members.is_empty() {
1508 return;
1509 }
1510 members.sort_by_key(|m| m.0);
1512 members.reverse();
1513 members.dedup_by_key(|m| m.0);
1514 members.reverse();
1515 let mut runs: Vec<virtual_members::MemberRun> = Vec::new();
1516 let mut singles: Vec<usize> = Vec::new();
1517 let mut i = 0;
1518 while i < members.len() {
1519 let (v, sheet, row, col, f) = members[i];
1520 let mut len = 1usize;
1521 if let FormulaRef::Member { template, anchor } = f {
1522 while let Some(&(v2, s2, r2, c2, f2)) = members.get(i + len)
1523 && v2.0 == v.0 + len as u32
1524 && s2 == sheet
1525 && c2 == col
1526 && r2 == row + len as u32
1527 && f2 == f
1528 {
1529 len += 1;
1530 }
1531 if len >= 2 {
1532 runs.push(virtual_members::MemberRun {
1533 sheet,
1534 col,
1535 row0: row,
1536 len: len as u32,
1537 first: v.0,
1538 template,
1539 anchor,
1540 });
1541 i += len;
1542 continue;
1543 }
1544 }
1545 singles.push(i);
1546 i += 1;
1547 }
1548 for i in singles {
1549 let (v, sheet, row, col, f) = members[i];
1550 self.vertex_formulas.restore(v, f);
1551 if self.first_load_assume_new {
1552 let packed = Self::packed_cell_key(sheet, AbsCoord::new(row, col));
1553 self.load_packed_to_vertex.insert(packed, v);
1554 } else {
1555 self.cell_to_vertex
1556 .insert(CellRef::new(sheet, Coord::new(row, col, true, true)), v);
1557 }
1558 if self.config.sheet_index_mode != crate::engine::SheetIndexMode::Lazy {
1559 self.sheet_index_mut(sheet)
1560 .add_vertex(GridAddr::new(row, col), v);
1561 }
1562 }
1563 for r in runs {
1564 for (v, _) in r.members() {
1565 self.store.set_virtual(v, true);
1566 }
1567 self.vertex_formulas.virtual_members_mut().insert(r);
1568 }
1569 }
1570
1571 #[cfg(any(test, feature = "legacy_oracle"))]
1572 pub fn add_edges_nobatch(&mut self, dependent: VertexId, dependencies: &[VertexId]) {
1574 self.add_dependent_edges_nobatch(dependent, dependencies);
1575 }
1576
1577 pub fn iter_vertex_ids(&self) -> impl Iterator<Item = VertexId> + '_ {
1579 self.store.all_vertices()
1580 }
1581
1582 pub fn vertex_addr(&self, vid: VertexId) -> VertexAddr {
1585 self.store.addr(vid)
1586 }
1587
1588 pub fn vertex_grid_addr(&self, vid: VertexId) -> Option<GridAddr> {
1590 self.store.grid_addr(vid)
1591 }
1592
1593 pub fn vertex_count(&self) -> usize {
1595 self.store.len()
1596 }
1597
1598 #[cfg(any(test, feature = "legacy_oracle"))]
1599 pub fn build_edges_from_adjacency(
1601 &mut self,
1602 adjacency: Vec<(u32, Vec<u32>)>,
1603 coords: Vec<VertexAddr>,
1604 vertex_ids: Vec<u32>,
1605 ) {
1606 #[cfg(not(any(test, feature = "legacy_oracle")))]
1607 let _ = (&adjacency, &coords, &vertex_ids);
1608 #[cfg(any(test, feature = "legacy_oracle"))]
1609 {
1610 let adjacency = self.edges.adjacency_with_carried_forward_edges(adjacency);
1614 self.edges
1615 .build_from_adjacency(adjacency, coords, vertex_ids);
1616 }
1617 }
1618 pub fn used_row_bounds_for_columns(
1620 &self,
1621 sheet_id: SheetId,
1622 start_col: u32,
1623 end_col: u32,
1624 ) -> Option<(u32, u32)> {
1625 if let Some(index) = self.sheet_indexes.get(&sheet_id)
1627 && !index.is_empty()
1628 {
1629 let mut min_r: Option<u32> = None;
1630 let mut max_r: Option<u32> = None;
1631 for vid in index.vertices_in_col_range(start_col, end_col) {
1632 let Some(r) = self.store.grid_addr(vid).map(|addr| addr.row()) else {
1633 continue;
1634 };
1635 min_r = Some(min_r.map(|m| m.min(r)).unwrap_or(r));
1636 max_r = Some(max_r.map(|m| m.max(r)).unwrap_or(r));
1637 }
1638 self.virtual_row_bounds(sheet_id, start_col, end_col, &mut min_r, &mut max_r);
1639 self.extent_row_bounds(sheet_id, start_col, end_col, &mut min_r, &mut max_r);
1640 return match (min_r, max_r) {
1641 (Some(a), Some(b)) => Some((a, b)),
1642 _ => None,
1643 };
1644 }
1645 let mut min_r: Option<u32> = None;
1647 let mut max_r: Option<u32> = None;
1648 for cref in self.cell_to_vertex.keys() {
1649 if cref.sheet_id == sheet_id {
1650 let c = cref.coord.col();
1651 if c >= start_col && c <= end_col {
1652 let r = cref.coord.row();
1653 min_r = Some(min_r.map(|m| m.min(r)).unwrap_or(r));
1654 max_r = Some(max_r.map(|m| m.max(r)).unwrap_or(r));
1655 }
1656 }
1657 }
1658 for packed in self.load_packed_to_vertex.keys() {
1659 if packed.sheet_id() == sheet_id {
1660 let c = packed.col0();
1661 if c >= start_col && c <= end_col {
1662 let r = packed.row0();
1663 min_r = Some(min_r.map(|m| m.min(r)).unwrap_or(r));
1664 max_r = Some(max_r.map(|m| m.max(r)).unwrap_or(r));
1665 }
1666 }
1667 }
1668 self.virtual_row_bounds(sheet_id, start_col, end_col, &mut min_r, &mut max_r);
1669 self.extent_row_bounds(sheet_id, start_col, end_col, &mut min_r, &mut max_r);
1670 match (min_r, max_r) {
1671 (Some(a), Some(b)) => Some((a, b)),
1672 _ => None,
1673 }
1674 }
1675
1676 pub fn finalize_sheet_index(&mut self, sheet: &str) {
1678 let Some(sheet_id) = self.sheet_reg.get_id(sheet) else {
1679 return;
1680 };
1681 self.rebuild_sheet_index(sheet_id);
1682 }
1683
1684 fn rebuild_sheet_index(&mut self, sheet_id: SheetId) {
1685 let mut idx = SheetIndex::new();
1686 let mut batch: Vec<(GridAddr, VertexId)> =
1687 Vec::with_capacity(self.cell_to_vertex.len() + self.load_packed_to_vertex.len());
1688 for (cref, vid) in &self.cell_to_vertex {
1689 if cref.sheet_id == sheet_id {
1690 batch.push((GridAddr::new(cref.coord.row(), cref.coord.col()), *vid));
1691 }
1692 }
1693 for (&packed, &vid) in &self.load_packed_to_vertex {
1694 if packed.sheet_id() != sheet_id {
1695 continue;
1696 }
1697 let coord = GridAddr::new(packed.row0(), packed.col0());
1698 let addr = CellRef::new(sheet_id, Coord::new(coord.row(), coord.col(), true, true));
1699 if self.cell_to_vertex.contains_key(&addr) {
1700 continue;
1701 }
1702 batch.push((coord, vid));
1703 }
1704 idx.add_vertices_batch(&batch);
1705 self.sheet_indexes.insert(sheet_id, idx);
1706 }
1707
1708 pub(crate) fn prepare_sheet_index_for_query(&mut self, sheet_id: SheetId) {
1712 if self.config.sheet_index_mode == crate::engine::SheetIndexMode::Lazy {
1713 self.rebuild_sheet_index(sheet_id);
1714 }
1715 }
1716
1717 pub fn set_sheet_index_mode(&mut self, mode: crate::engine::SheetIndexMode) {
1718 self.config.sheet_index_mode = mode;
1719 }
1720
1721 pub(crate) fn set_evaluation_budgets(&mut self, budgets: crate::engine::EvaluationBudgets) {
1722 self.config.evaluation_budgets = budgets;
1723 }
1724
1725 pub fn used_col_bounds_for_rows(
1727 &self,
1728 sheet_id: SheetId,
1729 start_row: u32,
1730 end_row: u32,
1731 ) -> Option<(u32, u32)> {
1732 if let Some(index) = self.sheet_indexes.get(&sheet_id)
1733 && !index.is_empty()
1734 {
1735 let mut min_c: Option<u32> = None;
1736 let mut max_c: Option<u32> = None;
1737 for vid in index.vertices_in_row_range(start_row, end_row) {
1738 let Some(c) = self.store.grid_addr(vid).map(|addr| addr.col()) else {
1739 continue;
1740 };
1741 min_c = Some(min_c.map(|m| m.min(c)).unwrap_or(c));
1742 max_c = Some(max_c.map(|m| m.max(c)).unwrap_or(c));
1743 }
1744 self.virtual_col_bounds(sheet_id, start_row, end_row, &mut min_c, &mut max_c);
1745 self.extent_col_bounds(sheet_id, start_row, end_row, &mut min_c, &mut max_c);
1746 return match (min_c, max_c) {
1747 (Some(a), Some(b)) => Some((a, b)),
1748 _ => None,
1749 };
1750 }
1751 let mut min_c: Option<u32> = None;
1753 let mut max_c: Option<u32> = None;
1754 for cref in self.cell_to_vertex.keys() {
1755 if cref.sheet_id == sheet_id {
1756 let r = cref.coord.row();
1757 if r >= start_row && r <= end_row {
1758 let c = cref.coord.col();
1759 min_c = Some(min_c.map(|m| m.min(c)).unwrap_or(c));
1760 max_c = Some(max_c.map(|m| m.max(c)).unwrap_or(c));
1761 }
1762 }
1763 }
1764 for packed in self.load_packed_to_vertex.keys() {
1765 if packed.sheet_id() == sheet_id {
1766 let r = packed.row0();
1767 if r >= start_row && r <= end_row {
1768 let c = packed.col0();
1769 min_c = Some(min_c.map(|m| m.min(c)).unwrap_or(c));
1770 max_c = Some(max_c.map(|m| m.max(c)).unwrap_or(c));
1771 }
1772 }
1773 }
1774 self.virtual_col_bounds(sheet_id, start_row, end_row, &mut min_c, &mut max_c);
1775 self.extent_col_bounds(sheet_id, start_row, end_row, &mut min_c, &mut max_c);
1776 match (min_c, max_c) {
1777 (Some(a), Some(b)) => Some((a, b)),
1778 _ => None,
1779 }
1780 }
1781
1782 fn extent_row_bounds(
1784 &self,
1785 sheet: SheetId,
1786 c0: u32,
1787 c1: u32,
1788 min: &mut Option<u32>,
1789 max: &mut Option<u32>,
1790 ) {
1791 if let Some((a, b)) = self.extent_record.row_bounds_for_cols(sheet, c0, c1) {
1792 *min = Some(min.map_or(a, |m| m.min(a)));
1793 *max = Some(max.map_or(b, |m| m.max(b)));
1794 }
1795 }
1796
1797 fn extent_col_bounds(
1799 &self,
1800 sheet: SheetId,
1801 r0: u32,
1802 r1: u32,
1803 min: &mut Option<u32>,
1804 max: &mut Option<u32>,
1805 ) {
1806 if let Some((a, b)) = self.extent_record.col_bounds_for_rows(sheet, r0, r1) {
1807 *min = Some(min.map_or(a, |m| m.min(a)));
1808 *max = Some(max.map_or(b, |m| m.max(b)));
1809 }
1810 }
1811
1812 fn virtual_row_bounds(
1814 &self,
1815 sheet: SheetId,
1816 c0: u32,
1817 c1: u32,
1818 min: &mut Option<u32>,
1819 max: &mut Option<u32>,
1820 ) {
1821 for r in self
1822 .vertex_formulas
1823 .virtual_members()
1824 .runs_in_cols(sheet, c0, c1)
1825 {
1826 let (a, b) = (r.row0, r.row0 + r.len - 1);
1827 *min = Some(min.map_or(a, |m| m.min(a)));
1828 *max = Some(max.map_or(b, |m| m.max(b)));
1829 }
1830 }
1831
1832 fn virtual_col_bounds(
1834 &self,
1835 sheet: SheetId,
1836 r0: u32,
1837 r1: u32,
1838 min: &mut Option<u32>,
1839 max: &mut Option<u32>,
1840 ) {
1841 for r in self.vertex_formulas.virtual_members().runs_in_sheet(sheet) {
1842 if r.row0 <= r1 && r.row0 + r.len > r0 {
1843 *min = Some(min.map_or(r.col, |m| m.min(r.col)));
1844 *max = Some(max.map_or(r.col, |m| m.max(r.col)));
1845 }
1846 }
1847 }
1848
1849 pub fn sheet_has_formulas(&self, sheet_id: SheetId) -> bool {
1851 for vid in self.vertex_formulas.keys() {
1853 if self.store.sheet_id(vid) == sheet_id {
1854 return true;
1855 }
1856 }
1857 false
1858 }
1859 pub fn new() -> Self {
1860 Self::new_with_config(super::EvalConfig::default())
1861 }
1862
1863 pub fn new_with_config(config: super::EvalConfig) -> Self {
1864 let mut sheet_reg = SheetRegistry::new();
1865 let default_sheet_id = sheet_reg.id_for(&config.default_sheet_name);
1866
1867 #[cfg_attr(not(any(test, feature = "legacy_oracle")), allow(unused_mut))]
1868 let mut g = Self {
1869 store: VertexStore::new(),
1870 #[cfg(any(test, feature = "legacy_oracle"))]
1871 edges: CsrMutableEdges::new(),
1872 dep_edge_total: 0,
1873 range_reader_count: 0,
1874 renamed_sheet_aliases: FxHashMap::default(),
1875 data_store: DataStore::new(),
1876 vertex_values: FxHashMap::default(),
1877 vertex_formulas: FormulaMap::default(),
1878 value_cache_enabled: false,
1881 #[cfg(debug_assertions)]
1882 graph_value_read_attempts: AtomicU64::new(0),
1883 cell_to_vertex: std::collections::HashMap::with_hasher(CoordBuildHasher),
1884 vertex_journal: Default::default(),
1885 load_packed_to_vertex: std::collections::HashMap::with_hasher(CoordBuildHasher),
1886 formula_dirty: FormulaDirtyState::default(),
1887 dirty_propagation_visits: 0,
1888 deferred_dirty_depth: 0,
1889 deferred_dirty_pending: Vec::new(),
1890 deferred_dirty_pending_rects: Vec::new(),
1891 retired_ids: std::collections::BTreeMap::new(),
1892 retired_id_set: FxHashSet::default(),
1893 retired_dropped: Vec::new(),
1894 retired_dropped_by_undo: Vec::new(),
1895 extent_record: Default::default(),
1896 extent_dropped: Vec::new(),
1897 extent_undone: Vec::new(),
1898 volatile_vertices: FxHashSet::default(),
1899 ref_error_vertices: FxHashSet::default(),
1900 #[cfg(any(test, feature = "legacy_oracle"))]
1901 formula_to_range_deps: FxHashMap::default(),
1902 #[cfg(any(test, feature = "legacy_oracle"))]
1903 stripe_to_dependents: FxHashMap::default(),
1904 sheet_indexes: FxHashMap::default(),
1905 sheet_reg,
1906 default_sheet_id,
1907 named_ranges: FxHashMap::default(),
1908 named_ranges_lookup: FxHashMap::default(),
1909 sheet_named_ranges: FxHashMap::default(),
1910 sheet_named_ranges_lookup: FxHashMap::default(),
1911 #[cfg(any(test, feature = "legacy_oracle"))]
1912 vertex_to_names: FxHashMap::default(),
1913 name_vertex_lookup: FxHashMap::default(),
1914 pending_name_links: FxHashMap::default(),
1915 vertex_to_pending_names: FxHashMap::default(),
1916 tables: FxHashMap::default(),
1917 tables_lookup: FxHashMap::default(),
1918 table_vertex_lookup: FxHashMap::default(),
1919 source_scalars: FxHashMap::default(),
1920 source_tables: FxHashMap::default(),
1921 source_vertex_lookup: FxHashMap::default(),
1922 symbol_vertex_seq: 0,
1923 #[cfg(any(test, feature = "legacy_oracle"))]
1924 cell_to_name_dependents: FxHashMap::default(),
1925 #[cfg(any(test, feature = "legacy_oracle"))]
1926 name_to_cell_dependencies: FxHashMap::default(),
1927 #[cfg(any(test, feature = "legacy_oracle"))]
1928 oracle_vertexless_readers: FxHashMap::default(),
1929 #[cfg(any(test, feature = "legacy_oracle"))]
1930 oracle_vertexless_of: FxHashMap::default(),
1931 config: config.clone(),
1932 topology_revision: 0,
1933 symbol_revision: 0,
1934 authority: Default::default(),
1935 #[cfg(any(test, feature = "legacy_oracle"))]
1936 pk_order: None,
1937 spill_anchor_to_cells: FxHashMap::default(),
1938 fixed_single_arrays: FxHashSet::default(),
1939 fixed_array_shapes: FxHashMap::default(),
1940 spill_cell_to_anchor: std::collections::HashMap::with_hasher(CoordBuildHasher),
1941 spill_cells_by_sheet: FxHashMap::default(),
1942 spill_intruded_anchors: FxHashSet::default(),
1943 declared_dynamic_anchors: FxHashMap::default(),
1944 admission_budget_override: None,
1945 first_load_assume_new: false,
1946 ensure_touched_sheets: FxHashSet::default(),
1947 tombstone_registry: TombstoneRegistry::default(),
1948 #[cfg(test)]
1949 instr: std::sync::Mutex::new(GraphInstrumentation::default()),
1950 #[cfg(test)]
1951 prepared_legacy_graph_failure_for_test: false,
1952 };
1953
1954 #[cfg(any(test, feature = "legacy_oracle"))]
1955 if config.use_dynamic_topo {
1956 let nodes = g
1958 .store
1959 .all_vertices()
1960 .filter(|&id| g.store.vertex_exists_active(id));
1961 let mut pk = DynamicTopo::new(
1962 nodes,
1963 PkConfig {
1964 visit_budget: config.pk_visit_budget,
1965 compaction_interval_ops: config.pk_compaction_interval_ops,
1966 },
1967 );
1968 let adapter = GraphAdapter { g: &g };
1970 pk.rebuild_full(&adapter);
1971 g.pk_order = Some(pk);
1972 }
1973
1974 g
1975 }
1976
1977 #[cfg(any(test, feature = "legacy_oracle"))]
1978 pub(crate) fn pk_layers_for(&self, subset: &[VertexId]) -> Option<Vec<crate::engine::Layer>> {
1980 let pk = self.pk_order.as_ref()?;
1981 let adapter = crate::engine::topo::GraphAdapter { g: self };
1982 let layers = pk.layers_for(&adapter, subset, self.config.max_layer_width);
1983 Some(layers.into_iter().map(crate::engine::Layer::new).collect())
1984 }
1985
1986 #[cfg(any(test, feature = "legacy_oracle"))]
1987 #[inline]
1988 pub(crate) fn dynamic_topo_enabled(&self) -> bool {
1989 self.pk_order.is_some()
1990 }
1991
1992 #[cfg(test)]
1993 pub fn reset_instr(&mut self) {
1994 if let Ok(mut g) = self.instr.lock() {
1995 *g = GraphInstrumentation::default();
1996 }
1997 }
1998
1999 #[cfg(test)]
2000 pub fn instr(&self) -> GraphInstrumentation {
2001 self.instr.lock().map(|g| g.clone()).unwrap_or_default()
2002 }
2003
2004 pub(crate) fn pk_active(&self) -> bool {
2007 #[cfg(any(test, feature = "legacy_oracle"))]
2008 {
2009 self.pk_order.is_some()
2010 }
2011 #[cfg(not(any(test, feature = "legacy_oracle")))]
2012 {
2013 false
2014 }
2015 }
2016
2017 pub fn begin_batch(&mut self) {
2019 #[cfg(any(test, feature = "legacy_oracle"))]
2020 self.edges.begin_batch();
2021 }
2022
2023 pub fn end_batch(&mut self) {
2025 #[cfg(any(test, feature = "legacy_oracle"))]
2026 self.edges.end_batch();
2027 }
2028
2029 pub fn default_sheet_id(&self) -> SheetId {
2030 self.default_sheet_id
2031 }
2032
2033 pub fn default_sheet_name(&self) -> &str {
2034 self.sheet_reg.name(self.default_sheet_id)
2035 }
2036
2037 pub fn set_default_sheet_by_name(&mut self, name: &str) {
2038 self.default_sheet_id = self.sheet_id_mut(name);
2039 }
2040
2041 pub fn set_default_sheet_by_id(&mut self, id: SheetId) {
2042 self.default_sheet_id = id;
2043 }
2044
2045 pub fn sheet_id_mut(&mut self, name: &str) -> SheetId {
2047 if let Some(id) = self.sheet_reg.get_id(name) {
2048 return id;
2049 }
2050 let id = self.sheet_reg.id_for(name);
2051 self.resolve_pending_symbol("sheet", name);
2052 id
2053 }
2054
2055 pub fn sheet_id(&self, name: &str) -> Option<SheetId> {
2056 self.sheet_reg.get_id(name)
2057 }
2058
2059 fn resolve_existing_sheet_id(&self, name: &str) -> Result<SheetId, ExcelError> {
2061 self.sheet_id(name).ok_or_else(|| {
2062 ExcelError::new(ExcelErrorKind::Ref).with_message(format!("Sheet not found: {name}"))
2063 })
2064 }
2065
2066 pub fn sheet_name(&self, id: SheetId) -> &str {
2068 self.sheet_reg.name(id)
2069 }
2070
2071 pub fn sheet_reg(&self) -> &SheetRegistry {
2073 &self.sheet_reg
2074 }
2075
2076 pub(crate) fn data_store(&self) -> &DataStore {
2077 &self.data_store
2078 }
2079
2080 pub(crate) fn make_ingest_pipeline<'a>(
2081 &'a mut self,
2082 function_provider: &'a dyn crate::traits::FunctionProvider,
2083 policy: formualizer_parse::parser::CollectPolicy,
2084 ) -> crate::engine::ingest_pipeline::IngestPipeline<'a> {
2085 use crate::engine::ingest_pipeline::{
2086 NameRegistryView, NamedEntryRef, NamedTarget, SourceEntryRef, SourceRegistryView,
2087 TableEntrySnapshot, TableRegistryView,
2088 };
2089
2090 let DependencyGraph {
2091 data_store,
2092 sheet_reg,
2093 named_ranges,
2094 named_ranges_lookup,
2095 sheet_named_ranges,
2096 sheet_named_ranges_lookup,
2097 tables,
2098 tables_lookup,
2099 source_scalars,
2100 source_tables,
2101 config,
2102 ..
2103 } = self;
2104
2105 let unbound_pending =
2106 config.preparation_policy == crate::engine::PreparationPolicy::BestEffort;
2107 let case_sensitive_names = config.case_sensitive_names;
2108 let names = NameRegistryView::new(move |name, current_sheet| {
2109 let found = if case_sensitive_names {
2110 sheet_named_ranges
2111 .get(&(current_sheet, name.to_string()))
2112 .or_else(|| named_ranges.get(name))
2113 } else {
2114 let key = name.to_lowercase();
2115 sheet_named_ranges_lookup
2116 .get(&(current_sheet, key.clone()))
2117 .and_then(|canon| sheet_named_ranges.get(&(current_sheet, canon.clone())))
2118 .or_else(|| {
2119 named_ranges_lookup
2120 .get(&key)
2121 .and_then(|canon| named_ranges.get(canon))
2122 })
2123 };
2124 found.map(|entry| NamedEntryRef {
2125 vertex: entry.vertex,
2126 target: match &entry.definition {
2127 crate::engine::named_range::NamedDefinition::Cell(cell) => {
2128 NamedTarget::Cell(*cell)
2129 }
2130 crate::engine::named_range::NamedDefinition::Range(range) => {
2131 NamedTarget::Range(*range)
2132 }
2133 crate::engine::named_range::NamedDefinition::Literal(_)
2134 | crate::engine::named_range::NamedDefinition::Formula { .. } => {
2135 NamedTarget::Other
2136 }
2137 },
2138 })
2139 });
2140
2141 let case_sensitive_tables = config.case_sensitive_tables;
2142 let tables_ref = &*tables;
2143 let tables_lookup_ref = &*tables_lookup;
2144 let snapshot_table = |entry: &tables::TableEntry| TableEntrySnapshot {
2145 name: entry.name.clone(),
2146 range: entry.range,
2147 header_row: entry.header_row,
2148 headers: entry.headers.clone(),
2149 vertex: entry.vertex,
2150 };
2151 let tables_view = TableRegistryView::new(
2152 move |name| {
2153 if case_sensitive_tables {
2154 tables_ref.get(name).map(snapshot_table)
2155 } else {
2156 let key = name.to_lowercase();
2157 tables_lookup_ref
2158 .get(&key)
2159 .and_then(|canon| tables_ref.get(canon))
2160 .map(snapshot_table)
2161 }
2162 },
2163 move |cell| {
2164 let row0 = cell.coord.row();
2165 let col0 = cell.coord.col();
2166 let mut best: Option<&tables::TableEntry> = None;
2167 let mut best_area = u64::MAX;
2168 let mut best_name = "";
2169 for table in tables_ref.values() {
2170 if table.sheet_id() != cell.sheet_id {
2171 continue;
2172 }
2173 let sr0 = table.range.start.coord.row();
2174 let sc0 = table.range.start.coord.col();
2175 let er0 = table.range.end.coord.row();
2176 let ec0 = table.range.end.coord.col();
2177 if row0 < sr0 || row0 > er0 || col0 < sc0 || col0 > ec0 {
2178 continue;
2179 }
2180 let area = ((er0 - sr0 + 1) as u64).saturating_mul((ec0 - sc0 + 1) as u64);
2181 let name = table.name.as_str();
2182 if best.is_none() || area < best_area || (area == best_area && name < best_name)
2183 {
2184 best = Some(table);
2185 best_area = area;
2186 best_name = name;
2187 }
2188 }
2189 best.map(snapshot_table)
2190 },
2191 );
2192
2193 let sources = SourceRegistryView::new(
2194 move |name| {
2195 source_scalars.get(name).map(|entry| SourceEntryRef {
2196 vertex: entry.vertex,
2197 })
2198 },
2199 move |name| {
2200 source_tables.get(name).map(|entry| SourceEntryRef {
2201 vertex: entry.vertex,
2202 })
2203 },
2204 );
2205
2206 crate::engine::ingest_pipeline::IngestPipeline::new(
2207 data_store,
2208 sheet_reg,
2209 names,
2210 tables_view,
2211 sources,
2212 function_provider,
2213 policy,
2214 )
2215 .with_unbound_pending(unbound_pending)
2216 }
2217
2218 pub fn to_a1(&self, cell_ref: CellRef) -> String {
2220 format!("{}!{}", self.sheet_name(cell_ref.sheet_id), cell_ref.coord)
2221 }
2222
2223 pub(crate) fn vertex_len(&self) -> usize {
2224 self.store.len()
2225 }
2226
2227 #[cfg(test)]
2229 pub(crate) fn next_vertex_id_for_test(&self) -> u32 {
2230 crate::engine::vertex_store::FIRST_NORMAL_VERTEX + self.store.len() as u32
2231 }
2232
2233 pub(crate) fn topology_revision(&self) -> u64 {
2234 self.topology_revision
2235 }
2236
2237 pub(crate) fn bump_topology_revision(&mut self) {
2238 self.topology_revision = self.topology_revision.wrapping_add(1);
2239 }
2240
2241 pub(crate) fn symbol_revision(&self) -> u64 {
2242 self.symbol_revision
2243 }
2244
2245 pub(crate) fn bump_symbol_revision(&mut self) {
2246 self.symbol_revision = self.symbol_revision.wrapping_add(1);
2247 self.authority_sync_if_ready();
2250 }
2251
2252 #[cfg(any(test, feature = "legacy_oracle"))]
2253 pub(crate) fn formula_range_dependencies(
2254 &self,
2255 vertex: VertexId,
2256 ) -> Option<&[SharedRangeRef<'static>]> {
2257 self.formula_to_range_deps.get(&vertex).map(Vec::as_slice)
2258 }
2259
2260 pub(crate) fn spill_anchors_in_region(
2261 &self,
2262 sheet_id: SheetId,
2263 start_row0: u32,
2264 start_col0: u32,
2265 end_row0: u32,
2266 end_col0: u32,
2267 ) -> Vec<VertexId> {
2268 let mut anchors = self
2269 .spill_cells_by_sheet
2270 .get(&sheet_id)
2271 .into_iter()
2272 .flat_map(|cells| cells.range((start_row0, 0)..=(end_row0, u32::MAX)))
2273 .filter_map(|(&(row, col), anchor)| {
2274 (row <= end_row0 && col >= start_col0 && col <= end_col0).then_some(*anchor)
2275 })
2276 .collect::<Vec<_>>();
2277 anchors.sort_unstable();
2278 anchors.dedup();
2279 anchors
2280 }
2281
2282 pub fn sheet_index_mut(&mut self, sheet_id: SheetId) -> &mut SheetIndex {
2285 self.sheet_indexes.entry(sheet_id).or_default()
2286 }
2287
2288 pub fn sheet_index(&self, sheet_id: SheetId) -> Option<&SheetIndex> {
2290 self.sheet_indexes.get(&sheet_id)
2291 }
2292
2293 pub(crate) fn sheet_index_vertex_count(&self, sheet_id: SheetId) -> usize {
2294 self.sheet_indexes.get(&sheet_id).map_or(0, SheetIndex::len)
2295 + self
2296 .vertex_formulas
2297 .virtual_members()
2298 .runs_in_sheet(sheet_id)
2299 .map(|r| r.len as usize)
2300 .sum::<usize>()
2301 }
2302
2303 pub(crate) fn set_admission_budget_override(
2304 &mut self,
2305 budgets: Option<crate::engine::EvaluationBudgets>,
2306 ) -> Option<crate::engine::EvaluationBudgets> {
2307 std::mem::replace(&mut self.admission_budget_override, budgets)
2308 }
2309
2310 fn self_admission_budgets(&self) -> crate::engine::EvaluationBudgets {
2311 self.admission_budget_override
2312 .clone()
2313 .unwrap_or_else(|| self.config.resolved_evaluation_budgets())
2314 }
2315
2316 fn preview_spill_materialization(
2317 &self,
2318 target_cells: &[CellRef],
2319 ) -> Result<crate::engine::resource_ledger::GraphAdmission, ExcelError> {
2320 let unique = target_cells.iter().copied().collect::<FxHashSet<_>>();
2321 let added_vertices = unique
2322 .iter()
2323 .filter(|cell| self.cell_vertex(cell).is_none())
2324 .count();
2325 let stats = self.baseline_stats();
2326 Ok(crate::engine::resource_ledger::GraphAdmission {
2327 final_vertices: stats
2328 .graph_vertex_count
2329 .checked_add(added_vertices)
2330 .ok_or_else(|| {
2331 ExcelError::new(ExcelErrorKind::NImpl)
2332 .with_message("spill vertex count overflow")
2333 })?,
2334 final_edges: stats.graph_edge_count,
2335 materialization_cells: unique.len() as u64,
2336 added_vertices,
2337 added_edges: 0,
2338 })
2339 }
2340
2341 pub(crate) fn preview_value_mutation(
2342 &self,
2343 sheet_id: SheetId,
2344 row: u32,
2345 col: u32,
2346 ) -> Result<crate::engine::resource_ledger::GraphAdmission, ExcelError> {
2347 let cell = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
2348 let existing = self.cell_vertex(&cell);
2349 let stats = self.baseline_stats();
2350 let removed_edges = existing.map_or(0, |vertex| self.store.edge_offset(vertex) as usize);
2351 Ok(crate::engine::resource_ledger::GraphAdmission {
2353 final_vertices: stats.graph_vertex_count,
2354 final_edges: stats
2355 .graph_edge_count
2356 .checked_sub(removed_edges)
2357 .ok_or_else(|| {
2358 ExcelError::new(ExcelErrorKind::NImpl)
2359 .with_message("graph edge count underflow")
2360 })?,
2361 materialization_cells: 0,
2362 added_vertices: 0,
2363 added_edges: 0,
2364 })
2365 }
2366
2367 pub(crate) fn preview_value_mutations(
2368 &self,
2369 sheet_id: SheetId,
2370 cells: &[(u32, u32)],
2371 ) -> Result<crate::engine::resource_ledger::GraphAdmission, ExcelError> {
2372 let mut targets = std::collections::BTreeSet::new();
2373 let added_vertices = 0usize;
2374 let mut removed_edges = 0usize;
2375 for (row, col) in cells {
2376 let packed = PackedSheetCell::try_from_excel_1based(sheet_id, *row, *col)
2377 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
2378 if !targets.insert(packed) {
2379 continue;
2380 }
2381 let reference = CellRef::new(sheet_id, Coord::from_excel(*row, *col, true, true));
2382 if let Some(vertex) = self.cell_vertex(&reference) {
2384 removed_edges = removed_edges
2385 .checked_add(self.store.edge_offset(vertex) as usize)
2386 .ok_or_else(|| ExcelError::new(ExcelErrorKind::NImpl))?;
2387 }
2388 }
2389 let stats = self.baseline_stats();
2390 Ok(crate::engine::resource_ledger::GraphAdmission {
2391 final_vertices: stats
2392 .graph_vertex_count
2393 .checked_add(added_vertices)
2394 .ok_or_else(|| ExcelError::new(ExcelErrorKind::NImpl))?,
2395 final_edges: stats
2396 .graph_edge_count
2397 .checked_sub(removed_edges)
2398 .ok_or_else(|| ExcelError::new(ExcelErrorKind::NImpl))?,
2399 materialization_cells: 0,
2400 added_vertices,
2401 added_edges: 0,
2402 })
2403 }
2404
2405 pub(crate) fn preview_formula_mutations(
2406 &self,
2407 plans: &[(SheetId, u32, u32, DependencyPlanRow)],
2408 ) -> Result<crate::engine::resource_ledger::GraphAdmission, ExcelError> {
2409 let mut new_cells = std::collections::BTreeSet::new();
2410 let mut removed_edges = 0usize;
2411 let mut added_edges = 0usize;
2412 for (sheet_id, row, col, plan) in plans {
2413 let target = PackedSheetCell::try_from_excel_1based(*sheet_id, *row, *col)
2414 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
2415 let target_ref = CellRef::new(*sheet_id, Coord::from_excel(*row, *col, true, true));
2416 if let Some(vertex) = self.cell_vertex(&target_ref) {
2417 removed_edges = removed_edges
2418 .checked_add(self.store.edge_offset(vertex) as usize)
2419 .ok_or_else(|| {
2420 ExcelError::new(ExcelErrorKind::NImpl)
2421 .with_message("graph edge count overflow")
2422 })?;
2423 } else {
2424 new_cells.insert(target);
2425 }
2426
2427 let mut dependencies = std::collections::BTreeSet::new();
2428 for dependency in &plan.direct_cell_deps {
2429 let packed = PackedSheetCell::try_new(
2430 dependency.sheet_id,
2431 dependency.coord.row(),
2432 dependency.coord.col(),
2433 )
2434 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
2435 let reference = CellRef::new(dependency.sheet_id, dependency.coord);
2436 if let Some(vertex) = self.cell_vertex(&reference) {
2437 dependencies.insert((0u8, u64::from(vertex.0)));
2438 } else {
2439 new_cells.insert(packed);
2440 dependencies.insert((1u8, packed.as_u64()));
2441 }
2442 }
2443 for name in plan.resolved_named_refs.iter().chain(&plan.named_refs) {
2444 if let Some(entry) = self.resolve_name_entry(name, *sheet_id) {
2445 dependencies.insert((0, u64::from(entry.vertex.0)));
2446 } else if let Some(entry) = self.resolve_source_scalar_entry(name) {
2447 dependencies.insert((0, u64::from(entry.vertex.0)));
2448 }
2449 }
2450 for name in &plan.source_refs {
2451 if let Some(vertex) = self
2452 .resolve_source_scalar_entry(name)
2453 .map(|entry| entry.vertex)
2454 .or_else(|| {
2455 self.resolve_source_table_entry(name)
2456 .map(|entry| entry.vertex)
2457 })
2458 {
2459 dependencies.insert((0, u64::from(vertex.0)));
2460 }
2461 }
2462 for name in &plan.table_refs {
2463 if let Some(vertex) = self
2464 .resolve_table_entry(name)
2465 .map(|entry| entry.vertex)
2466 .or_else(|| {
2467 self.resolve_source_table_entry(name)
2468 .map(|entry| entry.vertex)
2469 })
2470 {
2471 dependencies.insert((0, u64::from(vertex.0)));
2472 }
2473 }
2474 let target_row = target.row0();
2475 let target_col = target.col0();
2476 if plan.range_deps.iter().any(|range| {
2477 let range_sheet = self
2479 .sheet_reg
2480 .resolve_locator(&range.sheet, *sheet_id)
2481 .unwrap_or(*sheet_id);
2482 range_sheet == *sheet_id
2483 && range
2484 .start_row
2485 .is_none_or(|bound| target_row >= bound.index)
2486 && range.end_row.is_none_or(|bound| target_row <= bound.index)
2487 && range
2488 .start_col
2489 .is_none_or(|bound| target_col >= bound.index)
2490 && range.end_col.is_none_or(|bound| target_col <= bound.index)
2491 }) {
2492 dependencies.insert((1, target.as_u64()));
2493 }
2494 added_edges = added_edges.checked_add(dependencies.len()).ok_or_else(|| {
2495 ExcelError::new(ExcelErrorKind::NImpl).with_message("graph edge count overflow")
2496 })?;
2497 }
2498 let stats = self.baseline_stats();
2499 Ok(crate::engine::resource_ledger::GraphAdmission {
2500 final_vertices: stats
2501 .graph_vertex_count
2502 .checked_add(new_cells.len())
2503 .ok_or_else(|| {
2504 ExcelError::new(ExcelErrorKind::NImpl)
2505 .with_message("graph vertex count overflow")
2506 })?,
2507 final_edges: stats
2508 .graph_edge_count
2509 .checked_sub(removed_edges)
2510 .and_then(|count| count.checked_add(added_edges))
2511 .ok_or_else(|| {
2512 ExcelError::new(ExcelErrorKind::NImpl).with_message("graph edge count overflow")
2513 })?,
2514 materialization_cells: plans.len() as u64,
2515 added_vertices: new_cells.len(),
2516 added_edges,
2517 })
2518 }
2519
2520 pub(crate) fn formula_intervals_in_region(
2529 &self,
2530 sheet_id: SheetId,
2531 start_row0: u32,
2532 end_row0: u32,
2533 start_col0: u32,
2534 end_col0: u32,
2535 mut matches: impl FnMut(AstNodeId) -> bool,
2536 ) -> Vec<(u32, u32, u32)> {
2537 let mut out = Vec::new();
2538 if let Some(index) = self.sheet_indexes.get(&sheet_id) {
2539 for v in index.rect_candidates(start_row0, end_row0, start_col0, end_col0) {
2540 if !self.formula_view(v).is_some_and(|f| matches(f.template)) {
2541 continue;
2542 }
2543 if let Some(cell) = self.get_cell_ref(v) {
2544 let (row, col) = (cell.coord.row(), cell.coord.col());
2545 if (start_row0..=end_row0).contains(&row)
2546 && (start_col0..=end_col0).contains(&col)
2547 {
2548 out.push((col, row, row));
2549 }
2550 }
2551 }
2552 }
2553 for r in self
2554 .vertex_formulas
2555 .virtual_members()
2556 .runs_in_cols(sheet_id, start_col0, end_col0)
2557 {
2558 let lo = r.row0.max(start_row0);
2559 let hi = (r.row0 + r.len - 1).min(end_row0);
2560 if lo <= hi && matches(r.template) {
2561 out.push((r.col, lo, hi));
2562 }
2563 }
2564 out
2565 }
2566
2567 pub(crate) fn vertices_in_region(
2568 &self,
2569 sheet_id: SheetId,
2570 start_row0: u32,
2571 end_row0: u32,
2572 start_col0: u32,
2573 end_col0: u32,
2574 ) -> Vec<VertexId> {
2575 let Some(index) = self.sheet_indexes.get(&sheet_id) else {
2576 return Vec::new();
2577 };
2578 let mut out = index.vertices_in_rect(start_row0, end_row0, start_col0, end_col0);
2579 for r in self
2582 .vertex_formulas
2583 .virtual_members()
2584 .runs_in_cols(sheet_id, start_col0, end_col0)
2585 {
2586 let lo = r.row0.max(start_row0);
2587 let hi = (r.row0 + r.len - 1).min(end_row0);
2588 if lo <= hi {
2589 out.extend((lo..=hi).map(|row| VertexId(r.first + (row - r.row0))));
2590 }
2591 }
2592 out
2593 }
2594
2595 #[cfg(test)]
2596 pub(crate) fn reset_sheet_index_query_stats(&self) {
2597 for index in self.sheet_indexes.values() {
2598 index.reset_query_stats();
2599 }
2600 }
2601
2602 #[cfg(test)]
2603 pub(crate) fn sheet_index_query_stats(
2604 &self,
2605 ) -> crate::engine::sheet_index::SheetIndexQueryStats {
2606 self.sheet_indexes.values().fold(
2607 crate::engine::sheet_index::SheetIndexQueryStats::default(),
2608 |mut total, index| {
2609 let stats = index.query_stats();
2610 total.coordinate_nodes_visited = total
2611 .coordinate_nodes_visited
2612 .saturating_add(stats.coordinate_nodes_visited);
2613 total.values_visited = total.values_visited.saturating_add(stats.values_visited);
2614 total
2615 },
2616 )
2617 }
2618
2619 pub fn set_cell_value(
2625 &mut self,
2626 sheet: &str,
2627 row: u32,
2628 col: u32,
2629 value: LiteralValue,
2630 ) -> Result<OperationSummary, ExcelError> {
2631 let _ = normalize_stored_literal(value);
2632 let sheet_id = self.sheet_id_mut(sheet);
2633 let budgets = self.self_admission_budgets();
2634 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
2635 let usage = self.preview_value_mutation(sheet_id, row, col)?;
2636 crate::engine::resource_ledger::preflight_graph_admission(&budgets, usage, None)
2637 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
2638 }
2639 let coord = Coord::from_excel(row, col, true, true);
2641 let addr = CellRef::new(sheet_id, coord);
2642 self.vacate_cell(&addr);
2643 Ok(OperationSummary {
2644 affected_vertices: self.mark_dirty_cells(&[(sheet_id, coord.row(), coord.col())]),
2645 created_placeholders: Vec::new(),
2646 })
2647 }
2648
2649 pub fn reserve_cells(&mut self, additional: usize) {
2651 self.store.reserve(additional);
2652 if self.value_cache_enabled {
2653 self.vertex_values.reserve(additional);
2654 }
2655 self.cell_to_vertex.reserve(additional);
2656 }
2658
2659 pub fn set_cell_value_bulk_untracked(
2662 &mut self,
2663 sheet: &str,
2664 row: u32,
2665 col: u32,
2666 value: LiteralValue,
2667 ) -> Result<(), ExcelError> {
2668 let _ = normalize_stored_literal(value);
2669 let sheet_id = self.sheet_id_mut(sheet);
2670 let budgets = self.self_admission_budgets();
2671 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
2672 let usage = self.preview_value_mutation(sheet_id, row, col)?;
2673 crate::engine::resource_ledger::preflight_graph_admission(&budgets, usage, None)
2674 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
2675 }
2676 let coord = Coord::from_excel(row, col, true, true);
2677 self.vacate_cell(&CellRef::new(sheet_id, coord));
2678 Ok(())
2679 }
2680
2681 pub fn bulk_insert_values<I>(&mut self, sheet: &str, cells: I) -> Result<(), ExcelError>
2685 where
2686 I: IntoIterator<Item = (u32, u32, LiteralValue)>,
2687 {
2688 let collected: Vec<(u32, u32, LiteralValue)> = cells.into_iter().collect();
2689 if collected.is_empty() {
2690 return Ok(());
2691 }
2692 let sheet_id = self.sheet_id_mut(sheet);
2693 let budgets = self.self_admission_budgets();
2694 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
2695 let coordinates = collected
2696 .iter()
2697 .map(|(row, col, _)| (*row, *col))
2698 .collect::<Vec<_>>();
2699 let usage = self.preview_value_mutations(sheet_id, &coordinates)?;
2700 crate::engine::resource_ledger::preflight_graph_admission(&budgets, usage, None)
2701 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
2702 }
2703 let assume_new = self.first_load_assume_new
2705 && self
2706 .sheet_id(sheet)
2707 .map(|sid| !self.ensure_touched_sheets.contains(&sid))
2708 .unwrap_or(false);
2709 if assume_new {
2710 for (row, col, _) in collected {
2711 self.extent_record
2712 .note(sheet_id, row.saturating_sub(1), col.saturating_sub(1));
2713 }
2714 return Ok(());
2715 }
2716 for (row, col, _) in collected {
2717 let coord = Coord::from_excel(row, col, true, true);
2718 self.vacate_cell(&CellRef::new(sheet_id, coord));
2719 }
2720 Ok(())
2721 }
2722
2723 pub fn set_cell_formula(
2725 &mut self,
2726 sheet: &str,
2727 row: u32,
2728 col: u32,
2729 ast: ASTNode,
2730 ) -> Result<OperationSummary, ExcelError> {
2731 self.set_cell_formula_with_volatility(sheet, row, col, ast, false)
2732 }
2733
2734 pub fn set_cell_formula_with_volatility(
2737 &mut self,
2738 sheet: &str,
2739 row: u32,
2740 col: u32,
2741 ast: ASTNode,
2742 _volatile: bool,
2743 ) -> Result<OperationSummary, ExcelError> {
2744 let sheet_id = self.sheet_id_mut(sheet);
2745 let placement = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
2746 let provider = RegistryFunctionProvider;
2747 let ingested = {
2748 let mut pipeline = self.ingest_pipeline(&provider);
2749 pipeline.ingest_formula(FormulaAstInput::Tree(ast), placement, None)?
2750 };
2751 self.set_cell_formula_with_plan(
2752 sheet,
2753 row,
2754 col,
2755 ingested.ast_id,
2756 &ingested.dep_plan,
2757 ingested.dep_plan.volatile,
2758 ingested.dep_plan.dynamic,
2759 )
2760 }
2761
2762 pub(crate) fn set_cell_formula_with_plan(
2763 &mut self,
2764 sheet: &str,
2765 row: u32,
2766 col: u32,
2767 ast_id: AstNodeId,
2768 plan: &DependencyPlanRow,
2769 volatile: bool,
2770 dynamic: bool,
2771 ) -> Result<OperationSummary, ExcelError> {
2772 let dbg = std::env::var("FZ_DEBUG_LOAD")
2773 .ok()
2774 .is_some_and(|v| v != "0");
2775 let dep_ms_thresh: u128 = std::env::var("FZ_DEBUG_DEP_MS")
2776 .ok()
2777 .and_then(|s| s.parse().ok())
2778 .unwrap_or(0);
2779 let sample_n: usize = std::env::var("FZ_DEBUG_SAMPLE_N")
2780 .ok()
2781 .and_then(|s| s.parse().ok())
2782 .unwrap_or(0);
2783 let t0 = if dbg {
2784 Some(crate::instant::FzInstant::now())
2785 } else {
2786 None
2787 };
2788 let sheet_id = self.sheet_id_mut(sheet);
2789 let budgets = self.self_admission_budgets();
2790 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
2791 let usage = self.preview_formula_mutations(&[(sheet_id, row, col, plan.clone())])?;
2792 crate::engine::resource_ledger::preflight_graph_admission(&budgets, usage, None)
2793 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
2794 }
2795 let coord = Coord::from_excel(row, col, true, true);
2796 let addr = CellRef::new(sheet_id, coord);
2797
2798 let t_dep0 = if dbg {
2799 Some(crate::instant::FzInstant::now())
2800 } else {
2801 None
2802 };
2803 let mut created_placeholders = Vec::new();
2804 let (mut new_dependencies, vertexless_deps) =
2805 self.resolve_direct_deps(&plan.direct_cell_deps);
2806 let mut named_dependencies = Vec::new();
2807 let mut unresolved_names = Vec::new();
2808 for name in plan
2809 .resolved_named_refs
2810 .iter()
2811 .chain(plan.named_refs.iter())
2812 {
2813 if let Some(named) = self.resolve_name_entry(name, sheet_id) {
2814 if !new_dependencies.contains(&named.vertex) {
2815 new_dependencies.push(named.vertex);
2816 }
2817 if !named_dependencies.contains(&named.vertex) {
2818 named_dependencies.push(named.vertex);
2819 }
2820 } else if let Some(source) = self.resolve_source_scalar_entry(name) {
2821 if !new_dependencies.contains(&source.vertex) {
2822 new_dependencies.push(source.vertex);
2823 }
2824 } else {
2825 unresolved_names.push(name.clone());
2826 }
2827 }
2828 for source_name in &plan.source_refs {
2829 if let Some(source) = self.resolve_source_scalar_entry(source_name) {
2830 if !new_dependencies.contains(&source.vertex) {
2831 new_dependencies.push(source.vertex);
2832 }
2833 } else if let Some(source) = self.resolve_source_table_entry(source_name)
2834 && !new_dependencies.contains(&source.vertex)
2835 {
2836 new_dependencies.push(source.vertex);
2837 }
2838 }
2839 for table_name in &plan.table_refs {
2840 if let Some(table) = self.resolve_table_entry(table_name) {
2841 if !new_dependencies.contains(&table.vertex) {
2842 new_dependencies.push(table.vertex);
2843 }
2844 } else if let Some(source) = self.resolve_source_table_entry(table_name)
2845 && !new_dependencies.contains(&source.vertex)
2846 {
2847 new_dependencies.push(source.vertex);
2848 }
2849 }
2850 if let (true, Some(t)) = (dbg, t_dep0) {
2851 let elapsed = t.elapsed().as_millis();
2852 let do_log = (dep_ms_thresh > 0 && elapsed >= dep_ms_thresh)
2853 || (sample_n > 0 && (row as usize).is_multiple_of(sample_n));
2854 if (dep_ms_thresh == 0 && sample_n == 0 && row.is_multiple_of(1000)) || do_log {
2855 eprintln!(
2856 "[fz][dep] {}!{} planned: deps={}, ranges={}, placeholders={}, names={} in {} ms",
2857 self.sheet_name(sheet_id),
2858 crate::reference::Coord::from_excel(row, col, true, true),
2859 new_dependencies.len(),
2860 plan.range_deps.len(),
2861 created_placeholders.len(),
2862 named_dependencies.len(),
2863 elapsed
2864 );
2865 }
2866 }
2867
2868 self.replay_formula_vertex(&addr);
2870 let addr_vertex_id = self.get_or_create_vertex(&addr, &mut created_placeholders);
2871 self.materialize_vertex(addr_vertex_id);
2872
2873 self.ref_error_vertices.remove(&addr_vertex_id);
2875
2876 let self_reference = new_dependencies.contains(&addr_vertex_id)
2891 || vertexless_deps.iter().any(|c| same_cell(c, &addr));
2892 if self_reference && !self.config.cycle.allows_self_dependency() {
2893 return Err(ExcelError::new(ExcelErrorKind::Circ)
2894 .with_message("Self-reference detected".to_string()));
2895 }
2896
2897 for &name_vertex in &named_dependencies {
2898 let mut visited = FxHashSet::default();
2899 if self.name_depends_on_vertex(name_vertex, addr_vertex_id, &mut visited) {
2900 return Err(ExcelError::new(ExcelErrorKind::Circ)
2901 .with_message("Circular reference through named range".to_string()));
2902 }
2903 }
2904
2905 self.forget_declared_dynamic_anchor(addr_vertex_id);
2907 self.remove_dependent_edges(addr_vertex_id);
2908 self.detach_vertex_from_names(addr_vertex_id);
2909 self.clear_pending_name_references(addr_vertex_id);
2910
2911 self.store
2913 .set_kind(addr_vertex_id, VertexKind::FormulaScalar);
2914 self.vertex_formulas.insert(addr_vertex_id, ast_id);
2915 self.store.set_dirty(addr_vertex_id, true);
2916
2917 self.vertex_values.remove(&addr_vertex_id);
2919
2920 self.mark_volatile(addr_vertex_id, volatile);
2921 self.store.set_dynamic(addr_vertex_id, dynamic);
2922
2923 if !named_dependencies.is_empty() {
2924 self.attach_vertex_to_names(addr_vertex_id, &named_dependencies);
2925 }
2926 for unresolved_name in &unresolved_names {
2927 self.record_pending_name_reference(sheet_id, unresolved_name, addr_vertex_id);
2928 }
2929
2930 if let (true, Some(t)) = (dbg, t0) {
2931 let elapsed = t.elapsed().as_millis();
2932 let log_set = dep_ms_thresh > 0 && elapsed >= dep_ms_thresh;
2933 if log_set {
2934 eprintln!(
2935 "[fz][set] {}!{} total {} ms",
2936 self.sheet_name(sheet_id),
2937 crate::reference::Coord::from_excel(row, col, true, true),
2938 elapsed
2939 );
2940 }
2941 }
2942
2943 let vertexless_deps: Vec<CellRef> = vertexless_deps
2946 .into_iter()
2947 .filter(|c| {
2948 if same_cell(c, &addr) {
2949 if !new_dependencies.contains(&addr_vertex_id) {
2950 new_dependencies.push(addr_vertex_id);
2951 }
2952 false
2953 } else {
2954 true
2955 }
2956 })
2957 .collect();
2958 self.add_dependent_edges(addr_vertex_id, &new_dependencies);
2959 self.note_vertexless_deps(
2960 addr_vertex_id,
2961 vertexless_deps
2962 .iter()
2963 .map(|c| (c.sheet_id, c.coord.row(), c.coord.col())),
2964 );
2965 self.add_range_dependent_edges(addr_vertex_id, &plan.range_deps, sheet_id);
2966
2967 Ok(OperationSummary {
2968 affected_vertices: self.mark_dirty(addr_vertex_id),
2969 created_placeholders,
2970 })
2971 }
2972
2973 pub(crate) fn rewrite_structured_references_for_cell(
2974 &self,
2975 ast: &mut ASTNode,
2976 cell: CellRef,
2977 ) -> Result<bool, ExcelError> {
2978 self.rewrite_structured_references_node(ast, cell)
2979 }
2980
2981 fn rewrite_structured_references_node(
2982 &self,
2983 node: &mut ASTNode,
2984 cell: CellRef,
2985 ) -> Result<bool, ExcelError> {
2986 match &mut node.node_type {
2987 ASTNodeType::Reference { reference, .. } => {
2988 self.rewrite_structured_reference(reference, cell)
2989 }
2990 ASTNodeType::UnaryOp { expr, .. } => {
2991 self.rewrite_structured_references_node(expr, cell)
2992 }
2993 ASTNodeType::BinaryOp { left, right, .. } => {
2994 let left_rewritten = self.rewrite_structured_references_node(left, cell)?;
2995 let right_rewritten = self.rewrite_structured_references_node(right, cell)?;
2996 Ok(left_rewritten || right_rewritten)
2997 }
2998 ASTNodeType::Function { args, .. } => {
2999 let mut rewritten = false;
3000 for a in args.iter_mut() {
3001 rewritten |= self.rewrite_structured_references_node(a, cell)?;
3002 }
3003 Ok(rewritten)
3004 }
3005 ASTNodeType::Call { callee, args } => {
3006 let mut rewritten = self.rewrite_structured_references_node(callee, cell)?;
3007 for a in args.iter_mut() {
3008 rewritten |= self.rewrite_structured_references_node(a, cell)?;
3009 }
3010 Ok(rewritten)
3011 }
3012 ASTNodeType::Array(rows) => {
3013 let mut rewritten = false;
3014 for r in rows.iter_mut() {
3015 for item in r.iter_mut() {
3016 rewritten |= self.rewrite_structured_references_node(item, cell)?;
3017 }
3018 }
3019 Ok(rewritten)
3020 }
3021 ASTNodeType::Literal(_) | ASTNodeType::Omitted => Ok(false),
3022 }
3023 }
3024
3025 fn rewrite_structured_reference(
3026 &self,
3027 reference: &mut ReferenceType,
3028 cell: CellRef,
3029 ) -> Result<bool, ExcelError> {
3030 use formualizer_parse::parser::{SpecialItem, TableSpecifier};
3031
3032 let ReferenceType::Table(tref) = reference else {
3033 return Ok(false);
3034 };
3035
3036 if !tref.name.is_empty() {
3038 return Ok(false);
3039 }
3040
3041 let col_name = match &tref.specifier {
3042 Some(TableSpecifier::Combination(parts)) => {
3043 let mut saw_this_row = false;
3044 let mut col: Option<&str> = None;
3045 for p in parts {
3046 match p.as_ref() {
3047 TableSpecifier::SpecialItem(SpecialItem::ThisRow) => {
3048 saw_this_row = true;
3049 }
3050 TableSpecifier::Column(c) => {
3051 if col.is_some() {
3052 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
3053 "This-row structured reference with multiple columns is not supported"
3054 .to_string(),
3055 ));
3056 }
3057 col = Some(c.as_str());
3058 }
3059 other => {
3060 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
3061 format!(
3062 "Unsupported this-row structured reference component: {other}"
3063 ),
3064 ));
3065 }
3066 }
3067 }
3068 if !saw_this_row {
3069 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
3070 "Unnamed structured reference requires a this-row selector".to_string(),
3071 ));
3072 }
3073 col.ok_or_else(|| {
3074 ExcelError::new(ExcelErrorKind::NImpl).with_message(
3075 "This-row structured reference missing column selector".to_string(),
3076 )
3077 })?
3078 }
3079 _ => {
3080 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
3081 "Unnamed structured reference form is not supported".to_string(),
3082 ));
3083 }
3084 };
3085
3086 let Some(table) = self.find_table_containing_cell(cell) else {
3087 return Err(ExcelError::new(ExcelErrorKind::Name)
3088 .with_message("This-row structured reference used outside a table".to_string()));
3089 };
3090
3091 let row0 = cell.coord.row();
3092 let col0 = cell.coord.col();
3093 let sr0 = table.range.start.coord.row();
3094 let sc0 = table.range.start.coord.col();
3095 let er0 = table.range.end.coord.row();
3096 let ec0 = table.range.end.coord.col();
3097
3098 if row0 < sr0 || row0 > er0 || col0 < sc0 || col0 > ec0 {
3099 return Err(ExcelError::new(ExcelErrorKind::Name)
3100 .with_message("This-row structured reference used outside a table".to_string()));
3101 }
3102
3103 if table.header_row && row0 == sr0 {
3104 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
3105 "This-row structured references are not valid in the table header row".to_string(),
3106 ));
3107 }
3108
3109 let data_start = if table.header_row { sr0 + 1 } else { sr0 };
3110 if row0 < data_start {
3111 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
3112 "This-row structured references require a data/totals row context".to_string(),
3113 ));
3114 }
3115
3116 let Some(idx) = table.col_index(col_name) else {
3117 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(format!(
3118 "Unknown table column in this-row reference: {col_name}"
3119 )));
3120 };
3121 let target_col0 = sc0 + (idx as u32);
3122 let target_row = row0 + 1;
3123 let target_col = target_col0 + 1;
3124
3125 *reference = ReferenceType::Cell {
3126 sheet: None,
3127 row: target_row,
3128 col: target_col,
3129 row_abs: true,
3130 col_abs: true,
3131 };
3132
3133 Ok(true)
3134 }
3135
3136 fn find_table_containing_cell(&self, cell: CellRef) -> Option<&tables::TableEntry> {
3137 let row0 = cell.coord.row();
3138 let col0 = cell.coord.col();
3139
3140 let mut best: Option<&tables::TableEntry> = None;
3141 let mut best_area: u64 = u64::MAX;
3142 let mut best_name: &str = "";
3143
3144 for t in self.tables.values() {
3145 if t.sheet_id() != cell.sheet_id {
3146 continue;
3147 }
3148 let sr0 = t.range.start.coord.row();
3149 let sc0 = t.range.start.coord.col();
3150 let er0 = t.range.end.coord.row();
3151 let ec0 = t.range.end.coord.col();
3152 if row0 < sr0 || row0 > er0 || col0 < sc0 || col0 > ec0 {
3153 continue;
3154 }
3155
3156 let h = (er0 - sr0 + 1) as u64;
3157 let w = (ec0 - sc0 + 1) as u64;
3158 let area = h.saturating_mul(w);
3159 let name = t.name.as_str();
3160 let better = match best {
3161 None => true,
3162 Some(_) => area < best_area || (area == best_area && name < best_name),
3163 };
3164 if better {
3165 best = Some(t);
3166 best_area = area;
3167 best_name = name;
3168 }
3169 }
3170
3171 best
3172 }
3173
3174 #[allow(clippy::type_complexity)]
3175 pub(crate) fn fp8_parity_extract_dependencies_with_pending_names(
3176 &mut self,
3177 ast: &ASTNode,
3178 current_sheet_id: SheetId,
3179 ) -> Result<
3180 (
3181 Vec<VertexId>,
3182 Vec<SharedRangeRef<'static>>,
3183 Vec<CellRef>,
3184 Vec<VertexId>,
3185 Vec<String>,
3186 ),
3187 ExcelError,
3188 > {
3189 self.extract_dependencies_with_pending_names(ast, current_sheet_id)
3190 }
3191
3192 pub(crate) fn fp8_parity_is_ast_volatile(&self, ast: &ASTNode) -> bool {
3193 self.is_ast_volatile(ast)
3194 }
3195
3196 pub fn set_cell_value_ref(
3197 &mut self,
3198 cell: formualizer_common::SheetCellRef<'_>,
3199 value: LiteralValue,
3200 ) -> Result<OperationSummary, ExcelError> {
3201 let owned = cell.into_owned();
3202 let sheet_id = match owned.sheet {
3203 formualizer_common::SheetLocator::Id(id) => id,
3204 formualizer_common::SheetLocator::Name(name) => self.sheet_id_mut(name.as_ref()),
3205 formualizer_common::SheetLocator::Current => self.default_sheet_id,
3206 };
3207 let sheet_name = self.sheet_name(sheet_id).to_string();
3208 self.set_cell_value(
3209 &sheet_name,
3210 owned.coord.row() + 1,
3211 owned.coord.col() + 1,
3212 value,
3213 )
3214 }
3215
3216 pub fn set_cell_formula_ref(
3217 &mut self,
3218 cell: formualizer_common::SheetCellRef<'_>,
3219 ast: ASTNode,
3220 ) -> Result<OperationSummary, ExcelError> {
3221 let owned = cell.into_owned();
3222 let sheet_id = match owned.sheet {
3223 formualizer_common::SheetLocator::Id(id) => id,
3224 formualizer_common::SheetLocator::Name(name) => self.sheet_id_mut(name.as_ref()),
3225 formualizer_common::SheetLocator::Current => self.default_sheet_id,
3226 };
3227 let sheet_name = self.sheet_name(sheet_id).to_string();
3228 self.set_cell_formula(
3229 &sheet_name,
3230 owned.coord.row() + 1,
3231 owned.coord.col() + 1,
3232 ast,
3233 )
3234 }
3235
3236 pub fn get_cell_value_ref(
3237 &self,
3238 cell: formualizer_common::SheetCellRef<'_>,
3239 ) -> Option<LiteralValue> {
3240 let owned = cell.into_owned();
3241 let sheet_id = match owned.sheet {
3242 formualizer_common::SheetLocator::Id(id) => id,
3243 formualizer_common::SheetLocator::Name(name) => self.sheet_id(name.as_ref())?,
3244 formualizer_common::SheetLocator::Current => self.default_sheet_id,
3245 };
3246 let sheet_name = self.sheet_name(sheet_id);
3247 self.get_cell_value(sheet_name, owned.coord.row() + 1, owned.coord.col() + 1)
3248 }
3249
3250 pub fn get_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
3252 if !self.value_cache_enabled {
3253 #[cfg(debug_assertions)]
3254 {
3255 self.graph_value_read_attempts
3256 .fetch_add(1, Ordering::Relaxed);
3257 }
3258 return None;
3259 }
3260 let sheet_id = self.sheet_reg.get_id(sheet)?;
3261 let coord = Coord::from_excel(row, col, true, true);
3262 let addr = CellRef::new(sheet_id, coord);
3263
3264 self.get_vertex_id_for_address(&addr).and_then(|vertex_id| {
3265 self.vertex_values
3267 .get(&vertex_id)
3268 .map(|&value_ref| self.data_store.retrieve_value(value_ref))
3269 })
3270 }
3271
3272 fn mark_dirty(&mut self, vertex_id: VertexId) -> Vec<VertexId> {
3274 self.mark_dirty_many(&[vertex_id])
3275 }
3276
3277 pub(crate) fn mark_dirty_many(&mut self, vertex_ids: &[VertexId]) -> Vec<VertexId> {
3297 if self.deferred_dirty_depth > 0 {
3298 self.deferred_dirty_pending.extend_from_slice(vertex_ids);
3299 return vertex_ids.to_vec();
3300 }
3301 self.authority_mark_dirty(vertex_ids)
3302 }
3303
3304 pub(crate) fn dirty_propagation_visits(&self) -> u64 {
3307 self.dirty_propagation_visits
3308 }
3309
3310 pub fn begin_deferred_dirty(&mut self) {
3331 #[cfg(any(test, feature = "legacy_oracle"))]
3332 self.edges.begin_batch();
3333 self.deferred_dirty_depth += 1;
3334 }
3335
3336 pub fn end_deferred_dirty(&mut self) -> Vec<VertexId> {
3341 debug_assert!(
3342 self.deferred_dirty_depth > 0,
3343 "end_deferred_dirty without matching begin_deferred_dirty"
3344 );
3345 #[cfg(any(test, feature = "legacy_oracle"))]
3346 self.edges.end_batch();
3347 self.deferred_dirty_depth = self.deferred_dirty_depth.saturating_sub(1);
3348 if self.deferred_dirty_depth > 0 {
3349 return Vec::new();
3350 }
3351 let pending = std::mem::take(&mut self.deferred_dirty_pending);
3352 let rects = std::mem::take(&mut self.deferred_dirty_pending_rects);
3353 let mut affected = self.authority_mark_dirty_rects(&rects);
3354 if pending.is_empty() {
3355 return affected;
3356 }
3357 let live: Vec<VertexId> = pending
3358 .into_iter()
3359 .filter(|&id| self.vertex_exists(id))
3360 .collect();
3361 affected.extend(self.mark_dirty_many(&live));
3362 affected
3363 }
3364
3365 pub(crate) fn mark_dirty_cells(
3368 &mut self,
3369 cells: &[crate::engine::authority::geom::Cell],
3370 ) -> Vec<VertexId> {
3371 let rects: Vec<(SheetId, u32, u32, u32, u32)> =
3372 cells.iter().map(|&(s, r, c)| (s, r, r, c, c)).collect();
3373 self.mark_dirty_rects(&rects)
3374 }
3375
3376 pub(crate) fn mark_dirty_rects(
3379 &mut self,
3380 rects: &[(SheetId, u32, u32, u32, u32)],
3381 ) -> Vec<VertexId> {
3382 let rects: Vec<(u16, crate::engine::authority::geom::Rect)> = rects
3383 .iter()
3384 .map(|&(s, r0, r1, c0, c1)| {
3385 (s, crate::engine::authority::geom::Rect::new(r0, c0, r1, c1))
3386 })
3387 .collect();
3388 if self.deferred_dirty_depth > 0 {
3389 self.deferred_dirty_pending_rects.extend_from_slice(&rects);
3390 return Vec::new();
3391 }
3392 self.authority_mark_dirty_rects(&rects)
3393 }
3394
3395 pub fn deferred_dirty_active(&self) -> bool {
3398 self.deferred_dirty_depth > 0
3399 }
3400
3401 pub fn get_evaluation_vertices(&self) -> Vec<VertexId> {
3403 let mut result: Vec<VertexId> = self
3406 .formula_dirty
3407 .legacy_iter()
3408 .chain(self.volatile_vertices.iter().copied())
3409 .filter(|&id| {
3410 self.store.vertex_exists_active(id)
3413 && matches!(
3414 self.store.kind(id),
3415 VertexKind::FormulaScalar
3416 | VertexKind::FormulaArray
3417 | VertexKind::NamedScalar
3418 | VertexKind::NamedArray
3419 )
3420 })
3421 .collect();
3422 result.sort_unstable();
3423 result.dedup();
3424 result
3425 }
3426
3427 pub(crate) fn has_dirty_evaluation_vertices(&self) -> bool {
3430 self.formula_dirty.legacy_iter().any(|id| {
3431 self.store.vertex_exists_active(id)
3432 && matches!(
3433 self.store.kind(id),
3434 VertexKind::FormulaScalar
3435 | VertexKind::FormulaArray
3436 | VertexKind::NamedScalar
3437 | VertexKind::NamedArray
3438 )
3439 })
3440 }
3441
3442 pub fn clear_dirty_flags(&mut self, vertices: &[VertexId]) {
3444 for &vertex_id in vertices {
3445 self.store.set_dirty(vertex_id, false);
3446 self.formula_dirty.legacy_remove(&vertex_id);
3447 }
3448 self.formula_dirty.legacy_shrink_if_sparse();
3449 self.authority_observe_clean(vertices);
3450 }
3451
3452 pub fn clear_volatile_flags(&mut self) {
3454 self.volatile_vertices.clear();
3455 }
3456
3457 pub(crate) fn redirty_volatiles(&mut self) {
3462 let volatile_ids: Vec<VertexId> = self.volatile_vertices.iter().copied().collect();
3463 let _ = self.mark_dirty_many(&volatile_ids);
3464 }
3465
3466 pub(crate) fn redirty_iterative_members(&mut self, members: &[VertexId]) {
3479 let live: Vec<VertexId> = members
3480 .iter()
3481 .copied()
3482 .filter(|&id| self.vertex_exists(id))
3483 .collect();
3484 let _ = self.mark_dirty_many(&live);
3485 }
3486
3487 pub(crate) fn dep_vertex(&mut self, addr: &CellRef) -> Option<VertexId> {
3491 if let Some(vertex_id) = self.cell_vertex(addr) {
3492 return Some(vertex_id);
3493 }
3494 if self.first_load_assume_new {
3495 let packed = Self::packed_cell_key(
3496 addr.sheet_id,
3497 AbsCoord::new(addr.coord.row(), addr.coord.col()),
3498 );
3499 if let Some(&existing) = self.load_packed_to_vertex.get(&packed) {
3500 self.cell_to_vertex.insert(*addr, existing);
3501 return Some(existing);
3502 }
3503 }
3504 None
3505 }
3506
3507 pub(crate) fn vacate_cell(&mut self, addr: &CellRef) -> Option<(VertexId, bool)> {
3513 self.extent_record
3516 .note(addr.sheet_id, addr.coord.row(), addr.coord.col());
3517 let v = self.cell_vertex_mut(addr)?;
3518 let was_formula = matches!(
3519 self.store.kind(v),
3520 VertexKind::FormulaScalar | VertexKind::FormulaArray
3521 ) || self.vertex_formulas.contains_key(&v);
3522 self.remove_dependent_edges(v);
3523 self.detach_vertex_from_names(v);
3524 self.clear_pending_name_references(v);
3525 self.forget_declared_dynamic_anchor(v);
3526 self.vertex_formulas.remove(&v);
3527 self.vertex_values.remove(&v);
3528 self.ref_error_vertices.remove(&v);
3529 self.clear_formula_vertex_dirty(v);
3530 self.mark_volatile(v, false);
3531 self.store.set_dynamic(v, false);
3532 self.store.set_kind(v, VertexKind::Empty);
3533 let key = (addr.sheet_id, addr.coord.row(), addr.coord.col());
3534 #[cfg(any(test, feature = "legacy_oracle"))]
3537 {
3538 let readers = self.get_dependents(v);
3539 self.remove_all_edges(v);
3540 for r in readers {
3541 if !self.store.is_deleted(r) {
3542 self.oracle_vertexless_readers
3543 .entry(key)
3544 .or_default()
3545 .push(r);
3546 self.oracle_vertexless_of.entry(r).or_default().push(key);
3547 }
3548 }
3549 }
3550 self.cell_to_vertex.remove(addr);
3551 if let Some(index) = self.sheet_indexes.get_mut(&addr.sheet_id) {
3552 index.remove_vertex(GridAddr::new(key.1, key.2), v);
3553 }
3554 self.store.mark_deleted(v, true);
3555 if was_formula {
3556 self.retired_ids.insert(key, v);
3557 self.retired_id_set.insert(v);
3558 }
3559 Some((v, was_formula))
3560 }
3561
3562 pub(crate) fn add_empty_vertex(
3566 &mut self,
3567 sheet: SheetId,
3568 row: u32,
3569 col: u32,
3570 ) -> Result<VertexId, ExcelError> {
3571 let budgets = self.self_admission_budgets();
3572 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
3573 let mut usage = self.preview_value_mutation(sheet, row + 1, col + 1)?;
3574 if self
3575 .cell_vertex(&CellRef::new(sheet, Coord::new(row, col, true, true)))
3576 .is_none()
3577 {
3578 usage.final_vertices = usage.final_vertices.saturating_add(1);
3579 usage.added_vertices = 1;
3580 }
3581 crate::engine::resource_ledger::preflight_graph_admission(&budgets, usage, None)
3582 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
3583 }
3584 let addr = CellRef::new(sheet, Coord::new(row, col, true, true));
3585 let mut created = Vec::new();
3586 let id = self.get_or_create_vertex(&addr, &mut created);
3587 self.materialize_vertex(id);
3588 let _ = self.mark_dirty(id);
3589 Ok(id)
3590 }
3591
3592 pub(crate) fn retire_cell_for_replay(&mut self, addr: CellRef) {
3596 self.vacate_cell(&addr);
3599 self.forget_extent_cells(
3601 addr.sheet_id,
3602 (addr.coord.row(), addr.coord.row()),
3603 (addr.coord.col(), addr.coord.col()),
3604 );
3605 let _ = self.mark_dirty_cells(&[(addr.sheet_id, addr.coord.row(), addr.coord.col())]);
3606 }
3607
3608 pub(crate) fn note_extent_cell(&mut self, sheet: SheetId, row0: u32, col0: u32) {
3611 self.extent_record.note(sheet, row0, col0);
3612 }
3613
3614 pub(crate) fn forget_extent_cells(
3618 &mut self,
3619 sheet: SheetId,
3620 rows: (u32, u32),
3621 cols: (u32, u32),
3622 ) -> Vec<(u32, u32, u32)> {
3623 self.extent_record.forget_rect(sheet, rows, cols)
3624 }
3625
3626 pub(crate) fn had_legacy_cell_vertex(&self, cell: &CellRef) -> bool {
3629 self.extent_record
3630 .contains(cell.sheet_id, cell.coord.row(), cell.coord.col())
3631 }
3632
3633 pub(crate) fn extent_record_columns(&self, sheet: SheetId) -> Vec<u32> {
3635 self.extent_record.columns(sheet)
3636 }
3637
3638 #[cfg(test)]
3640 pub(crate) fn extent_record_runs(&self) -> usize {
3641 self.extent_record.run_count()
3642 }
3643
3644 pub(crate) fn revive_retired_id(&mut self, addr: &CellRef) -> Option<VertexId> {
3647 if self.retired_ids.is_empty() {
3648 return None;
3649 }
3650 let key = (addr.sheet_id, addr.coord.row(), addr.coord.col());
3651 let id = *self.retired_ids.get(&key)?;
3652 let coord = GridAddr::new(key.1, key.2);
3653 if let Some(x) = self.cell_vertex(addr)
3656 && !self.store.is_deleted(x)
3657 && self.store.grid_addr(x) == Some(coord)
3658 {
3659 return None;
3660 }
3661 self.retired_ids.remove(&key);
3662 self.revive_vertex(id, addr.sheet_id, coord).then_some(id)
3663 }
3664
3665 pub(crate) fn retired_id_count(&self) -> usize {
3667 self.retired_ids.len()
3668 }
3669
3670 pub(crate) fn shift_retired_ids(
3676 &mut self,
3677 op: &crate::engine::graph::editor::reference_adjuster::ShiftOperation,
3678 journal: bool,
3679 ) {
3680 use crate::engine::graph::editor::reference_adjuster::ShiftOperation as Op;
3681 let dropped = self.extent_record.shift(op);
3682 if journal && matches!(*op, Op::DeleteRows { .. } | Op::DeleteColumns { .. }) {
3683 self.extent_dropped.push(dropped);
3684 }
3685 self.shift_retired_id_table(op, journal);
3686 }
3687
3688 fn shift_retired_id_table(
3690 &mut self,
3691 op: &crate::engine::graph::editor::reference_adjuster::ShiftOperation,
3692 journal: bool,
3693 ) {
3694 use crate::engine::graph::editor::reference_adjuster::ShiftOperation as Op;
3695 let deleting = matches!(*op, Op::DeleteRows { .. } | Op::DeleteColumns { .. });
3696 if self.retired_ids.is_empty() {
3697 if deleting && journal {
3698 self.retired_dropped.push(Vec::new());
3699 }
3700 return;
3701 }
3702 let (sheet, rows, start, count, insert) = match *op {
3703 Op::InsertRows {
3704 sheet_id,
3705 before,
3706 count,
3707 } => (sheet_id, true, before, count, true),
3708 Op::DeleteRows {
3709 sheet_id,
3710 start,
3711 count,
3712 } => (sheet_id, true, start, count, false),
3713 Op::InsertColumns {
3714 sheet_id,
3715 before,
3716 count,
3717 } => (sheet_id, false, before, count, true),
3718 Op::DeleteColumns {
3719 sheet_id,
3720 start,
3721 count,
3722 } => (sheet_id, false, start, count, false),
3723 };
3724 let entries: Vec<((SheetId, u32, u32), VertexId)> = self
3725 .retired_ids
3726 .range((sheet, 0, 0)..=(sheet, u32::MAX, u32::MAX))
3727 .map(|(k, v)| (*k, *v))
3728 .collect();
3729 for (key, _) in &entries {
3730 self.retired_ids.remove(key);
3731 }
3732 let mut dropped = Vec::new();
3733 for ((s, r, c), id) in entries {
3734 let pos = if rows { r } else { c };
3735 let moved = if pos < start {
3736 Some(pos)
3737 } else if insert {
3738 pos.checked_add(count)
3739 } else if pos < start.saturating_add(count) {
3740 None
3741 } else {
3742 Some(pos - count)
3743 };
3744 match moved {
3745 Some(p) => {
3746 let key = if rows { (s, p, c) } else { (s, r, p) };
3747 self.retired_ids.insert(key, id);
3748 }
3749 None => dropped.push(((s, r, c), id)),
3750 }
3751 }
3752 if !insert && journal {
3753 self.retired_dropped.push(dropped);
3755 }
3756 }
3757
3758 fn restore_retired_batch(&mut self, batch: RetiredBatch) {
3760 for (key, id) in batch {
3761 self.retired_ids.insert(key, id);
3762 }
3763 }
3764
3765 pub(crate) fn replay_structural_marker(&mut self, description: &str, forward: bool) {
3772 use crate::engine::graph::editor::reference_adjuster::ShiftOperation as Op;
3773 let Some(op) = parse_structural_description(description) else {
3774 return;
3775 };
3776 if forward {
3777 self.shift_retired_ids(&op, true);
3778 if matches!(op, Op::InsertRows { .. } | Op::InsertColumns { .. })
3779 && let Some(batch) = self.retired_dropped_by_undo.pop()
3780 {
3781 self.restore_retired_batch(batch);
3783 }
3784 return;
3785 }
3786 let (inverse, band) = match op {
3787 Op::InsertRows {
3788 sheet_id,
3789 before,
3790 count,
3791 } => (
3792 Op::DeleteRows {
3793 sheet_id,
3794 start: before,
3795 count,
3796 },
3797 None,
3798 ),
3799 Op::InsertColumns {
3800 sheet_id,
3801 before,
3802 count,
3803 } => (
3804 Op::DeleteColumns {
3805 sheet_id,
3806 start: before,
3807 count,
3808 },
3809 None,
3810 ),
3811 Op::DeleteRows {
3812 sheet_id,
3813 start,
3814 count,
3815 } => (
3816 Op::InsertRows {
3817 sheet_id,
3818 before: start,
3819 count,
3820 },
3821 Some((sheet_id, true, start, count)),
3822 ),
3823 Op::DeleteColumns {
3824 sheet_id,
3825 start,
3826 count,
3827 } => (
3828 Op::InsertColumns {
3829 sheet_id,
3830 before: start,
3831 count,
3832 },
3833 Some((sheet_id, false, start, count)),
3834 ),
3835 };
3836 if self.extent_undone.last() == Some(&shift_key(&op)) {
3839 self.extent_undone.pop();
3840 } else {
3841 self.shift_extent_back(&op, &inverse);
3842 }
3843 self.shift_retired_id_table(&inverse, true);
3846 if band.is_none() {
3847 if let Some(batch) = self.retired_dropped.pop() {
3848 self.retired_dropped_by_undo.push(batch);
3849 }
3850 return;
3851 }
3852 if let Some((sheet, rows, start, count)) = band {
3854 if count == 0 {
3855 return;
3856 }
3857 if let Some(batch) = self.retired_dropped.pop() {
3859 self.restore_retired_batch(batch);
3860 }
3861 let end = start.saturating_add(count - 1);
3862 let rect = if rows {
3864 (sheet, start, end, 0, 16_383)
3865 } else {
3866 (sheet, 0, 1_048_575, start, end)
3867 };
3868 let _ = self.mark_dirty_rects(&[rect]);
3869 }
3870 }
3871
3872 pub(crate) fn undo_structural_extent(&mut self, description: &str) {
3879 use crate::engine::graph::editor::reference_adjuster::ShiftOperation as Op;
3880 let Some(op) = parse_structural_description(description) else {
3881 return;
3882 };
3883 let inverse = match op {
3884 Op::InsertRows {
3885 sheet_id,
3886 before,
3887 count,
3888 } => Op::DeleteRows {
3889 sheet_id,
3890 start: before,
3891 count,
3892 },
3893 Op::InsertColumns {
3894 sheet_id,
3895 before,
3896 count,
3897 } => Op::DeleteColumns {
3898 sheet_id,
3899 start: before,
3900 count,
3901 },
3902 Op::DeleteRows {
3903 sheet_id,
3904 start,
3905 count,
3906 } => Op::InsertRows {
3907 sheet_id,
3908 before: start,
3909 count,
3910 },
3911 Op::DeleteColumns {
3912 sheet_id,
3913 start,
3914 count,
3915 } => Op::InsertColumns {
3916 sheet_id,
3917 before: start,
3918 count,
3919 },
3920 };
3921 self.shift_extent_back(&op, &inverse);
3922 self.extent_undone.push(shift_key(&op));
3923 }
3924
3925 pub(crate) fn clear_extent_undone(&mut self) {
3927 self.extent_undone.clear();
3928 }
3929
3930 fn shift_extent_back(
3933 &mut self,
3934 op: &crate::engine::graph::editor::reference_adjuster::ShiftOperation,
3935 inverse: &crate::engine::graph::editor::reference_adjuster::ShiftOperation,
3936 ) {
3937 use crate::engine::graph::editor::reference_adjuster::ShiftOperation as Op;
3938 let _ = self.extent_record.shift(inverse);
3939 if matches!(*op, Op::DeleteRows { .. } | Op::DeleteColumns { .. })
3940 && let Some(dropped) = self.extent_dropped.pop()
3941 {
3942 self.extent_record.restore(dropped);
3943 }
3944 }
3945
3946 #[cfg(test)]
3948 pub(crate) fn extent_record_pending(&self) -> usize {
3949 self.extent_record.pending_len()
3950 }
3951
3952 #[cfg(test)]
3955 pub(crate) fn extent_history_counts(&self) -> (usize, usize) {
3956 (
3957 self.extent_dropped.len(),
3958 self.extent_dropped.iter().map(Vec::len).sum(),
3959 )
3960 }
3961
3962 pub(crate) fn drop_retired_ids_of_sheet(&mut self, sheet: SheetId) {
3964 self.extent_record.drop_sheet(sheet);
3965 let keys: Vec<(SheetId, u32, u32)> = self
3966 .retired_ids
3967 .range((sheet, 0, 0)..=(sheet, u32::MAX, u32::MAX))
3968 .map(|(k, _)| *k)
3969 .collect();
3970 for key in keys {
3971 if let Some(id) = self.retired_ids.remove(&key) {
3972 self.vertex_journal.retired(key, id.0);
3973 }
3974 }
3975 }
3976
3977 pub(crate) fn resolve_direct_deps(
3980 &mut self,
3981 cells: &[CellRef],
3982 ) -> (Vec<VertexId>, Vec<CellRef>) {
3983 let mut vertices: Vec<VertexId> = Vec::with_capacity(cells.len());
3984 let mut vertexless: Vec<CellRef> = Vec::new();
3985 for cell in cells {
3986 match self.dep_vertex(cell) {
3987 Some(v) => {
3988 if !vertices.contains(&v) {
3989 vertices.push(v);
3990 }
3991 }
3992 None => {
3993 if !vertexless.iter().any(|c| same_cell(c, cell)) {
3994 vertexless.push(*cell);
3995 }
3996 }
3997 }
3998 }
3999 (vertices, vertexless)
4000 }
4001
4002 fn get_or_create_vertex(
4003 &mut self,
4004 addr: &CellRef,
4005 created_placeholders: &mut Vec<CellRef>,
4006 ) -> VertexId {
4007 if let Some(vertex_id) = self.cell_vertex(addr) {
4008 return vertex_id;
4009 }
4010 if let Some(vertex_id) = self.revive_retired_id(addr) {
4012 return vertex_id;
4013 }
4014
4015 if self.first_load_assume_new {
4020 let packed = Self::packed_cell_key(
4021 addr.sheet_id,
4022 AbsCoord::new(addr.coord.row(), addr.coord.col()),
4023 );
4024 if let Some(&existing) = self.load_packed_to_vertex.get(&packed) {
4025 self.cell_to_vertex.insert(*addr, existing);
4026 return existing;
4027 }
4028 }
4029
4030 created_placeholders.push(*addr);
4031 let position = GridAddr::new(addr.coord.row(), addr.coord.col());
4032 let vertex_id = self
4033 .store
4034 .allocate(VertexAddr::grid(position), addr.sheet_id, 0x00);
4035
4036 #[cfg(any(test, feature = "legacy_oracle"))]
4037 {
4038 self.edges
4039 .add_vertex(VertexAddr::grid(position), vertex_id.0);
4040 self.oracle_cell_vertex_created(
4041 (addr.sheet_id, position.row(), position.col()),
4042 vertex_id,
4043 );
4044 }
4045
4046 self.sheet_index_mut(addr.sheet_id)
4048 .add_vertex(position, vertex_id);
4049
4050 self.store.set_kind(vertex_id, VertexKind::Empty);
4051 self.cell_to_vertex.insert(*addr, vertex_id);
4052 vertex_id
4053 }
4054
4055 pub(crate) fn note_vertexless_deps(
4060 &mut self,
4061 dependent: VertexId,
4062 cells: impl IntoIterator<Item = (SheetId, u32, u32)>,
4063 ) {
4064 let mut n = 0usize;
4065 #[cfg(any(test, feature = "legacy_oracle"))]
4066 let mut keys = Vec::new();
4067 for cell in cells {
4068 n += 1;
4069 self.extent_record.note(cell.0, cell.1, cell.2);
4071 #[cfg(any(test, feature = "legacy_oracle"))]
4072 {
4073 self.oracle_vertexless_readers
4074 .entry(cell)
4075 .or_default()
4076 .push(dependent);
4077 keys.push(cell);
4078 }
4079 #[cfg(not(any(test, feature = "legacy_oracle")))]
4080 let _ = cell;
4081 }
4082 #[cfg(any(test, feature = "legacy_oracle"))]
4083 if !keys.is_empty() {
4084 self.oracle_vertexless_of
4085 .entry(dependent)
4086 .or_default()
4087 .extend(keys);
4088 }
4089 self.note_dep_edges(dependent, n);
4090 }
4091
4092 #[cfg(any(test, feature = "legacy_oracle"))]
4095 pub(crate) fn oracle_cell_vertex_created(&mut self, cell: (SheetId, u32, u32), v: VertexId) {
4096 if self.oracle_vertexless_readers.is_empty() {
4097 return;
4098 }
4099 let Some(readers) = self.oracle_vertexless_readers.remove(&cell) else {
4100 return;
4101 };
4102 for reader in readers {
4103 if let Some(cells) = self.oracle_vertexless_of.get_mut(&reader) {
4104 cells.retain(|c| *c != cell);
4105 if cells.is_empty() {
4106 self.oracle_vertexless_of.remove(&reader);
4107 }
4108 }
4109 self.oracle_add_dependent_edges(reader, &[v]);
4110 }
4111 }
4112
4113 #[cfg(any(test, feature = "legacy_oracle"))]
4116 pub(crate) fn oracle_vertexless_readers_of(
4117 &self,
4118 cell: crate::engine::authority::geom::Cell,
4119 ) -> Vec<VertexId> {
4120 self.oracle_vertexless_readers
4121 .get(&(cell.0 as SheetId, cell.1, cell.2))
4122 .cloned()
4123 .unwrap_or_default()
4124 }
4125
4126 #[cfg(any(test, feature = "legacy_oracle"))]
4129 pub(crate) fn oracle_vertexless_cells(&self, dependent: VertexId) -> Vec<CellRef> {
4130 self.oracle_vertexless_of
4131 .get(&dependent)
4132 .map(|cells| {
4133 cells
4134 .iter()
4135 .map(|&(s, r, c)| CellRef::new(s, Coord::new(r, c, true, true)))
4136 .collect()
4137 })
4138 .unwrap_or_default()
4139 }
4140
4141 #[cfg(any(test, feature = "legacy_oracle"))]
4142 fn oracle_forget_vertexless(&mut self, dependent: VertexId) {
4143 if let Some(cells) = self.oracle_vertexless_of.remove(&dependent) {
4144 for cell in cells {
4145 if let Some(readers) = self.oracle_vertexless_readers.get_mut(&cell) {
4146 readers.retain(|r| *r != dependent);
4147 if readers.is_empty() {
4148 self.oracle_vertexless_readers.remove(&cell);
4149 }
4150 }
4151 }
4152 }
4153 }
4154
4155 pub(crate) fn note_dep_edges(&mut self, dependent: VertexId, n: usize) {
4157 let now = self.store.edge_offset(dependent) as usize + n;
4158 self.store
4159 .set_edge_offset(dependent, u32::try_from(now).unwrap_or(u32::MAX));
4160 self.dep_edge_total += n;
4161 }
4162
4163 pub(crate) fn renamed_sheet_alias(&self, name: &str) -> Option<SheetId> {
4166 self.renamed_sheet_aliases
4167 .get(&name.to_ascii_lowercase())
4168 .copied()
4169 .filter(|&id| self.sheet_reg.name(id) != name)
4170 }
4171
4172 pub(crate) fn reads_compressed_range(&self, vertex: VertexId) -> bool {
4174 self.store.reads_range(vertex)
4175 }
4176
4177 pub(crate) fn note_reads_range(&mut self, vertex: VertexId) {
4179 if !self.store.reads_range(vertex) {
4180 self.store.set_reads_range(vertex, true);
4181 self.range_reader_count += 1;
4182 }
4183 }
4184
4185 pub(crate) fn has_compressed_range_readers(&self) -> bool {
4187 self.range_reader_count > 0
4188 }
4189
4190 pub(crate) fn forget_dep_edges(&mut self, vertex: VertexId) {
4192 let n = self.store.edge_offset(vertex) as usize;
4193 if n > 0 {
4194 self.store.set_edge_offset(vertex, 0);
4195 self.dep_edge_total = self.dep_edge_total.saturating_sub(n);
4196 }
4197 }
4198
4199 fn add_dependent_edges(&mut self, dependent: VertexId, dependencies: &[VertexId]) {
4200 self.note_dep_edges(dependent, dependencies.len());
4201 #[cfg(any(test, feature = "legacy_oracle"))]
4202 self.oracle_add_dependent_edges(dependent, dependencies);
4203 }
4204
4205 #[cfg(any(test, feature = "legacy_oracle"))]
4207 fn oracle_add_dependent_edges(&mut self, dependent: VertexId, dependencies: &[VertexId]) {
4208 #[cfg(any(test, feature = "legacy_oracle"))]
4210 self.edges.begin_batch();
4211
4212 let mut skip_deps: rustc_hash::FxHashSet<VertexId> = rustc_hash::FxHashSet::default();
4215 if self.pk_order.is_some()
4216 && let Some(mut pk) = self.pk_order.take()
4217 {
4218 pk.ensure_nodes(std::iter::once(dependent));
4219 pk.ensure_nodes(dependencies.iter().copied());
4220 {
4221 let adapter = GraphAdapter { g: self };
4222 for &dep_id in dependencies {
4223 match pk.try_add_edge(&adapter, dep_id, dependent) {
4224 Ok(_) => {}
4225 Err(_cycle) => {
4226 if self.config.pk_reject_cycle_edges {
4227 skip_deps.insert(dep_id);
4228 } else {
4229 pk.rebuild_full(&adapter);
4230 }
4231 }
4232 }
4233 }
4234 } self.pk_order = Some(pk);
4236 }
4237
4238 for &dep_id in dependencies {
4240 if self.config.pk_reject_cycle_edges && skip_deps.contains(&dep_id) {
4241 continue;
4242 }
4243 self.edges.add_edge(dependent, dep_id);
4244 #[cfg(test)]
4245 {
4246 if let Ok(mut g) = self.instr.lock() {
4247 g.edges_added += 1;
4248 }
4249 }
4250 }
4251
4252 #[cfg(any(test, feature = "legacy_oracle"))]
4253 self.edges.end_batch();
4254 }
4255
4256 fn add_dependent_edges_nobatch(&mut self, dependent: VertexId, dependencies: &[VertexId]) {
4258 self.note_dep_edges(dependent, dependencies.len());
4259 #[cfg(any(test, feature = "legacy_oracle"))]
4260 {
4261 let mut skip_deps: rustc_hash::FxHashSet<VertexId> = rustc_hash::FxHashSet::default();
4263 if self.pk_order.is_some()
4264 && let Some(mut pk) = self.pk_order.take()
4265 {
4266 pk.ensure_nodes(std::iter::once(dependent));
4267 pk.ensure_nodes(dependencies.iter().copied());
4268 {
4269 let adapter = GraphAdapter { g: self };
4270 for &dep_id in dependencies {
4271 match pk.try_add_edge(&adapter, dep_id, dependent) {
4272 Ok(_) => {}
4273 Err(_cycle) => {
4274 if self.config.pk_reject_cycle_edges {
4275 skip_deps.insert(dep_id);
4276 } else {
4277 pk.rebuild_full(&adapter);
4278 }
4279 }
4280 }
4281 }
4282 }
4283 self.pk_order = Some(pk);
4284 }
4285
4286 for &dep_id in dependencies {
4287 if self.config.pk_reject_cycle_edges && skip_deps.contains(&dep_id) {
4288 continue;
4289 }
4290 self.edges.add_edge(dependent, dep_id);
4291 #[cfg(test)]
4292 {
4293 if let Ok(mut g) = self.instr.lock() {
4294 g.edges_added += 1;
4295 }
4296 }
4297 }
4298 }
4299 }
4300
4301 pub fn bulk_set_formulas<I>(&mut self, sheet: &str, items: I) -> Result<usize, ExcelError>
4303 where
4304 I: IntoIterator<Item = (u32, u32, ASTNode)>,
4305 {
4306 let collected: Vec<(u32, u32, ASTNode)> = items.into_iter().collect();
4307 if collected.is_empty() {
4308 return Ok(0);
4309 }
4310 let vol_flags: Vec<bool> = collected
4311 .iter()
4312 .map(|(_, _, ast)| self.is_ast_volatile(ast))
4313 .collect();
4314 self.bulk_set_formulas_with_volatility(sheet, collected, vol_flags)
4315 }
4316
4317 pub fn bulk_set_formulas_with_volatility(
4318 &mut self,
4319 sheet: &str,
4320 collected: Vec<(u32, u32, ASTNode)>,
4321 _vol_flags: Vec<bool>,
4322 ) -> Result<usize, ExcelError> {
4323 let sheet_id = self.sheet_id_mut(sheet);
4324 if collected.is_empty() {
4325 return Ok(0);
4326 }
4327 let provider = RegistryFunctionProvider;
4328 let ingested = {
4329 let mut pipeline = self.ingest_pipeline(&provider);
4330 let inputs = collected.into_iter().map(|(row, col, ast)| {
4331 let placement = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
4332 (FormulaAstInput::Tree(ast), placement, None)
4333 });
4334 pipeline.ingest_batch(inputs)?
4335 };
4336 let planned = ingested
4337 .into_iter()
4338 .map(|formula| {
4339 (
4340 formula.placement.coord.row() + 1,
4341 formula.placement.coord.col() + 1,
4342 formula.ast_id,
4343 formula.dep_plan,
4344 )
4345 })
4346 .collect();
4347 self.bulk_set_formulas_with_plans(sheet, planned)
4348 }
4349
4350 pub(crate) fn bulk_set_formulas_with_plans(
4351 &mut self,
4352 sheet: &str,
4353 planned: Vec<(u32, u32, AstNodeId, DependencyPlanRow)>,
4354 ) -> Result<usize, ExcelError> {
4355 let sheet_id = self.sheet_id_mut(sheet);
4356 if planned.is_empty() {
4357 return Ok(0);
4358 }
4359 let budgets = self.self_admission_budgets();
4360 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
4361 let admission_plans = planned
4362 .iter()
4363 .map(|(row, col, _, plan)| (sheet_id, *row, *col, plan.clone()))
4364 .collect::<Vec<_>>();
4365 let usage = self.preview_formula_mutations(&admission_plans)?;
4366 crate::engine::resource_ledger::preflight_graph_admission(&budgets, usage, None)
4367 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
4368 }
4369 let mut created_placeholders: Vec<CellRef> = Vec::new();
4370 let mut target_vids: Vec<VertexId> = Vec::with_capacity(planned.len());
4371 for (row, col, _, _) in &planned {
4372 let addr = CellRef::new(sheet_id, Coord::from_excel(*row, *col, true, true));
4373 let target = self.get_or_create_vertex(&addr, &mut created_placeholders);
4374 self.materialize_vertex(target);
4375 target_vids.push(target);
4376 }
4377
4378 for (i, &tvid) in target_vids.iter().enumerate() {
4379 self.forget_declared_dynamic_anchor(tvid);
4380 if self.vertex_formulas.contains_key(&tvid) {
4381 self.remove_dependent_edges(tvid);
4382 }
4383 self.detach_vertex_from_names(tvid);
4384 self.clear_pending_name_references(tvid);
4385 self.store.set_kind(tvid, VertexKind::FormulaScalar);
4386 self.store.set_dirty(tvid, true);
4387 self.vertex_values.remove(&tvid);
4388 self.vertex_formulas.insert(tvid, planned[i].2);
4389 self.mark_volatile(tvid, planned[i].3.volatile);
4390 self.store.set_dynamic(tvid, planned[i].3.dynamic);
4391 }
4392 self.formula_dirty
4393 .legacy_extend(target_vids.iter().copied());
4394
4395 #[cfg(any(test, feature = "legacy_oracle"))]
4396 self.edges.begin_batch();
4397 for (i, tvid) in target_vids.iter().copied().enumerate() {
4398 let plan = &planned[i].3;
4399 let (mut deps, vertexless) = self.resolve_direct_deps(&plan.direct_cell_deps);
4400 self.note_vertexless_deps(
4401 tvid,
4402 vertexless
4403 .iter()
4404 .map(|c| (c.sheet_id, c.coord.row(), c.coord.col())),
4405 );
4406
4407 let mut name_vertices = Vec::new();
4408 for name in plan
4409 .resolved_named_refs
4410 .iter()
4411 .chain(plan.named_refs.iter())
4412 {
4413 if let Some(named) = self.resolve_name_entry(name, sheet_id) {
4414 if !deps.contains(&named.vertex) {
4415 deps.push(named.vertex);
4416 }
4417 if !name_vertices.contains(&named.vertex) {
4418 name_vertices.push(named.vertex);
4419 }
4420 } else if let Some(source) = self.resolve_source_scalar_entry(name) {
4421 if !deps.contains(&source.vertex) {
4422 deps.push(source.vertex);
4423 }
4424 } else {
4425 self.record_pending_name_reference(sheet_id, name, tvid);
4426 }
4427 }
4428 for source_name in &plan.source_refs {
4429 if let Some(source) = self.resolve_source_scalar_entry(source_name) {
4430 if !deps.contains(&source.vertex) {
4431 deps.push(source.vertex);
4432 }
4433 } else if let Some(source) = self.resolve_source_table_entry(source_name)
4434 && !deps.contains(&source.vertex)
4435 {
4436 deps.push(source.vertex);
4437 }
4438 }
4439 for table_name in &plan.table_refs {
4440 if let Some(table) = self.resolve_table_entry(table_name) {
4441 if !deps.contains(&table.vertex) {
4442 deps.push(table.vertex);
4443 }
4444 } else if let Some(source) = self.resolve_source_table_entry(table_name)
4445 && !deps.contains(&source.vertex)
4446 {
4447 deps.push(source.vertex);
4448 }
4449 }
4450 if !name_vertices.is_empty() {
4451 self.attach_vertex_to_names(tvid, &name_vertices);
4452 }
4453 if !deps.is_empty() {
4454 self.add_dependent_edges_nobatch(tvid, &deps);
4455 }
4456 self.add_range_dependent_edges(tvid, &plan.range_deps, sheet_id);
4457 }
4458 #[cfg(any(test, feature = "legacy_oracle"))]
4459 self.edges.end_batch();
4460
4461 let written = planned.len();
4470 let mut cells: Vec<(u32, u32)> = planned
4471 .iter()
4472 .map(|(row, col, _, _)| (col.saturating_sub(1), row.saturating_sub(1)))
4473 .collect();
4474 drop(planned);
4475 cells.sort_unstable();
4476 cells.dedup();
4477 let mut runs: Vec<(SheetId, u32, u32, u32, u32)> = Vec::new();
4478 for (col, row) in cells {
4479 match runs.last_mut() {
4480 Some((_, _, r1, c0, _)) if *c0 == col && *r1 + 1 == row => *r1 = row,
4481 _ => runs.push((sheet_id, row, row, col, col)),
4482 }
4483 }
4484 self.mark_dirty_rects(&runs);
4485
4486 Ok(written)
4487 }
4488
4489 #[cfg(any(test, feature = "legacy_oracle"))]
4490 pub fn add_dependency_edge(
4492 &mut self,
4493 dependent: VertexId,
4494 dependency: VertexId,
4495 ) -> Result<(), ExcelError> {
4496 if dependent == dependency {
4497 return Ok(());
4498 }
4499 let budgets = self.self_admission_budgets();
4500 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
4501 let stats = self.baseline_stats();
4502 let added = usize::from(!self.get_dependencies(dependent).contains(&dependency));
4503 crate::engine::resource_ledger::preflight_graph_admission(
4504 &budgets,
4505 crate::engine::resource_ledger::GraphAdmission {
4506 final_vertices: stats.graph_vertex_count,
4507 final_edges: stats.graph_edge_count.checked_add(added).ok_or_else(|| {
4508 ExcelError::new(ExcelErrorKind::NImpl)
4509 .with_message("graph edge count overflow")
4510 })?,
4511 materialization_cells: 0,
4512 added_vertices: 0,
4513 added_edges: added,
4514 },
4515 None,
4516 )
4517 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
4518 }
4519 if self.pk_order.is_some()
4521 && let Some(mut pk) = self.pk_order.take()
4522 {
4523 pk.ensure_nodes(std::iter::once(dependent));
4524 pk.ensure_nodes(std::iter::once(dependency));
4525 let adapter = GraphAdapter { g: self };
4526 if pk.try_add_edge(&adapter, dependency, dependent).is_err() {
4527 pk.rebuild_full(&adapter);
4529 }
4530 self.pk_order = Some(pk);
4531 }
4532 self.edges.add_edge(dependent, dependency);
4533 self.store.set_dirty(dependent, true);
4534 self.formula_dirty.legacy_insert(dependent);
4535 Ok(())
4536 }
4537
4538 fn remove_dependent_edges(&mut self, vertex: VertexId) {
4539 self.forget_dep_edges(vertex);
4540 if self.store.reads_range(vertex) {
4541 self.store.set_reads_range(vertex, false);
4542 self.range_reader_count = self.range_reader_count.saturating_sub(1);
4543 }
4544 #[cfg(any(test, feature = "legacy_oracle"))]
4545 self.oracle_remove_dependent_edges(vertex);
4546 }
4547
4548 #[cfg(any(test, feature = "legacy_oracle"))]
4550 fn oracle_remove_dependent_edges(&mut self, vertex: VertexId) {
4551 self.oracle_forget_vertexless(vertex);
4552 let dependencies = self.edges.out_edges(vertex);
4554
4555 #[cfg(any(test, feature = "legacy_oracle"))]
4556 self.edges.begin_batch();
4557 if self.pk_order.is_some()
4558 && let Some(mut pk) = self.pk_order.take()
4559 {
4560 for dep in &dependencies {
4561 pk.remove_edge(*dep, vertex);
4562 }
4563 self.pk_order = Some(pk);
4564 }
4565 for dep in dependencies {
4566 self.edges.remove_edge(vertex, dep);
4567 }
4568 #[cfg(any(test, feature = "legacy_oracle"))]
4569 self.edges.end_batch();
4570
4571 if let Some(old_ranges) = self.formula_to_range_deps.remove(&vertex) {
4573 let old_sheet_id = self.store.sheet_id(vertex);
4574
4575 for range in &old_ranges {
4576 let sheet_id = self
4578 .sheet_reg
4579 .resolve_locator(&range.sheet, old_sheet_id)
4580 .unwrap_or(old_sheet_id);
4581 let s_row = range.start_row.map(|b| b.index);
4582 let e_row = range.end_row.map(|b| b.index);
4583 let s_col = range.start_col.map(|b| b.index);
4584 let e_col = range.end_col.map(|b| b.index);
4585
4586 let mut keys_to_clean = FxHashSet::default();
4587
4588 let col_stripes = (s_row.is_none() && e_row.is_none())
4589 || (s_col.is_some() && e_col.is_some() && (s_row.is_none() || e_row.is_none()));
4590 let row_stripes = (s_col.is_none() && e_col.is_none())
4591 || (s_row.is_some() && e_row.is_some() && (s_col.is_none() || e_col.is_none()));
4592
4593 if col_stripes && !row_stripes {
4594 let sc = s_col.unwrap_or(0);
4595 let ec = e_col.unwrap_or(sc);
4596 for col in sc..=ec {
4597 keys_to_clean.insert(StripeKey {
4598 sheet_id,
4599 stripe_type: StripeType::Column,
4600 index: col,
4601 });
4602 }
4603 } else if row_stripes && !col_stripes {
4604 let sr = s_row.unwrap_or(0);
4605 let er = e_row.unwrap_or(sr);
4606 for row in sr..=er {
4607 keys_to_clean.insert(StripeKey {
4608 sheet_id,
4609 stripe_type: StripeType::Row,
4610 index: row,
4611 });
4612 }
4613 } else {
4614 let start_row = s_row.unwrap_or(0);
4615 let start_col = s_col.unwrap_or(0);
4616 let end_row = e_row.unwrap_or(start_row);
4617 let end_col = e_col.unwrap_or(start_col);
4618
4619 let height = end_row.saturating_sub(start_row) + 1;
4620 let width = end_col.saturating_sub(start_col) + 1;
4621
4622 if self.config.enable_block_stripes && height > 1 && width > 1 {
4623 let start_block_row = start_row / BLOCK_H;
4624 let end_block_row = end_row / BLOCK_H;
4625 let start_block_col = start_col / BLOCK_W;
4626 let end_block_col = end_col / BLOCK_W;
4627
4628 for block_row in start_block_row..=end_block_row {
4629 for block_col in start_block_col..=end_block_col {
4630 keys_to_clean.insert(StripeKey {
4631 sheet_id,
4632 stripe_type: StripeType::Block,
4633 index: block_index(block_row * BLOCK_H, block_col * BLOCK_W),
4634 });
4635 }
4636 }
4637 } else if height > width {
4638 for col in start_col..=end_col {
4639 keys_to_clean.insert(StripeKey {
4640 sheet_id,
4641 stripe_type: StripeType::Column,
4642 index: col,
4643 });
4644 }
4645 } else {
4646 for row in start_row..=end_row {
4647 keys_to_clean.insert(StripeKey {
4648 sheet_id,
4649 stripe_type: StripeType::Row,
4650 index: row,
4651 });
4652 }
4653 }
4654 }
4655
4656 for key in keys_to_clean {
4657 if let Some(dependents) = self.stripe_to_dependents.get_mut(&key) {
4658 dependents.remove(&vertex);
4659 if dependents.is_empty() {
4660 self.stripe_to_dependents.remove(&key);
4661 #[cfg(test)]
4662 {
4663 if let Ok(mut g) = self.instr.lock() {
4664 g.stripe_removes += 1;
4665 }
4666 }
4667 }
4668 }
4669 }
4670 }
4671 }
4672 }
4673
4674 pub(crate) fn has_spill_anchors(&self) -> bool {
4681 !self.spill_anchor_to_cells.is_empty()
4682 }
4683
4684 pub(crate) fn is_spill_anchor(&self, vertex: VertexId) -> bool {
4686 self.spill_anchor_to_cells.contains_key(&vertex)
4687 }
4688
4689 pub(crate) fn update_vertex_value_ref(&mut self, vertex_id: VertexId, value: &LiteralValue) {
4692 if !self.value_cache_enabled && self.is_grid_backed(vertex_id) {
4693 if !self.vertex_values.is_empty() {
4694 self.vertex_values.remove(&vertex_id);
4695 }
4696 return;
4697 }
4698 self.update_vertex_value(vertex_id, value.clone());
4699 }
4700
4701 pub(crate) fn update_vertex_value(&mut self, vertex_id: VertexId, value: LiteralValue) {
4702 if !self.value_cache_enabled && self.is_grid_backed(vertex_id) {
4703 self.vertex_values.remove(&vertex_id);
4706 return;
4707 }
4708 self.materialize_vertex(vertex_id);
4710 let value_ref = self.data_store.store_value(normalize_stored_literal(value));
4711 self.vertex_values.insert(vertex_id, value_ref);
4712 }
4713
4714 pub fn plan_spill_region(
4720 &self,
4721 anchor: VertexId,
4722 target_cells: &[CellRef],
4723 ) -> Result<(), ExcelError> {
4724 self.plan_spill_region_yielding(anchor, target_cells, &[])
4725 .map_err(|(error, _)| error)
4726 }
4727
4728 pub(crate) fn plan_spill_region_with_blocker(
4730 &self,
4731 anchor: VertexId,
4732 target_cells: &[CellRef],
4733 ) -> Result<(), (ExcelError, CellRef)> {
4734 self.plan_spill_region_yielding(anchor, target_cells, &[])
4735 }
4736
4737 fn plan_spill_region_yielding(
4740 &self,
4741 anchor: VertexId,
4742 target_cells: &[CellRef],
4743 yielding: &[VertexId],
4744 ) -> Result<(), (ExcelError, CellRef)> {
4745 use formualizer_common::{ExcelErrorExtra, ExcelErrorKind};
4746 let (expected_rows, expected_cols) = if target_cells.is_empty() {
4748 (0u32, 0u32)
4749 } else {
4750 let mut min_r = u32::MAX;
4751 let mut max_r = 0u32;
4752 let mut min_c = u32::MAX;
4753 let mut max_c = 0u32;
4754 for cell in target_cells {
4755 let r = cell.coord.row();
4756 let c = cell.coord.col();
4757 if r < min_r {
4758 min_r = r;
4759 }
4760 if r > max_r {
4761 max_r = r;
4762 }
4763 if c < min_c {
4764 min_c = c;
4765 }
4766 if c > max_c {
4767 max_c = c;
4768 }
4769 }
4770 (
4771 max_r.saturating_sub(min_r).saturating_add(1),
4772 max_c.saturating_sub(min_c).saturating_add(1),
4773 )
4774 };
4775 let probe_owned = self.spill_may_be_intruded(anchor);
4777 for cell in target_cells {
4779 let owned_by_anchor = match self.spill_cell_to_anchor.get(cell) {
4781 Some(&existing_anchor) if existing_anchor == anchor => true,
4782 Some(other) if yielding.contains(other) => false,
4783 Some(_other) => {
4784 return Err((
4785 ExcelError::new(ExcelErrorKind::Spill)
4786 .with_message("BlockedBySpill")
4787 .with_extra(ExcelErrorExtra::Spill {
4788 expected_rows,
4789 expected_cols,
4790 }),
4791 *cell,
4792 ));
4793 }
4794 None => false,
4795 };
4796
4797 if owned_by_anchor && !(probe_owned && self.is_foreign_formula_cell(cell, anchor)) {
4802 continue;
4803 }
4804
4805 if let Some(vid) = self.cell_vertex(cell)
4807 && vid != anchor
4808 {
4809 match self.store.kind(vid) {
4811 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
4812 return Err((
4813 ExcelError::new(ExcelErrorKind::Spill)
4814 .with_message("BlockedByFormula")
4815 .with_extra(ExcelErrorExtra::Spill {
4816 expected_rows,
4817 expected_cols,
4818 }),
4819 *cell,
4820 ));
4821 }
4822 _ => {
4823 if let Some(vref) = self.vertex_values.get(&vid) {
4825 let v = self.data_store.retrieve_value(*vref);
4826 if !matches!(v, LiteralValue::Empty) {
4827 return Err((
4828 ExcelError::new(ExcelErrorKind::Spill)
4829 .with_message("BlockedByValue")
4830 .with_extra(ExcelErrorExtra::Spill {
4831 expected_rows,
4832 expected_cols,
4833 }),
4834 *cell,
4835 ));
4836 }
4837 }
4838 }
4839 }
4840 }
4841 }
4842 Ok(())
4843 }
4844
4845 pub fn commit_spill_region_atomic_with_fault(
4858 &mut self,
4859 anchor: VertexId,
4860 target_cells: Vec<CellRef>,
4861 values: Vec<Vec<LiteralValue>>,
4862 fault_after_ops: Option<usize>,
4863 ) -> Result<(), ExcelError> {
4864 self.materialize_vertex(anchor);
4865 let budgets = self.self_admission_budgets();
4866 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
4867 let admission = self.preview_spill_materialization(&target_cells)?;
4868 crate::engine::resource_ledger::preflight_graph_admission(&budgets, admission, None)
4869 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
4870 }
4871
4872 let anchor_cell = self
4876 .get_cell_ref(anchor)
4877 .expect("anchor cell ref for spill commit");
4878 let anchor_sheet_name = self.sheet_name(anchor_cell.sheet_id).to_string();
4879 let anchor_row = anchor_cell.coord.row();
4880 let anchor_col = anchor_cell.coord.col();
4881
4882 let prev_cells = self
4884 .spill_anchor_to_cells
4885 .get(&anchor)
4886 .cloned()
4887 .unwrap_or_default();
4888 let new_set: std::collections::HashSet<CellRef, CoordBuildHasher> =
4891 target_cells.iter().copied().collect();
4892 let prev_set: std::collections::HashSet<CellRef, CoordBuildHasher> =
4893 prev_cells.iter().copied().collect();
4894
4895 #[derive(Clone)]
4897 struct Op {
4898 sheet: String,
4899 row: u32,
4900 col: u32,
4901 new_value: LiteralValue,
4902 }
4903 let mut ops: Vec<Op> = Vec::new();
4904
4905 let probe_owned = self.spill_may_be_intruded(anchor);
4908 let entered = |cell: &CellRef| probe_owned && self.is_foreign_formula_cell(cell, anchor);
4909 for cell in prev_cells.iter() {
4910 if !new_set.contains(cell) && !entered(cell) {
4911 let sheet = self.sheet_name(cell.sheet_id).to_string();
4912 ops.push(Op {
4913 sheet,
4914 row: cell.coord.row(),
4915 col: cell.coord.col(),
4916 new_value: LiteralValue::Empty,
4917 });
4918 }
4919 }
4920
4921 if !target_cells.is_empty() {
4923 let first = target_cells.first().copied().unwrap();
4924 let row0 = first.coord.row();
4925 let col0 = first.coord.col();
4926 let sheet = self.sheet_name(first.sheet_id).to_string();
4927 for (r_off, row_vals) in values.iter().enumerate() {
4928 for (c_off, v) in row_vals.iter().enumerate() {
4929 ops.push(Op {
4930 sheet: sheet.clone(),
4931 row: row0 + r_off as u32,
4932 col: col0 + c_off as u32,
4933 new_value: v.clone(),
4934 });
4935 }
4936 }
4937 }
4938
4939 #[derive(Clone)]
4941 struct OldVal {
4942 present: bool,
4943 value: LiteralValue,
4944 }
4945 let mut old_values: Vec<((String, u32, u32), OldVal)> = Vec::with_capacity(ops.len());
4946
4947 for op in &ops {
4949 let old = self
4951 .get_cell_value(&op.sheet, op.row + 1, op.col + 1)
4952 .unwrap_or(LiteralValue::Empty);
4953 let present = true; old_values.push((
4955 (op.sheet.clone(), op.row, op.col),
4956 OldVal {
4957 present,
4958 value: old,
4959 },
4960 ));
4961 }
4962
4963 for (applied, op) in ops.iter().enumerate() {
4965 if let Some(n) = fault_after_ops
4966 && applied == n
4967 {
4968 for idx in (0..applied).rev() {
4969 let ((ref sheet, row, col), ref old) = old_values[idx];
4970 if sheet == &anchor_sheet_name && row == anchor_row && col == anchor_col {
4971 self.update_vertex_value_ref(anchor, &old.value);
4972 } else {
4973 let _ = self.set_cell_value(sheet, row + 1, col + 1, old.value.clone());
4974 }
4975 }
4976 return Err(ExcelError::new(ExcelErrorKind::Error)
4977 .with_message("Injected persistence fault during spill commit"));
4978 }
4979 if op.sheet == anchor_sheet_name && op.row == anchor_row && op.col == anchor_col {
4980 self.update_vertex_value_ref(anchor, &op.new_value);
4981 } else {
4982 let _ =
4983 self.set_cell_value(&op.sheet, op.row + 1, op.col + 1, op.new_value.clone());
4984 }
4985 }
4986
4987 for cell in prev_cells.iter() {
4990 if !new_set.contains(cell) {
4991 self.spill_cell_to_anchor.remove(cell);
4992 let remove_sheet = self
4993 .spill_cells_by_sheet
4994 .get_mut(&cell.sheet_id)
4995 .is_some_and(|sheet| {
4996 sheet.remove(&(cell.coord.row(), cell.coord.col()));
4997 sheet.is_empty()
4998 });
4999 if remove_sheet {
5000 self.spill_cells_by_sheet.remove(&cell.sheet_id);
5001 }
5002 }
5003 }
5004 for cell in &target_cells {
5006 self.spill_cell_to_anchor.insert(*cell, anchor);
5007 self.spill_cells_by_sheet
5008 .entry(cell.sheet_id)
5009 .or_default()
5010 .insert((cell.coord.row(), cell.coord.col()), anchor);
5011 }
5012 if !self.spill_intruded_anchors.is_empty()
5016 && self.spill_intruded_anchors.contains(&anchor)
5017 && !target_cells
5018 .iter()
5019 .any(|cell| self.is_foreign_formula_cell(cell, anchor))
5020 {
5021 self.spill_intruded_anchors.remove(&anchor);
5022 }
5023 self.spill_anchor_to_cells.insert(anchor, target_cells);
5024 Ok(())
5025 }
5026
5027 pub(crate) fn spill_cells_for_anchor(&self, anchor: VertexId) -> Option<&[CellRef]> {
5028 self.spill_anchor_to_cells
5029 .get(&anchor)
5030 .map(|v| v.as_slice())
5031 }
5032
5033 pub(crate) fn spill_extent_for_anchor(&self, anchor: VertexId) -> Option<(CellRef, CellRef)> {
5043 let cells = self.spill_anchor_to_cells.get(&anchor)?;
5044 let first = *cells.first()?;
5045 let last = *cells.last()?;
5046 if last.sheet_id != first.sheet_id
5047 || last.coord.row() < first.coord.row()
5048 || last.coord.col() < first.coord.col()
5049 {
5050 return None;
5051 }
5052 let rows = (last.coord.row() - first.coord.row()) as usize + 1;
5053 let cols = (last.coord.col() - first.coord.col()) as usize + 1;
5054 if rows.checked_mul(cols) != Some(cells.len()) {
5055 return None;
5056 }
5057 Some((first, last))
5058 }
5059
5060 pub(crate) fn spill_registry_has_anchor(&self, anchor: VertexId) -> bool {
5061 self.spill_anchor_to_cells.contains_key(&anchor)
5062 }
5063
5064 pub(crate) fn spill_registry_anchor_for_cell(&self, cell: CellRef) -> Option<VertexId> {
5065 self.spill_cell_to_anchor.get(&cell).copied()
5066 }
5067
5068 pub(crate) fn spill_registry_counts(&self) -> (usize, usize) {
5069 (
5070 self.spill_anchor_to_cells.len(),
5071 self.spill_cell_to_anchor.len(),
5072 )
5073 }
5074
5075 pub(crate) fn declare_dynamic_anchor(&mut self, anchor: VertexId, cell: CellRef) -> bool {
5078 let cell = CellRef::new(
5079 cell.sheet_id,
5080 Coord::new(cell.coord.row(), cell.coord.col(), true, true),
5081 );
5082 self.declared_dynamic_anchors.insert(anchor, cell) != Some(cell)
5083 }
5084
5085 #[inline]
5088 pub(crate) fn forget_declared_dynamic_anchor(&mut self, vertex: VertexId) {
5089 if !self.fixed_single_arrays.is_empty() {
5090 self.fixed_single_arrays.remove(&vertex);
5091 }
5092 if !self.fixed_array_shapes.is_empty() {
5093 self.fixed_array_shapes.remove(&vertex);
5094 }
5095 if !self.declared_dynamic_anchors.is_empty() {
5096 self.declared_dynamic_anchors.remove(&vertex);
5097 }
5098 }
5099
5100 pub(crate) fn is_current_declared_dynamic_anchor(&self, vertex: VertexId) -> bool {
5105 if self.declared_dynamic_anchors.is_empty() {
5106 return false;
5107 }
5108 self.declared_dynamic_anchors
5109 .get(&vertex)
5110 .is_some_and(|declared| {
5111 self.vertex_has_formula(vertex)
5112 && self.get_cell_ref(vertex).is_some_and(|at| {
5113 at.sheet_id == declared.sheet_id
5114 && at.coord.row() == declared.coord.row()
5115 && at.coord.col() == declared.coord.col()
5116 })
5117 })
5118 }
5119
5120 #[cfg(test)]
5121 pub(crate) fn declared_dynamic_anchor_count(&self) -> usize {
5122 self.declared_dynamic_anchors.len()
5123 }
5124
5125 pub(crate) fn spills_yielding_to(
5139 &self,
5140 anchor: VertexId,
5141 target_cells: &[CellRef],
5142 ) -> Option<Vec<VertexId>> {
5143 let anchor_cell = self.get_cell_ref(anchor)?;
5144 let (first, last) = (*target_cells.first()?, *target_cells.last()?);
5145 let inside = |cell: CellRef, first: CellRef, last: CellRef| {
5146 cell.sheet_id == first.sheet_id
5147 && (first.coord.row()..=last.coord.row()).contains(&cell.coord.row())
5148 && (first.coord.col()..=last.coord.col()).contains(&cell.coord.col())
5149 };
5150 let key = |cell: CellRef| (cell.sheet_id, cell.coord.col(), cell.coord.row());
5151 let mut yielding: Vec<VertexId> = Vec::new();
5152 for cell in target_cells {
5153 let Some(&owner) = self.spill_cell_to_anchor.get(cell) else {
5154 continue;
5155 };
5156 if owner == anchor || yielding.contains(&owner) {
5157 continue;
5158 }
5159 let owner_cell = self.get_cell_ref(owner)?;
5160 let (owner_first, owner_last) = self.spill_extent_for_anchor(owner)?;
5161 if inside(owner_cell, first, last)
5162 || inside(anchor_cell, owner_first, owner_last)
5163 || key(owner_cell) <= key(anchor_cell)
5164 {
5165 return None;
5166 }
5167 yielding.push(owner);
5168 }
5169 if yielding.is_empty() {
5170 return None;
5171 }
5172 self.plan_spill_region_yielding(anchor, target_cells, &yielding)
5173 .ok()
5174 .map(|()| yielding)
5175 }
5176
5177 pub(crate) fn spill_cells_to_clear(&self, anchor: VertexId) -> Vec<CellRef> {
5181 let Some(cells) = self.spill_cells_for_anchor(anchor) else {
5182 return Vec::new();
5183 };
5184 if !self.spill_may_be_intruded(anchor) {
5185 return cells.to_vec();
5186 }
5187 cells
5188 .iter()
5189 .copied()
5190 .filter(|cell| !self.is_foreign_formula_cell(cell, anchor))
5191 .collect()
5192 }
5193
5194 #[inline]
5199 pub(crate) fn spill_may_be_intruded(&self, anchor: VertexId) -> bool {
5200 self.vertex_formulas.has_touched()
5201 || (!self.spill_intruded_anchors.is_empty()
5202 && self.spill_intruded_anchors.contains(&anchor))
5203 }
5204
5205 pub(crate) fn note_spill_intrusions_of_touched(&mut self) -> Vec<VertexId> {
5211 if self.spill_cell_to_anchor.is_empty() {
5212 return Vec::new();
5213 }
5214 let owners: Vec<VertexId> = self
5215 .vertex_formulas
5216 .touched()
5217 .iter()
5218 .filter_map(|&vertex| {
5219 let cell = self.get_cell_ref(vertex)?;
5220 let &owner = self.spill_cell_to_anchor.get(&cell)?;
5221 (owner != vertex && self.is_foreign_formula_cell(&cell, owner)).then_some(owner)
5222 })
5223 .collect();
5224 owners
5225 .into_iter()
5226 .filter(|&owner| self.spill_intruded_anchors.insert(owner))
5227 .collect()
5228 }
5229
5230 pub(crate) fn note_structural_spill_intrusions(&mut self) {
5234 if self.spill_anchor_to_cells.is_empty() {
5235 return;
5236 }
5237 let anchors: Vec<VertexId> = self.spill_anchor_to_cells.keys().copied().collect();
5238 self.spill_intruded_anchors.extend(anchors);
5239 }
5240
5241 #[cfg(test)]
5242 pub(crate) fn spill_intruded_anchor_count(&self) -> usize {
5243 self.spill_intruded_anchors.len()
5244 }
5245
5246 pub(crate) fn is_foreign_formula_cell(&self, cell: &CellRef, anchor: VertexId) -> bool {
5248 self.cell_vertex(cell).is_some_and(|vid| {
5249 vid != anchor
5250 && matches!(
5251 self.store.kind(vid),
5252 VertexKind::FormulaScalar | VertexKind::FormulaArray
5253 )
5254 })
5255 }
5256
5257 pub fn clear_spill_region(&mut self, anchor: VertexId) {
5259 let _ = self.clear_spill_region_bulk(anchor);
5260 }
5261
5262 pub fn clear_spill_region_bulk(&mut self, anchor: VertexId) -> Vec<CellRef> {
5271 let anchor_cell = self.get_cell_ref(anchor);
5272 let Some(cells) = self.spill_anchor_to_cells.remove(&anchor) else {
5273 return Vec::new();
5274 };
5275 let probe_owned = self.spill_may_be_intruded(anchor);
5276 if !self.spill_intruded_anchors.is_empty() {
5277 self.spill_intruded_anchors.remove(&anchor);
5278 }
5279
5280 for cell in cells.iter() {
5282 self.spill_cell_to_anchor.remove(cell);
5283 let remove_sheet = self
5284 .spill_cells_by_sheet
5285 .get_mut(&cell.sheet_id)
5286 .is_some_and(|sheet| {
5287 sheet.remove(&(cell.coord.row(), cell.coord.col()));
5288 sheet.is_empty()
5289 });
5290 if remove_sheet {
5291 self.spill_cells_by_sheet.remove(&cell.sheet_id);
5292 }
5293 }
5294
5295 let mut changed: Vec<crate::engine::authority::geom::Cell> = Vec::new();
5298 for cell in cells.iter().copied() {
5299 let is_anchor = anchor_cell.map(|a| a == cell).unwrap_or(false);
5300 if is_anchor || (probe_owned && self.is_foreign_formula_cell(&cell, anchor)) {
5303 continue;
5304 }
5305 self.vacate_cell(&cell);
5306 changed.push((cell.sheet_id, cell.coord.row(), cell.coord.col()));
5307 }
5308
5309 if !changed.is_empty() {
5311 let _ = self.mark_dirty_cells(&changed);
5312 }
5313
5314 cells
5315 }
5316
5317 #[cfg(any(test, feature = "legacy_oracle"))]
5318 fn collect_range_dependents_for_vertex(&self, vertex_id: VertexId) -> Vec<VertexId> {
5319 let Some(position) = self.store.grid_addr(vertex_id) else {
5321 return Vec::new();
5322 };
5323 self.collect_range_dependents_for_rect(
5324 self.store.sheet_id(vertex_id),
5325 position.row(),
5326 position.col(),
5327 position.row(),
5328 position.col(),
5329 )
5330 }
5331
5332 #[cfg(any(test, feature = "legacy_oracle"))]
5333 fn collect_range_dependents_for_rect(
5334 &self,
5335 sheet_id: SheetId,
5336 start_row: u32,
5337 start_col: u32,
5338 end_row: u32,
5339 end_col: u32,
5340 ) -> Vec<VertexId> {
5341 if self.stripe_to_dependents.is_empty() {
5342 return Vec::new();
5343 }
5344 let mut candidates: FxHashSet<VertexId> = FxHashSet::default();
5345
5346 for col in start_col..=end_col {
5347 let key = StripeKey {
5348 sheet_id,
5349 stripe_type: StripeType::Column,
5350 index: col,
5351 };
5352 if let Some(deps) = self.stripe_to_dependents.get(&key) {
5353 candidates.extend(deps);
5354 }
5355 }
5356 for row in start_row..=end_row {
5357 let key = StripeKey {
5358 sheet_id,
5359 stripe_type: StripeType::Row,
5360 index: row,
5361 };
5362 if let Some(deps) = self.stripe_to_dependents.get(&key) {
5363 candidates.extend(deps);
5364 }
5365 }
5366 if self.config.enable_block_stripes {
5367 let br0 = start_row / BLOCK_H;
5368 let br1 = end_row / BLOCK_H;
5369 let bc0 = start_col / BLOCK_W;
5370 let bc1 = end_col / BLOCK_W;
5371 for br in br0..=br1 {
5372 for bc in bc0..=bc1 {
5373 let key = StripeKey {
5374 sheet_id,
5375 stripe_type: StripeType::Block,
5376 index: block_index(br * BLOCK_H, bc * BLOCK_W),
5377 };
5378 if let Some(deps) = self.stripe_to_dependents.get(&key) {
5379 candidates.extend(deps);
5380 }
5381 }
5382 }
5383 }
5384
5385 let mut out: Vec<VertexId> = Vec::new();
5387 for dep_id in candidates {
5388 let Some(ranges) = self.formula_to_range_deps.get(&dep_id) else {
5389 continue;
5390 };
5391 let mut hit = false;
5392 for range in ranges {
5393 let range_sheet_id = self
5396 .sheet_reg
5397 .resolve_locator(&range.sheet, self.get_vertex_sheet_id(dep_id))
5398 .unwrap_or(sheet_id);
5399 if range_sheet_id != sheet_id {
5400 continue;
5401 }
5402 let sr0 = range.start_row.map(|b| b.index).unwrap_or(0);
5403 let er0 = range.end_row.map(|b| b.index).unwrap_or(u32::MAX);
5404 let sc0 = range.start_col.map(|b| b.index).unwrap_or(0);
5405 let ec0 = range.end_col.map(|b| b.index).unwrap_or(u32::MAX);
5406 let overlap =
5407 sr0 <= end_row && er0 >= start_row && sc0 <= end_col && ec0 >= start_col;
5408 if overlap {
5409 hit = true;
5410 break;
5411 }
5412 }
5413 if hit {
5414 out.push(dep_id);
5415 }
5416 }
5417 out
5418 }
5419
5420 pub(crate) fn is_live_formula_vertex(&self, vertex_id: VertexId) -> bool {
5424 self.store.vertex_exists_active(vertex_id) && self.has_formula(vertex_id)
5425 }
5426
5427 pub(crate) fn vertex_exists(&self, vertex_id: VertexId) -> bool {
5429 if vertex_id.0 < FIRST_NORMAL_VERTEX {
5430 return false;
5431 }
5432 let index = (vertex_id.0 - FIRST_NORMAL_VERTEX) as usize;
5433 index < self.store.len()
5434 }
5435
5436 pub(crate) fn get_vertex_kind(&self, vertex_id: VertexId) -> VertexKind {
5438 self.store.kind(vertex_id)
5439 }
5440
5441 pub(crate) fn get_vertex_sheet_id(&self, vertex_id: VertexId) -> SheetId {
5443 self.store.sheet_id(vertex_id)
5444 }
5445
5446 pub fn get_formula_id(&self, vertex_id: VertexId) -> Option<AstNodeId> {
5450 self.vertex_formulas.get(&vertex_id).and_then(|f| f.own())
5451 }
5452
5453 pub fn formula_view(&self, vertex_id: VertexId) -> Option<FormulaView> {
5455 let f = self.vertex_formulas.get(&vertex_id)?;
5456 Some(match f {
5457 FormulaRef::Own(template) => FormulaView {
5458 template,
5459 row_delta: 0,
5460 col_delta: 0,
5461 },
5462 FormulaRef::Member { template, anchor } => {
5463 let addr = self.store.grid_addr(vertex_id)?;
5464 FormulaView {
5465 template,
5466 row_delta: i64::from(addr.row()) - i64::from(anchor.0),
5467 col_delta: i64::from(addr.col()) - i64::from(anchor.1),
5468 }
5469 }
5470 })
5471 }
5472
5473 pub(crate) fn has_formula(&self, vertex_id: VertexId) -> bool {
5475 self.vertex_formulas.contains_key(&vertex_id)
5476 }
5477
5478 pub(crate) fn own_formula_id(&mut self, vertex_id: VertexId) -> Option<AstNodeId> {
5482 self.materialize_vertex(vertex_id);
5483 match self.vertex_formulas.get(&vertex_id)? {
5484 FormulaRef::Own(id) => Some(id),
5485 FormulaRef::Member { .. } => {
5486 let ast = self.get_formula(vertex_id)?;
5487 let id = self.data_store.store_ast(&ast, &self.sheet_reg);
5488 self.vertex_formulas.decompress(vertex_id, id);
5489 Some(id)
5490 }
5491 }
5492 }
5493
5494 pub(crate) fn materialized_formula_vertices_sorted(&self) -> Vec<VertexId> {
5497 let mut vertices: Vec<VertexId> = self.vertex_formulas.map_iter().map(|(v, _)| v).collect();
5498 vertices.sort_unstable();
5499 vertices
5500 }
5501
5502 pub(crate) fn formula_vertices(&self) -> Vec<VertexId> {
5503 let mut vertices = self.vertex_formulas.keys().collect::<Vec<_>>();
5504 vertices.sort_unstable();
5505 vertices
5506 }
5507
5508 pub fn get_formula_id_and_volatile(&self, vertex_id: VertexId) -> Option<(AstNodeId, bool)> {
5509 let ast_id = self.get_formula_id(vertex_id)?;
5510 Some((ast_id, self.is_volatile(vertex_id)))
5511 }
5512
5513 pub fn get_formula_node(&self, vertex_id: VertexId) -> Option<&super::arena::AstNodeData> {
5514 let ast_id = self.get_formula_id(vertex_id)?;
5515 self.data_store.get_node(ast_id)
5516 }
5517
5518 pub fn get_formula_node_and_volatile(
5519 &self,
5520 vertex_id: VertexId,
5521 ) -> Option<(&super::arena::AstNodeData, bool)> {
5522 let (ast_id, vol) = self.get_formula_id_and_volatile(vertex_id)?;
5523 let node = self.data_store.get_node(ast_id)?;
5524 Some((node, vol))
5525 }
5526
5527 pub fn get_formula(&self, vertex_id: VertexId) -> Option<ASTNode> {
5531 let view = self.formula_view(vertex_id)?;
5532 let ast = self
5533 .data_store
5534 .retrieve_ast(view.template, &self.sheet_reg)?;
5535 if view.row_delta == 0 && view.col_delta == 0 {
5536 return Some(ast);
5537 }
5538 crate::engine::template::relocate::instantiate_member_ast(
5539 &ast,
5540 view.row_delta,
5541 view.col_delta,
5542 )
5543 .ok()
5544 }
5545
5546 pub fn get_value(&self, vertex_id: VertexId) -> Option<LiteralValue> {
5548 if !self.value_cache_enabled && self.is_grid_backed(vertex_id) {
5549 #[cfg(debug_assertions)]
5552 {
5553 self.graph_value_read_attempts
5554 .fetch_add(1, Ordering::Relaxed);
5555 }
5556 return None;
5557 }
5558 self.vertex_values
5559 .get(&vertex_id)
5560 .map(|&value_ref| self.data_store.retrieve_value(value_ref))
5561 }
5562
5563 #[inline]
5570 fn is_grid_backed(&self, vertex_id: VertexId) -> bool {
5571 self.store.grid_addr(vertex_id).is_some()
5572 }
5573
5574 pub(crate) fn get_cell_ref(&self, vertex_id: VertexId) -> Option<CellRef> {
5579 let grid = self.store.grid_addr(vertex_id)?;
5580 let sheet_id = self.store.sheet_id(vertex_id);
5581 let coord = Coord::new(grid.row(), grid.col(), true, true);
5582 Some(CellRef::new(sheet_id, coord))
5583 }
5584
5585 pub(crate) fn make_cell_ref_internal(&self, sheet_id: SheetId, row: u32, col: u32) -> CellRef {
5587 let coord = Coord::new(row, col, true, true);
5588 CellRef::new(sheet_id, coord)
5589 }
5590
5591 pub fn make_cell_ref(&self, sheet_name: &str, row: u32, col: u32) -> CellRef {
5593 let sheet_id = self.sheet_reg.get_id(sheet_name).unwrap_or(0);
5594 let coord = Coord::from_excel(row, col, true, true);
5595 CellRef::new(sheet_id, coord)
5596 }
5597
5598 pub(crate) fn is_dirty(&self, vertex_id: VertexId) -> bool {
5600 self.store.is_dirty(vertex_id)
5601 }
5602
5603 pub(crate) fn is_volatile(&self, vertex_id: VertexId) -> bool {
5605 self.store.is_volatile(vertex_id)
5606 }
5607
5608 pub(crate) fn is_dynamic(&self, vertex_id: VertexId) -> bool {
5609 self.store.is_dynamic(vertex_id)
5610 }
5611
5612 pub fn get_vertex_id_for_address(&self, addr: &CellRef) -> Option<VertexId> {
5618 self.cell_vertex(addr)
5619 }
5620
5621 #[cfg(test)]
5622 pub fn cell_to_vertex(
5623 &self,
5624 ) -> &std::collections::HashMap<CellRef, VertexId, CoordBuildHasher> {
5625 &self.cell_to_vertex
5626 }
5627
5628 #[cfg(any(test, feature = "legacy_oracle"))]
5629 #[inline]
5633 pub(crate) fn dependencies_slice(&self, vertex_id: VertexId) -> Option<&[VertexId]> {
5634 self.edges.out_edges_ref(vertex_id)
5635 }
5636
5637 #[cfg(any(test, feature = "legacy_oracle"))]
5638 pub(crate) fn get_dependencies(&self, vertex_id: VertexId) -> Vec<VertexId> {
5640 self.edges.out_edges(vertex_id)
5641 }
5642
5643 #[cfg(any(test, feature = "legacy_oracle"))]
5644 pub(crate) fn has_self_loop(&self, vertex_id: VertexId) -> bool {
5646 if let Some(deps) = self.dependencies_slice(vertex_id) {
5647 deps.contains(&vertex_id)
5648 } else {
5649 self.edges.out_edges(vertex_id).contains(&vertex_id)
5650 }
5651 }
5652
5653 #[cfg(any(test, feature = "legacy_oracle"))]
5654 #[inline]
5658 pub(crate) fn dependents_slice(&self, vertex_id: VertexId) -> Option<&[VertexId]> {
5659 self.edges.in_edges_ref(vertex_id)
5660 }
5661
5662 #[cfg(any(test, feature = "legacy_oracle"))]
5663 pub(crate) fn get_dependents(&self, vertex_id: VertexId) -> Vec<VertexId> {
5669 self.edges.in_edges_merged(vertex_id)
5670 }
5671
5672 #[cfg(any(test, feature = "legacy_oracle"))]
5673 pub(crate) fn visit_direct_dependents_bounded(
5677 &self,
5678 vertex_id: VertexId,
5679 remaining_work: &mut u64,
5680 visitor: &mut dyn FnMut(VertexId) -> bool,
5681 ) -> bool {
5682 self.edges
5683 .visit_in_edges_bounded(vertex_id, remaining_work, visitor)
5684 }
5685
5686 #[doc(hidden)]
5690 pub fn snapshot_vertex(&self, id: VertexId) -> crate::engine::VertexSnapshot {
5691 let coord = self.store.grid_addr(id).unwrap_or_default();
5692 let sheet_id = self.store.sheet_id(id);
5693 let kind = self.store.kind(id);
5694 let flags = self.store.flags(id) & !crate::engine::vertex_store::VIRTUAL_FLAG;
5695
5696 let value_ref = self.vertex_values.get(&id).copied();
5698 let formula_ref = self.vertex_formulas.get(&id).map(|f| f.root());
5699
5700 #[cfg(any(test, feature = "legacy_oracle"))]
5702 let out_edges = self.get_dependencies(id);
5703 #[cfg(not(any(test, feature = "legacy_oracle")))]
5704 let out_edges = Vec::new();
5705
5706 crate::engine::VertexSnapshot {
5707 coord,
5708 sheet_id,
5709 kind,
5710 flags,
5711 value_ref,
5712 formula_ref,
5713 out_edges,
5714 }
5715 }
5716
5717 #[doc(hidden)]
5719 pub(crate) fn remove_all_edges(&mut self, id: VertexId) {
5720 self.materialize_vertex(id);
5721 #[cfg(not(any(test, feature = "legacy_oracle")))]
5722 self.remove_dependent_edges(id);
5723 #[cfg(any(test, feature = "legacy_oracle"))]
5724 {
5725 #[cfg(any(test, feature = "legacy_oracle"))]
5727 self.edges.begin_batch();
5728
5729 self.remove_dependent_edges(id);
5731
5732 let dependents = self.get_dependents(id);
5735 if self.pk_order.is_some()
5736 && let Some(mut pk) = self.pk_order.take()
5737 {
5738 for dependent in &dependents {
5739 pk.remove_edge(id, *dependent);
5740 }
5741 self.pk_order = Some(pk);
5742 }
5743 for dependent in dependents {
5744 self.edges.remove_edge(dependent, id);
5745 }
5746
5747 #[cfg(any(test, feature = "legacy_oracle"))]
5749 self.edges.end_batch();
5750 }
5751 }
5752
5753 #[doc(hidden)]
5755 pub fn mark_as_ref_error(&mut self, id: VertexId) {
5756 self.materialize_vertex(id);
5757 if !self.value_cache_enabled && self.is_grid_backed(id) {
5758 self.ref_error_vertices.insert(id);
5759 self.vertex_values.remove(&id);
5762 let _ = self.mark_dirty(id);
5763 return;
5764 }
5765 let error = LiteralValue::Error(ExcelError::new(ExcelErrorKind::Ref));
5766 let value_ref = self.data_store.store_value(error);
5767 self.vertex_values.insert(id, value_ref);
5768 let _ = self.mark_dirty(id);
5769 }
5770
5771 pub fn is_ref_error(&self, id: VertexId) -> bool {
5773 if !self.value_cache_enabled && self.is_grid_backed(id) {
5774 return self.ref_error_vertices.contains(&id);
5775 }
5776 if let Some(value_ref) = self.vertex_values.get(&id) {
5777 let value = self.data_store.retrieve_value(*value_ref);
5778 if let LiteralValue::Error(err) = value {
5779 return err.kind == ExcelErrorKind::Ref;
5780 }
5781 }
5782 false
5783 }
5784
5785 #[doc(hidden)]
5787 pub fn mark_dependents_dirty(&mut self, id: VertexId) {
5788 if self.authority_defers_marks() {
5792 self.authority_queue_direct_dirty(id);
5793 return;
5794 }
5795 for dep_id in self.authority_in_edge_readers(id) {
5796 self.store.set_dirty(dep_id, true);
5797 self.formula_dirty.legacy_insert(dep_id);
5798 }
5799 }
5800
5801 #[doc(hidden)]
5803 pub fn mark_volatile(&mut self, id: VertexId, volatile: bool) {
5804 if volatile {
5805 self.materialize_vertex(id);
5806 }
5807 self.store.set_volatile(id, volatile);
5808 if volatile {
5809 self.volatile_vertices.insert(id);
5810 } else {
5811 self.volatile_vertices.remove(&id);
5812 }
5813 }
5814
5815 #[doc(hidden)]
5820 pub fn set_grid_addr(&mut self, id: VertexId, coord: GridAddr) {
5821 self.materialize_vertex(id);
5822 if (!self.declared_dynamic_anchors.is_empty()
5825 || !self.fixed_single_arrays.is_empty()
5826 || !self.fixed_array_shapes.is_empty())
5827 && self.store.grid_addr(id) != Some(coord)
5828 {
5829 self.forget_declared_dynamic_anchor(id);
5830 }
5831 self.store.set_addr(id, VertexAddr::grid(coord));
5832 }
5833
5834 #[doc(hidden)]
5836 pub(crate) fn update_edge_grid_addr(&mut self, id: VertexId, coord: GridAddr) {
5837 self.materialize_vertex(id);
5838 #[cfg(not(any(test, feature = "legacy_oracle")))]
5839 let _ = (&id, &coord);
5840 #[cfg(any(test, feature = "legacy_oracle"))]
5841 {
5842 self.edges.update_addr(id, VertexAddr::grid(coord));
5843 }
5844 }
5845
5846 #[doc(hidden)]
5848 pub fn mark_deleted(&mut self, id: VertexId, deleted: bool) {
5849 self.materialize_vertex(id);
5850 self.store.mark_deleted(id, deleted);
5851 }
5852
5853 #[doc(hidden)]
5855 pub fn set_kind(&mut self, id: VertexId, kind: VertexKind) {
5856 self.materialize_vertex(id);
5857 self.store.set_kind(id, kind);
5858 }
5859
5860 #[doc(hidden)]
5862 pub fn set_dirty(&mut self, id: VertexId, dirty: bool) {
5863 self.store.set_dirty(id, dirty);
5864 if dirty {
5865 self.formula_dirty.legacy_insert(id);
5866 } else {
5867 self.formula_dirty.legacy_remove(&id);
5868 }
5869 }
5870
5871 #[cfg(test)]
5873 pub(crate) fn get_kind(&self, id: VertexId) -> VertexKind {
5874 self.store.kind(id)
5875 }
5876
5877 #[cfg(test)]
5879 pub(crate) fn get_flags(&self, id: VertexId) -> u8 {
5880 self.store.flags(id) & !crate::engine::vertex_store::VIRTUAL_FLAG
5881 }
5882
5883 #[inline]
5885 pub(crate) fn virtual_member_at(&self, addr: &CellRef) -> Option<VertexId> {
5886 self.vertex_formulas
5887 .virtual_members()
5888 .by_cell(addr.sheet_id, addr.coord.row(), addr.coord.col())
5889 .map(|m| m.vertex)
5890 }
5891
5892 #[cfg(test)]
5894 pub(crate) fn is_deleted(&self, id: VertexId) -> bool {
5895 self.store.is_deleted(id)
5896 }
5897
5898 #[cfg(any(test, feature = "legacy_oracle"))]
5899 #[doc(hidden)]
5901 pub fn rebuild_edges(&mut self) {
5902 self.edges.rebuild();
5903 }
5904
5905 #[cfg(any(test, feature = "legacy_oracle"))]
5906 pub fn flush_pending_edge_deltas(&mut self) {
5912 self.edges.rebuild();
5913 }
5914
5915 #[cfg(any(test, feature = "legacy_oracle"))]
5916 #[doc(hidden)]
5918 pub fn edges_delta_size(&self) -> usize {
5919 self.edges.delta_size()
5920 }
5921
5922 #[cfg(any(test, feature = "legacy_oracle"))]
5923 #[doc(hidden)]
5926 pub fn edges_rebuild_count(&self) -> u64 {
5927 self.edges.rebuild_count()
5928 }
5929
5930 pub fn get_vertex_for_cell(&self, addr: &CellRef) -> Option<VertexId> {
5932 self.cell_vertex(addr)
5933 }
5934
5935 #[inline]
5940 pub(crate) fn cell_vertex(&self, addr: &CellRef) -> Option<VertexId> {
5941 if let Some(&v) = self.cell_to_vertex.get(addr) {
5942 return Some(v);
5943 }
5944 self.vertex_formulas
5945 .virtual_members()
5946 .by_cell(addr.sheet_id, addr.coord.row(), addr.coord.col())
5947 .map(|m| m.vertex)
5948 }
5949
5950 #[inline]
5953 pub(crate) fn cell_vertex_mut(&mut self, addr: &CellRef) -> Option<VertexId> {
5954 if let Some(&v) = self.cell_to_vertex.get(addr) {
5955 return Some(v);
5956 }
5957 let m = self.vertex_formulas.virtual_members().by_cell(
5958 addr.sheet_id,
5959 addr.coord.row(),
5960 addr.coord.col(),
5961 )?;
5962 self.materialize_vertex(m.vertex);
5963 Some(m.vertex)
5964 }
5965
5966 #[inline]
5968 pub(crate) fn is_virtual_member(&self, v: VertexId) -> bool {
5969 !self.vertex_formulas.virtual_members().is_empty() && self.store.is_virtual(v)
5970 }
5971
5972 pub(crate) fn materialize_vertex(&mut self, v: VertexId) -> bool {
5976 if !self.is_virtual_member(v) {
5977 return false;
5978 }
5979 let Some(m) = self.vertex_formulas.materialize(v) else {
5980 return false;
5981 };
5982 self.store.ensure_dense(v, 1);
5983 self.store.set_virtual(v, false);
5984 let addr = CellRef::new(m.sheet, Coord::new(m.row, m.col, true, true));
5985 self.cell_to_vertex.insert(addr, v);
5986 self.sheet_index_mut(m.sheet)
5987 .add_vertex(GridAddr::new(m.row, m.col), v);
5988 true
5989 }
5990
5991 pub(crate) fn materialize_sheet(&mut self, sheet: SheetId) {
5993 let runs = self
5994 .vertex_formulas
5995 .virtual_members_mut()
5996 .drain_sheet(sheet);
5997 self.restore_runs(runs);
5998 }
5999
6000 pub(crate) fn materialize_all(&mut self) {
6003 if self.vertex_formulas.virtual_members().is_empty() {
6004 return;
6005 }
6006 let runs = self.vertex_formulas.virtual_members_mut().drain();
6007 self.restore_runs(runs);
6008 }
6009
6010 fn restore_runs(&mut self, runs: Vec<virtual_members::MemberRun>) {
6011 if runs.is_empty() {
6012 return;
6013 }
6014 let n: usize = runs.iter().map(|r| r.len as usize).sum();
6015 self.cell_to_vertex.reserve(n);
6016 self.vertex_formulas.reserve(n);
6017 let mut by_sheet: FxHashMap<SheetId, Vec<(GridAddr, VertexId)>> = FxHashMap::default();
6018 for r in &runs {
6019 self.store.ensure_dense(VertexId(r.first), r.len);
6020 let f = r.formula();
6021 let batch = by_sheet.entry(r.sheet).or_default();
6022 for (v, row) in r.members() {
6023 self.store.set_virtual(v, false);
6024 self.vertex_formulas.restore(v, f);
6025 self.cell_to_vertex
6026 .insert(CellRef::new(r.sheet, Coord::new(row, r.col, true, true)), v);
6027 batch.push((GridAddr::new(row, r.col), v));
6028 }
6029 }
6030 for (sheet, batch) in by_sheet {
6031 self.sheet_index_mut(sheet).add_vertices_batch(&batch);
6032 }
6033 }
6034
6035 pub(crate) fn virtual_members_in_cols(
6039 &self,
6040 sheet: SheetId,
6041 c0: u32,
6042 c1: u32,
6043 ) -> impl Iterator<Item = (VertexId, GridAddr)> + '_ {
6044 self.vertex_formulas
6045 .virtual_members()
6046 .runs_in_cols(sheet, c0, c1)
6047 .flat_map(|r| {
6048 r.members()
6049 .map(move |(v, row)| (v, GridAddr::new(row, r.col)))
6050 })
6051 }
6052
6053 pub(crate) fn vertices_in_cols(&self, sheet: SheetId, c0: u32, c1: u32) -> Vec<VertexId> {
6057 match self.sheet_indexes.get(&sheet) {
6058 Some(index) => {
6059 let mut out = index.vertices_in_col_range(c0, c1);
6060 out.extend(self.virtual_members_in_cols(sheet, c0, c1).map(|(v, _)| v));
6061 out
6062 }
6063 None => self
6064 .grid_vertices_in_sheet(sheet)
6065 .filter(|(_, a)| a.col() >= c0 && a.col() <= c1)
6066 .map(|(v, _)| v)
6067 .collect(),
6068 }
6069 }
6070
6071 pub(crate) fn vertices_in_rows(&self, sheet: SheetId, r0: u32, r1: u32) -> Vec<VertexId> {
6074 match self.sheet_indexes.get(&sheet) {
6075 Some(index) => {
6076 let mut out = index.vertices_in_row_range(r0, r1);
6077 for r in self.vertex_formulas.virtual_members().runs_in_sheet(sheet) {
6078 let lo = r.row0.max(r0);
6079 let hi = (r.row0 + r.len - 1).min(r1);
6080 if lo <= hi {
6081 out.extend((lo..=hi).map(|row| VertexId(r.first + (row - r.row0))));
6082 }
6083 }
6084 out
6085 }
6086 None => self
6087 .grid_vertices_in_sheet(sheet)
6088 .filter(|(_, a)| a.row() >= r0 && a.row() <= r1)
6089 .map(|(v, _)| v)
6090 .collect(),
6091 }
6092 }
6093
6094 pub(crate) fn virtualize_family_members(&mut self) -> usize {
6106 let mut cand: Vec<(u32, AstNodeId, (u32, u32))> = self
6107 .vertex_formulas
6108 .map_iter()
6109 .filter_map(|(v, f)| match f {
6110 FormulaRef::Member { template, anchor } => Some((v.0, template, anchor)),
6111 FormulaRef::Own(_) => None,
6112 })
6113 .collect();
6114 if cand.len() < 2 {
6115 return 0;
6116 }
6117 cand.sort_unstable_by_key(|c| c.0);
6118 let runs = self.member_runs_in_id_order(&cand);
6119 drop(cand);
6120 self.install_virtual_runs(runs)
6121 }
6122
6123 fn member_runs_in_id_order(
6127 &self,
6128 cand: &[(u32, AstNodeId, (u32, u32))],
6129 ) -> Vec<virtual_members::MemberRun> {
6130 let mut runs: Vec<virtual_members::MemberRun> = Vec::new();
6131 let mut cur: Option<virtual_members::MemberRun> = None;
6132 let close = |cur: &mut Option<virtual_members::MemberRun>,
6133 runs: &mut Vec<virtual_members::MemberRun>| {
6134 if let Some(r) = cur.take()
6135 && r.len >= 2
6136 {
6137 runs.push(r);
6138 }
6139 };
6140 for &(v, template, anchor) in cand {
6141 let vid = VertexId(v);
6142 if !self.virtualizable(vid) {
6143 close(&mut cur, &mut runs);
6144 continue;
6145 }
6146 let Some(addr) = self.store.grid_addr(vid) else {
6147 close(&mut cur, &mut runs);
6148 continue;
6149 };
6150 let sheet = self.store.sheet_id(vid);
6151 if let Some(r) = cur.as_mut()
6152 && r.sheet == sheet
6153 && r.col == addr.col()
6154 && r.first + r.len == v
6155 && r.row0 + r.len == addr.row()
6156 && r.template == template
6157 && r.anchor == anchor
6158 {
6159 r.len += 1;
6160 continue;
6161 }
6162 close(&mut cur, &mut runs);
6163 cur = Some(virtual_members::MemberRun {
6164 sheet,
6165 col: addr.col(),
6166 row0: addr.row(),
6167 len: 1,
6168 first: v,
6169 template,
6170 anchor,
6171 });
6172 }
6173 close(&mut cur, &mut runs);
6174 runs
6175 }
6176
6177 fn install_virtual_runs(&mut self, runs: Vec<virtual_members::MemberRun>) -> usize {
6180 if runs.is_empty() {
6181 return 0;
6182 }
6183 let mut made = 0usize;
6184 let mut sheets: Vec<SheetId> = Vec::new();
6185 for &r in &runs {
6186 for (v, _) in r.members() {
6187 self.store.set_virtual(v, true);
6188 }
6189 sheets.push(r.sheet);
6190 made += r.len as usize;
6191 self.vertex_formulas.virtual_members_mut().insert(r);
6192 }
6193 let store = &self.store;
6199 let mut removed: Vec<u32> = Vec::with_capacity(made);
6200 self.cell_to_vertex.retain(|c, v| {
6201 let mine = store.is_virtual(*v)
6202 && store.sheet_id(*v) == c.sheet_id
6203 && store.grid_addr(*v) == Some(GridAddr::new(c.coord.row(), c.coord.col()));
6204 if mine {
6205 removed.push(v.0);
6206 }
6207 !mine
6208 });
6209 self.cell_to_vertex.shrink_to_fit();
6210 let store = &self.store;
6211 self.vertex_formulas
6212 .drop_virtual_from_map(|v| store.is_virtual(v));
6213 if removed.len() != made {
6214 removed.sort_unstable();
6218 let orphans: Vec<VertexId> = runs
6219 .iter()
6220 .flat_map(|r| r.members().map(|(v, _)| v))
6221 .filter(|v| removed.binary_search(&v.0).is_err())
6222 .collect();
6223 for v in orphans {
6224 if self.vertex_formulas.materialize(v).is_some() {
6225 self.store.set_virtual(v, false);
6226 made -= 1;
6227 }
6228 }
6229 }
6230 sheets.sort_unstable();
6231 sheets.dedup();
6232 self.rebuild_sheet_indexes(&sheets);
6233 self.virtualize_member_pages();
6234 made
6235 }
6236
6237 pub(crate) fn virtualize_member_pages(&mut self) -> usize {
6240 let runs: Vec<virtual_members::MemberRun> = self
6241 .vertex_formulas
6242 .virtual_members()
6243 .runs()
6244 .filter(|r| r.len as usize >= 1024)
6245 .copied()
6246 .collect();
6247 runs.iter()
6248 .map(|r| {
6249 self.store
6250 .virtualize_member_span(VertexId(r.first), r.len, r.sheet, r.col, r.row0)
6251 })
6252 .sum()
6253 }
6254
6255 fn virtualizable(&self, v: VertexId) -> bool {
6257 let flags = self.store.flags(v);
6258 if flags & (0x02 | 0x04 | 0x08 | crate::engine::vertex_store::VIRTUAL_FLAG) != 0
6260 || self.store.kind(v) != VertexKind::FormulaScalar
6261 {
6262 return false;
6263 }
6264 let absent = |empty: bool, contains: &dyn Fn() -> bool| empty || !contains();
6265 absent(self.ref_error_vertices.is_empty(), &|| {
6266 self.ref_error_vertices.contains(&v)
6267 }) && absent(self.vertex_values.is_empty(), &|| {
6268 self.vertex_values.contains_key(&v)
6269 }) && absent(self.vertex_to_pending_names.is_empty(), &|| {
6270 self.vertex_to_pending_names.contains_key(&v)
6271 }) && absent(self.spill_anchor_to_cells.is_empty(), &|| {
6272 self.spill_anchor_to_cells.contains_key(&v)
6273 }) && absent(self.name_vertex_lookup.is_empty(), &|| {
6274 self.name_vertex_lookup.contains_key(&v)
6275 }) && absent(self.volatile_vertices.is_empty(), &|| {
6276 self.volatile_vertices.contains(&v)
6277 })
6278 }
6279
6280 fn rebuild_sheet_indexes(&mut self, sheets: &[SheetId]) {
6284 let store = &self.store;
6285 for sheet in sheets {
6286 if let Some(index) = self.sheet_indexes.get_mut(sheet) {
6287 index.retain_vertices(|v| !store.is_virtual(v));
6288 }
6289 }
6290 }
6291
6292 pub(crate) fn virtual_vertex_pages(&self) -> usize {
6295 self.store.virtual_pages()
6296 }
6297
6298 pub(crate) fn virtual_member_counts(&self) -> (usize, usize) {
6299 let m = self.vertex_formulas.virtual_members();
6300 (m.len(), m.run_count())
6301 }
6302
6303 pub fn get_grid_addr(&self, id: VertexId) -> Option<GridAddr> {
6308 self.store.grid_addr(id)
6309 }
6310
6311 pub fn get_sheet_id(&self, id: VertexId) -> SheetId {
6313 self.store.sheet_id(id)
6314 }
6315
6316 pub fn grid_vertices_in_sheet(
6323 &self,
6324 sheet_id: SheetId,
6325 ) -> impl Iterator<Item = (VertexId, GridAddr)> + '_ {
6326 self.store.all_vertices().filter_map(move |id| {
6327 if !self.vertex_exists(id) || self.store.sheet_id(id) != sheet_id {
6328 return None;
6329 }
6330 if !self.retired_id_set.is_empty()
6331 && self.retired_id_set.contains(&id)
6332 && self.store.is_deleted(id)
6333 {
6334 return None;
6335 }
6336 self.store.grid_addr(id).map(|addr| (id, addr))
6337 })
6338 }
6339
6340 pub fn vertex_has_formula(&self, id: VertexId) -> bool {
6342 self.vertex_formulas.contains_key(&id)
6343 }
6344
6345 pub fn vertices_with_formulas(&self) -> impl Iterator<Item = VertexId> + '_ {
6347 self.vertex_formulas.keys()
6348 }
6349
6350 pub fn update_vertex_formula(&mut self, id: VertexId, ast: ASTNode) -> Result<(), ExcelError> {
6352 self.materialize_vertex(id);
6353 let sheet_id = self.store.sheet_id(id);
6355
6356 let (
6358 new_dependencies,
6359 new_range_dependencies,
6360 vertexless,
6361 named_dependencies,
6362 unresolved_names,
6363 ) = self.extract_dependencies_with_pending_names(&ast, sheet_id)?;
6364
6365 let old_kind = self.store.kind(id);
6366
6367 self.remove_dependent_edges(id);
6369 self.detach_vertex_from_names(id);
6370 self.clear_pending_name_references(id);
6371
6372 let ast_id = self.data_store.store_ast(&ast, &self.sheet_reg);
6374 self.vertex_formulas.insert(id, ast_id);
6375
6376 self.add_dependent_edges(id, &new_dependencies);
6378 self.note_vertexless_deps(
6379 id,
6380 vertexless
6381 .iter()
6382 .map(|c| (c.sheet_id, c.coord.row(), c.coord.col())),
6383 );
6384 self.add_range_dependent_edges(id, &new_range_dependencies, sheet_id);
6385
6386 if !named_dependencies.is_empty() {
6387 self.attach_vertex_to_names(id, &named_dependencies);
6388 }
6389 for unresolved_name in &unresolved_names {
6390 self.record_pending_name_reference(sheet_id, unresolved_name, id);
6391 }
6392
6393 self.ref_error_vertices.remove(&id);
6396 self.vertex_values.remove(&id);
6397
6398 self.store.set_kind(
6400 id,
6401 if old_kind == VertexKind::FormulaArray {
6402 VertexKind::FormulaArray
6403 } else {
6404 VertexKind::FormulaScalar
6405 },
6406 );
6407
6408 Ok(())
6409 }
6410
6411 pub fn mark_vertex_dirty(&mut self, vertex_id: VertexId) {
6413 self.store.set_dirty(vertex_id, true);
6414 self.formula_dirty.legacy_insert(vertex_id);
6415 }
6416
6417 pub fn mark_vertices_dirty_batch(&mut self, vertices: &[VertexId]) {
6419 self.formula_dirty.legacy_reserve(vertices.len());
6420 for &vertex_id in vertices {
6421 self.store.set_dirty(vertex_id, true);
6422 }
6423 self.formula_dirty.legacy_extend(vertices.iter().copied());
6424 }
6425
6426 pub fn update_cell_mapping(
6428 &mut self,
6429 id: VertexId,
6430 old_addr: Option<CellRef>,
6431 new_addr: CellRef,
6432 ) {
6433 self.materialize_vertex(id);
6434 if let Some(old) = old_addr {
6435 self.cell_vertex_mut(&old);
6436 }
6437 self.cell_vertex_mut(&new_addr);
6438 if let Some(old) = old_addr {
6440 self.cell_to_vertex.remove(&old);
6441 }
6442 self.cell_to_vertex.insert(new_addr, id);
6444 }
6445
6446 pub fn remove_cell_mapping(&mut self, addr: &CellRef) {
6448 self.cell_vertex_mut(addr);
6449 self.cell_to_vertex.remove(addr);
6450 }
6451
6452 pub(crate) fn revive_vertex(&mut self, id: VertexId, sheet: SheetId, coord: GridAddr) -> bool {
6457 if !self.store.vertex_exists(id)
6458 || !self.store.is_deleted(id)
6459 || self.store.grid_addr(id).is_none()
6460 || self.store.sheet_id(id) != sheet
6461 {
6462 return false;
6463 }
6464 let cell = CellRef::new(sheet, Coord::new(coord.row(), coord.col(), true, true));
6465 let mut placeholder = None;
6470 if let Some(x) = self.cell_vertex(&cell)
6471 && !self.store.is_deleted(x)
6472 && self.store.grid_addr(x) == Some(coord)
6473 {
6474 if !self.is_pure_placeholder(x) {
6475 return false;
6476 }
6477 placeholder = Some(x);
6478 }
6479 #[cfg(any(test, feature = "legacy_oracle"))]
6480 let readers = placeholder
6481 .map(|x| self.get_dependents(x))
6482 .unwrap_or_default();
6483 if let Some(x) = placeholder {
6484 self.cell_to_vertex.remove(&cell);
6485 if let Some(index) = self.sheet_indexes.get_mut(&sheet) {
6486 index.remove_vertex(coord, x);
6487 }
6488 self.remove_all_edges(x);
6489 self.store.mark_deleted(x, true);
6490 }
6491 self.retired_id_set.remove(&id);
6492 self.store.mark_deleted(id, false);
6493 self.store.set_addr(id, VertexAddr::grid(coord));
6494 #[cfg(any(test, feature = "legacy_oracle"))]
6495 self.edges.update_addr(id, VertexAddr::grid(coord));
6496 self.store.set_kind(id, VertexKind::Empty);
6497 self.store.set_dynamic(id, false);
6498 self.store.set_volatile(id, false);
6499 self.cell_to_vertex.insert(cell, id);
6500 self.sheet_index_mut(sheet).add_vertex(coord, id);
6501 self.ref_error_vertices.remove(&id);
6502 #[cfg(any(test, feature = "legacy_oracle"))]
6505 for r in readers {
6506 if let Some(ast) = self.get_formula(r) {
6507 self.rebuild_formula_dependencies(r, &ast);
6508 }
6509 }
6510 let _ = self.mark_dirty(id);
6513 true
6514 }
6515
6516 fn is_pure_placeholder(&self, x: VertexId) -> bool {
6519 self.store.kind(x) == VertexKind::Empty
6520 && !self.vertex_formulas.contains_key(&x)
6521 && !self.vertex_values.contains_key(&x)
6522 && !self.ref_error_vertices.contains(&x)
6523 && !self.spill_anchor_to_cells.contains_key(&x)
6524 && !self.vertex_to_pending_names.contains_key(&x)
6525 && !self.name_vertex_lookup.contains_key(&x)
6526 }
6527
6528 pub(crate) fn journal_formula_left(&mut self, v: VertexId) {
6532 if !self.vertex_formulas.contains_key(&v) {
6533 return;
6534 }
6535 if let Some(cell) = self.get_cell_ref(v) {
6536 self.vertex_journal
6537 .retired((cell.sheet_id, cell.coord.row(), cell.coord.col()), v.0);
6538 }
6539 }
6540
6541 fn replay_formula_vertex(&mut self, addr: &CellRef) {
6547 if self.revive_retired_id(addr).is_some() {
6548 return;
6549 }
6550 let cell = (addr.sheet_id, addr.coord.row(), addr.coord.col());
6551 let Some(id) = self.vertex_journal.created(cell) else {
6552 return;
6553 };
6554 if self.cell_vertex(addr).is_none() {
6555 let coord = GridAddr::new(addr.coord.row(), addr.coord.col());
6556 self.revive_vertex(VertexId(id), addr.sheet_id, coord);
6557 }
6558 }
6559
6560 pub(crate) fn set_replay_mode(&mut self, mode: crate::engine::authority::history::Replay) {
6561 self.vertex_journal.set_mode(mode);
6562 }
6563
6564 pub fn get_cell_ref_for_vertex(&self, id: VertexId) -> Option<CellRef> {
6566 let coord = self.store.grid_addr(id)?;
6567 let sheet_id = self.store.sheet_id(id);
6568 let cell_ref = CellRef::new(sheet_id, Coord::new(coord.row(), coord.col(), true, true));
6570 if self.cell_vertex(&cell_ref) == Some(id) {
6572 Some(cell_ref)
6573 } else {
6574 None
6575 }
6576 }
6577
6578 pub(crate) fn rebuild_formula_dependencies(&mut self, vertex_id: VertexId, ast: &ASTNode) {
6584 self.materialize_vertex(vertex_id);
6585 let sheet_id = self.store.sheet_id(vertex_id);
6586
6587 self.remove_dependent_edges(vertex_id);
6589 self.detach_vertex_from_names(vertex_id);
6590 self.clear_pending_name_references(vertex_id);
6591
6592 let (
6593 new_dependencies,
6594 new_range_dependencies,
6595 vertexless,
6596 named_dependencies,
6597 unresolved_names,
6598 ) = match self.extract_dependencies_with_pending_names(ast, sheet_id) {
6599 Ok(v) => v,
6600 Err(_) => {
6601 self.mark_as_ref_error(vertex_id);
6602 return;
6603 }
6604 };
6605
6606 if new_dependencies.contains(&vertex_id) && !self.config.cycle.allows_self_dependency() {
6609 self.mark_as_ref_error(vertex_id);
6610 return;
6611 }
6612
6613 for &name_vertex in &named_dependencies {
6614 let mut visited = FxHashSet::default();
6615 if self.name_depends_on_vertex(name_vertex, vertex_id, &mut visited) {
6616 self.mark_as_ref_error(vertex_id);
6617 return;
6618 }
6619 }
6620
6621 self.ref_error_vertices.remove(&vertex_id);
6623 self.vertex_values.remove(&vertex_id);
6624
6625 if !named_dependencies.is_empty() {
6626 self.attach_vertex_to_names(vertex_id, &named_dependencies);
6627 }
6628 for unresolved_name in &unresolved_names {
6629 self.record_pending_name_reference(sheet_id, unresolved_name, vertex_id);
6630 }
6631
6632 self.add_dependent_edges(vertex_id, &new_dependencies);
6633 self.note_vertexless_deps(
6634 vertex_id,
6635 vertexless
6636 .iter()
6637 .map(|c| (c.sheet_id, c.coord.row(), c.coord.col())),
6638 );
6639 self.add_range_dependent_edges(vertex_id, &new_range_dependencies, sheet_id);
6640 self.vertex_formulas.touch(vertex_id);
6641 let _ = self.mark_dirty(vertex_id);
6642 }
6643}
6644
6645type RetiredBatch = Vec<((SheetId, u32, u32), VertexId)>;
6650
6651pub(crate) fn same_cell(a: &CellRef, b: &CellRef) -> bool {
6653 a.sheet_id == b.sheet_id && a.coord.row() == b.coord.row() && a.coord.col() == b.coord.col()
6654}
6655
6656fn shift_key(
6661 op: &crate::engine::graph::editor::reference_adjuster::ShiftOperation,
6662) -> (u8, SheetId, u32, u32) {
6663 use crate::engine::graph::editor::reference_adjuster::ShiftOperation as Op;
6664 match *op {
6665 Op::InsertRows {
6666 sheet_id,
6667 before,
6668 count,
6669 } => (0, sheet_id, before, count),
6670 Op::DeleteRows {
6671 sheet_id,
6672 start,
6673 count,
6674 } => (1, sheet_id, start, count),
6675 Op::InsertColumns {
6676 sheet_id,
6677 before,
6678 count,
6679 } => (2, sheet_id, before, count),
6680 Op::DeleteColumns {
6681 sheet_id,
6682 start,
6683 count,
6684 } => (3, sheet_id, start, count),
6685 }
6686}
6687
6688pub(crate) fn parse_structural_description(
6689 description: &str,
6690) -> Option<crate::engine::graph::editor::reference_adjuster::ShiftOperation> {
6691 use crate::engine::graph::editor::reference_adjuster::ShiftOperation as Op;
6692 let mut parts = description.split_whitespace();
6693 let kind = parts.next()?;
6694 let mut field = |name: &str| -> Option<u32> {
6695 parts
6696 .next()?
6697 .strip_prefix(name)?
6698 .strip_prefix('=')?
6699 .parse()
6700 .ok()
6701 };
6702 let sheet_id = u16::try_from(field("sheet")?).ok()?;
6703 Some(match kind {
6704 "InsertRows" => {
6705 let before = field("before")?;
6706 Op::InsertRows {
6707 sheet_id,
6708 before,
6709 count: field("count")?,
6710 }
6711 }
6712 "DeleteRows" => {
6713 let start = field("start")?;
6714 Op::DeleteRows {
6715 sheet_id,
6716 start,
6717 count: field("count")?,
6718 }
6719 }
6720 "InsertColumns" => {
6721 let before = field("before")?;
6722 Op::InsertColumns {
6723 sheet_id,
6724 before,
6725 count: field("count")?,
6726 }
6727 }
6728 "DeleteColumns" => {
6729 let start = field("start")?;
6730 Op::DeleteColumns {
6731 sheet_id,
6732 start,
6733 count: field("count")?,
6734 }
6735 }
6736 _ => return None,
6737 })
6738}