1use crate::SheetId;
2use crate::engine::named_range::{NameScope, NamedDefinition, NamedRange};
3use crate::engine::sheet_registry::SheetRegistry;
4use formualizer_common::{ExcelError, ExcelErrorKind, LiteralValue};
5use formualizer_parse::parser::{ASTNode, ASTNodeType, ReferenceType};
6use rustc_hash::{FxHashMap, FxHashSet};
7
8#[cfg(debug_assertions)]
9use std::sync::atomic::{AtomicU64, Ordering};
10
11#[cfg(test)]
12#[derive(Debug, Default, Clone)]
13pub struct GraphInstrumentation {
14 pub edges_added: u64,
15 pub stripe_inserts: u64,
16 pub stripe_removes: u64,
17 pub dependents_scan_fallback_calls: u64,
18 pub dependents_scan_vertices_scanned: u64,
19}
20
21mod ast_utils;
22pub mod editor;
23mod formula_analysis;
24mod names;
25mod range_deps;
26mod sheets;
27pub mod snapshot;
28mod sources;
29mod tables;
30
31use super::arena::{AstNodeId, DataStore, ValueRef};
32use super::delta_edges::CsrMutableEdges;
33use super::sheet_index::SheetIndex;
34use super::vertex::{VertexId, VertexKind};
35use super::vertex_store::{FIRST_NORMAL_VERTEX, VertexStore};
36use crate::engine::topo::{
37 GraphAdapter,
38 pk::{DynamicTopo, PkConfig},
39};
40use crate::reference::{CellRef, Coord, SharedRangeRef, SharedRef, SharedSheetLocator};
41use formualizer_common::Coord as AbsCoord;
42#[inline]
45fn normalize_stored_literal(value: LiteralValue) -> LiteralValue {
46 match value {
47 LiteralValue::Int(i) => LiteralValue::Number(i as f64),
49 other => other,
50 }
51}
52
53pub use editor::change_log::{ChangeEvent, ChangeLog};
54
55#[derive(Debug, Clone, PartialEq, Eq, Hash)]
59pub enum DependencyRef {
60 Cell(VertexId),
62 Range {
64 sheet: String,
65 start_row: u32,
66 start_col: u32,
67 end_row: u32, end_col: u32, },
70 WholeColumn { sheet: String, col: u32 },
72 WholeRow { sheet: String, row: u32 },
74}
75
76#[derive(Debug, Clone, Hash, PartialEq, Eq)]
78pub struct StripeKey {
79 pub sheet_id: SheetId,
80 pub stripe_type: StripeType,
81 pub index: u32, }
83
84#[derive(Debug, Clone, Hash, PartialEq, Eq)]
85pub enum StripeType {
86 Row,
87 Column,
88 Block, }
90
91const BLOCK_H: u32 = 256;
93const BLOCK_W: u32 = 256;
94
95pub fn block_index(row: u32, col: u32) -> u32 {
96 (row / BLOCK_H) << 16 | (col / BLOCK_W)
97}
98
99#[derive(Debug, Clone)]
102pub struct OperationSummary {
103 pub affected_vertices: Vec<VertexId>,
105 pub created_placeholders: Vec<CellRef>,
107}
108
109#[derive(Debug)]
111pub struct DependencyGraph {
112 store: VertexStore,
114
115 edges: CsrMutableEdges,
117
118 data_store: DataStore,
120 vertex_values: FxHashMap<VertexId, ValueRef>,
121 vertex_formulas: FxHashMap<VertexId, AstNodeId>,
122
123 value_cache_enabled: bool,
128
129 #[cfg(debug_assertions)]
132 graph_value_read_attempts: AtomicU64,
133
134 cell_to_vertex: FxHashMap<CellRef, VertexId>,
136
137 dirty_vertices: FxHashSet<VertexId>,
139 volatile_vertices: FxHashSet<VertexId>,
140
141 ref_error_vertices: FxHashSet<VertexId>,
147
148 formula_to_range_deps: FxHashMap<VertexId, Vec<SharedRangeRef<'static>>>,
151
152 stripe_to_dependents: FxHashMap<StripeKey, FxHashSet<VertexId>>,
155
156 sheet_indexes: FxHashMap<SheetId, SheetIndex>,
159
160 sheet_reg: SheetRegistry,
162 default_sheet_id: SheetId,
163
164 named_ranges: FxHashMap<String, NamedRange>,
167
168 named_ranges_lookup: FxHashMap<String, String>,
173
174 sheet_named_ranges: FxHashMap<(SheetId, String), NamedRange>,
176
177 sheet_named_ranges_lookup: FxHashMap<(SheetId, String), String>,
182
183 vertex_to_names: FxHashMap<VertexId, Vec<VertexId>>,
185
186 name_vertex_lookup: FxHashMap<VertexId, (NameScope, String)>,
188
189 pending_name_links: FxHashMap<String, Vec<(SheetId, VertexId)>>,
191
192 tables: FxHashMap<String, tables::TableEntry>,
194 tables_lookup: FxHashMap<String, String>,
196 table_vertex_lookup: FxHashMap<VertexId, String>,
197
198 source_scalars: FxHashMap<String, sources::SourceScalarEntry>,
200 source_tables: FxHashMap<String, sources::SourceTableEntry>,
201 source_vertex_lookup: FxHashMap<VertexId, String>,
202
203 name_vertex_seq: u32,
205
206 source_vertex_seq: u32,
208
209 cell_to_name_dependents: FxHashMap<VertexId, FxHashSet<VertexId>>,
211 name_to_cell_dependencies: FxHashMap<VertexId, Vec<VertexId>>,
213
214 config: super::EvalConfig,
216
217 pk_order: Option<DynamicTopo<VertexId>>,
219
220 spill_anchor_to_cells: FxHashMap<VertexId, Vec<CellRef>>,
222 spill_cell_to_anchor: FxHashMap<CellRef, VertexId>,
223
224 first_load_assume_new: bool,
226 ensure_touched_sheets: FxHashSet<SheetId>,
227
228 #[cfg(test)]
229 instr: std::sync::Mutex<GraphInstrumentation>,
230}
231
232impl Default for DependencyGraph {
233 fn default() -> Self {
234 Self::new()
235 }
236}
237
238impl DependencyGraph {
239 pub fn range_expansion_limit(&self) -> usize {
241 self.config.range_expansion_limit
242 }
243
244 pub fn get_config(&self) -> &super::EvalConfig {
245 &self.config
246 }
247
248 #[inline]
249 pub(crate) fn value_cache_enabled(&self) -> bool {
250 self.value_cache_enabled
251 }
252
253 #[cfg(test)]
257 pub fn debug_graph_value_read_attempts(&self) -> u64 {
258 #[cfg(debug_assertions)]
259 {
260 self.graph_value_read_attempts.load(Ordering::Relaxed)
261 }
262 #[cfg(not(debug_assertions))]
263 {
264 0
265 }
266 }
267
268 pub fn plan_dependencies<'a, I>(
270 &mut self,
271 items: I,
272 policy: &formualizer_parse::parser::CollectPolicy,
273 volatile: Option<&[bool]>,
274 ) -> Result<crate::engine::plan::DependencyPlan, formualizer_common::ExcelError>
275 where
276 I: IntoIterator<Item = (&'a str, u32, u32, &'a formualizer_parse::parser::ASTNode)>,
277 {
278 crate::engine::plan::build_dependency_plan(
279 &mut self.sheet_reg,
280 items.into_iter(),
281 policy,
282 volatile,
283 )
284 }
285
286 pub fn ensure_vertices_batch(
289 &mut self,
290 coords: &[(SheetId, AbsCoord)],
291 ) -> Vec<(AbsCoord, u32)> {
292 use rustc_hash::FxHashMap;
293 let mut grouped: FxHashMap<SheetId, Vec<AbsCoord>> = FxHashMap::default();
294 for (sid, pc) in coords.iter().copied() {
295 let addr = CellRef::new(sid, Coord::new(pc.row(), pc.col(), true, true));
296 if self.cell_to_vertex.contains_key(&addr) {
297 continue;
298 }
299 grouped.entry(sid).or_default().push(pc);
300 }
301 let mut add_batch: Vec<(AbsCoord, u32)> = Vec::new();
302 for (sid, pcs) in grouped {
303 if pcs.is_empty() {
304 continue;
305 }
306 self.ensure_touched_sheets.insert(sid);
308 let vids = self.store.allocate_contiguous(sid, &pcs, 0x00);
309 for (i, pc) in pcs.iter().enumerate() {
310 let vid = vids[i];
311 add_batch.push((*pc, vid.0));
312 let addr = CellRef::new(sid, Coord::new(pc.row(), pc.col(), true, true));
313 self.cell_to_vertex.insert(addr, vid);
314 match self.config.sheet_index_mode {
316 crate::engine::SheetIndexMode::Eager
317 | crate::engine::SheetIndexMode::FastBatch => {
318 self.sheet_index_mut(sid).add_vertex(*pc, vid);
319 }
320 crate::engine::SheetIndexMode::Lazy => {
321 }
323 }
324 }
325 }
326 if !add_batch.is_empty() {
327 self.edges.add_vertices_batch(&add_batch);
328 }
329 add_batch
330 }
331
332 pub fn set_first_load_assume_new(&mut self, enabled: bool) {
334 self.first_load_assume_new = enabled;
335 }
336
337 pub fn reset_ensure_touched(&mut self) {
339 self.ensure_touched_sheets.clear();
340 }
341
342 pub fn store_asts_batch<'a, I>(&mut self, asts: I) -> Vec<AstNodeId>
344 where
345 I: IntoIterator<Item = &'a formualizer_parse::parser::ASTNode>,
346 {
347 self.data_store.store_asts_batch(asts, &self.sheet_reg)
348 }
349
350 pub fn vid_for_sid_pc(&self, sid: SheetId, pc: AbsCoord) -> Option<VertexId> {
352 let addr = CellRef::new(sid, Coord::new(pc.row(), pc.col(), true, true));
353 self.cell_to_vertex.get(&addr).copied()
354 }
355
356 pub fn vid_for_plan_idx(
358 &self,
359 plan: &crate::engine::plan::DependencyPlan,
360 idx: u32,
361 ) -> Option<VertexId> {
362 let (sid, pc) = plan.global_cells.get(idx as usize).copied()?;
363 self.vid_for_sid_pc(sid, pc)
364 }
365
366 pub fn assign_formula_vertex(&mut self, vid: VertexId, ast_id: AstNodeId, volatile: bool) {
368 if self.vertex_formulas.contains_key(&vid) {
369 self.remove_dependent_edges(vid);
370 }
371 self.store
372 .set_kind(vid, crate::engine::vertex::VertexKind::FormulaScalar);
373 self.vertex_values.remove(&vid);
374 self.vertex_formulas.insert(vid, ast_id);
375 self.mark_volatile(vid, volatile);
376 self.mark_vertex_dirty(vid);
378 }
379
380 pub fn add_edges_nobatch(&mut self, dependent: VertexId, dependencies: &[VertexId]) {
382 self.add_dependent_edges_nobatch(dependent, dependencies);
383 }
384
385 pub fn iter_vertex_ids(&self) -> impl Iterator<Item = VertexId> + '_ {
387 self.store.all_vertices()
388 }
389
390 pub fn vertex_coord(&self, vid: VertexId) -> AbsCoord {
392 self.store.coord(vid)
393 }
394
395 pub fn vertex_count(&self) -> usize {
397 self.store.len()
398 }
399
400 pub fn build_edges_from_adjacency(
402 &mut self,
403 adjacency: Vec<(u32, Vec<u32>)>,
404 coords: Vec<AbsCoord>,
405 vertex_ids: Vec<u32>,
406 ) {
407 self.edges
408 .build_from_adjacency(adjacency, coords, vertex_ids);
409 }
410 pub fn used_row_bounds_for_columns(
412 &self,
413 sheet_id: SheetId,
414 start_col: u32,
415 end_col: u32,
416 ) -> Option<(u32, u32)> {
417 if let Some(index) = self.sheet_indexes.get(&sheet_id)
419 && !index.is_empty()
420 {
421 let mut min_r: Option<u32> = None;
422 let mut max_r: Option<u32> = None;
423 for vid in index.vertices_in_col_range(start_col, end_col) {
424 let r = self.store.coord(vid).row();
425 min_r = Some(min_r.map(|m| m.min(r)).unwrap_or(r));
426 max_r = Some(max_r.map(|m| m.max(r)).unwrap_or(r));
427 }
428 return match (min_r, max_r) {
429 (Some(a), Some(b)) => Some((a, b)),
430 _ => None,
431 };
432 }
433 let mut min_r: Option<u32> = None;
435 let mut max_r: Option<u32> = None;
436 for cref in self.cell_to_vertex.keys() {
437 if cref.sheet_id == sheet_id {
438 let c = cref.coord.col();
439 if c >= start_col && c <= end_col {
440 let r = cref.coord.row();
441 min_r = Some(min_r.map(|m| m.min(r)).unwrap_or(r));
442 max_r = Some(max_r.map(|m| m.max(r)).unwrap_or(r));
443 }
444 }
445 }
446 match (min_r, max_r) {
447 (Some(a), Some(b)) => Some((a, b)),
448 _ => None,
449 }
450 }
451
452 pub fn finalize_sheet_index(&mut self, sheet: &str) {
454 let Some(sheet_id) = self.sheet_reg.get_id(sheet) else {
455 return;
456 };
457 if let Some(idx) = self.sheet_indexes.get(&sheet_id)
459 && !idx.is_empty()
460 {
461 return;
462 }
463 let mut idx = SheetIndex::new();
464 let mut batch: Vec<(AbsCoord, VertexId)> = Vec::with_capacity(self.cell_to_vertex.len());
466 for (cref, vid) in &self.cell_to_vertex {
467 if cref.sheet_id == sheet_id {
468 batch.push((AbsCoord::new(cref.coord.row(), cref.coord.col()), *vid));
469 }
470 }
471 idx.add_vertices_batch(&batch);
473 self.sheet_indexes.insert(sheet_id, idx);
474 }
475
476 pub fn set_sheet_index_mode(&mut self, mode: crate::engine::SheetIndexMode) {
477 self.config.sheet_index_mode = mode;
478 }
479
480 pub fn used_col_bounds_for_rows(
482 &self,
483 sheet_id: SheetId,
484 start_row: u32,
485 end_row: u32,
486 ) -> Option<(u32, u32)> {
487 if let Some(index) = self.sheet_indexes.get(&sheet_id)
488 && !index.is_empty()
489 {
490 let mut min_c: Option<u32> = None;
491 let mut max_c: Option<u32> = None;
492 for vid in index.vertices_in_row_range(start_row, end_row) {
493 let c = self.store.coord(vid).col();
494 min_c = Some(min_c.map(|m| m.min(c)).unwrap_or(c));
495 max_c = Some(max_c.map(|m| m.max(c)).unwrap_or(c));
496 }
497 return match (min_c, max_c) {
498 (Some(a), Some(b)) => Some((a, b)),
499 _ => None,
500 };
501 }
502 let mut min_c: Option<u32> = None;
504 let mut max_c: Option<u32> = None;
505 for cref in self.cell_to_vertex.keys() {
506 if cref.sheet_id == sheet_id {
507 let r = cref.coord.row();
508 if r >= start_row && r <= end_row {
509 let c = cref.coord.col();
510 min_c = Some(min_c.map(|m| m.min(c)).unwrap_or(c));
511 max_c = Some(max_c.map(|m| m.max(c)).unwrap_or(c));
512 }
513 }
514 }
515 match (min_c, max_c) {
516 (Some(a), Some(b)) => Some((a, b)),
517 _ => None,
518 }
519 }
520
521 pub fn sheet_has_formulas(&self, sheet_id: SheetId) -> bool {
523 for &vid in self.vertex_formulas.keys() {
525 if self.store.sheet_id(vid) == sheet_id {
526 return true;
527 }
528 }
529 false
530 }
531 pub fn new() -> Self {
532 Self::new_with_config(super::EvalConfig::default())
533 }
534
535 pub fn new_with_config(config: super::EvalConfig) -> Self {
536 let mut sheet_reg = SheetRegistry::new();
537 let default_sheet_id = sheet_reg.id_for(&config.default_sheet_name);
538
539 let mut g = Self {
540 store: VertexStore::new(),
541 edges: CsrMutableEdges::new(),
542 data_store: DataStore::new(),
543 vertex_values: FxHashMap::default(),
544 vertex_formulas: FxHashMap::default(),
545 value_cache_enabled: false,
548 #[cfg(debug_assertions)]
549 graph_value_read_attempts: AtomicU64::new(0),
550 cell_to_vertex: FxHashMap::default(),
551 dirty_vertices: FxHashSet::default(),
552 volatile_vertices: FxHashSet::default(),
553 ref_error_vertices: FxHashSet::default(),
554 formula_to_range_deps: FxHashMap::default(),
555 stripe_to_dependents: FxHashMap::default(),
556 sheet_indexes: FxHashMap::default(),
557 sheet_reg,
558 default_sheet_id,
559 named_ranges: FxHashMap::default(),
560 named_ranges_lookup: FxHashMap::default(),
561 sheet_named_ranges: FxHashMap::default(),
562 sheet_named_ranges_lookup: FxHashMap::default(),
563 vertex_to_names: FxHashMap::default(),
564 name_vertex_lookup: FxHashMap::default(),
565 pending_name_links: FxHashMap::default(),
566 tables: FxHashMap::default(),
567 tables_lookup: FxHashMap::default(),
568 table_vertex_lookup: FxHashMap::default(),
569 source_scalars: FxHashMap::default(),
570 source_tables: FxHashMap::default(),
571 source_vertex_lookup: FxHashMap::default(),
572 name_vertex_seq: 0,
573 source_vertex_seq: 0,
574 cell_to_name_dependents: FxHashMap::default(),
575 name_to_cell_dependencies: FxHashMap::default(),
576 config: config.clone(),
577 pk_order: None,
578 spill_anchor_to_cells: FxHashMap::default(),
579 spill_cell_to_anchor: FxHashMap::default(),
580 first_load_assume_new: false,
581 ensure_touched_sheets: FxHashSet::default(),
582 #[cfg(test)]
583 instr: std::sync::Mutex::new(GraphInstrumentation::default()),
584 };
585
586 if config.use_dynamic_topo {
587 let nodes = g
589 .store
590 .all_vertices()
591 .filter(|&id| g.store.vertex_exists_active(id));
592 let mut pk = DynamicTopo::new(
593 nodes,
594 PkConfig {
595 visit_budget: config.pk_visit_budget,
596 compaction_interval_ops: config.pk_compaction_interval_ops,
597 },
598 );
599 let adapter = GraphAdapter { g: &g };
601 pk.rebuild_full(&adapter);
602 g.pk_order = Some(pk);
603 }
604
605 g
606 }
607
608 pub(crate) fn pk_layers_for(&self, subset: &[VertexId]) -> Option<Vec<crate::engine::Layer>> {
610 let pk = self.pk_order.as_ref()?;
611 let adapter = crate::engine::topo::GraphAdapter { g: self };
612 let layers = pk.layers_for(&adapter, subset, self.config.max_layer_width);
613 Some(
614 layers
615 .into_iter()
616 .map(|vs| crate::engine::Layer { vertices: vs })
617 .collect(),
618 )
619 }
620
621 #[inline]
622 pub(crate) fn dynamic_topo_enabled(&self) -> bool {
623 self.pk_order.is_some()
624 }
625
626 #[cfg(test)]
627 pub fn reset_instr(&mut self) {
628 if let Ok(mut g) = self.instr.lock() {
629 *g = GraphInstrumentation::default();
630 }
631 }
632
633 #[cfg(test)]
634 pub fn instr(&self) -> GraphInstrumentation {
635 self.instr.lock().map(|g| g.clone()).unwrap_or_default()
636 }
637
638 pub fn begin_batch(&mut self) {
640 self.edges.begin_batch();
641 }
642
643 pub fn end_batch(&mut self) {
645 self.edges.end_batch();
646 }
647
648 pub fn default_sheet_id(&self) -> SheetId {
649 self.default_sheet_id
650 }
651
652 pub fn default_sheet_name(&self) -> &str {
653 self.sheet_reg.name(self.default_sheet_id)
654 }
655
656 pub fn set_default_sheet_by_name(&mut self, name: &str) {
657 self.default_sheet_id = self.sheet_id_mut(name);
658 }
659
660 pub fn set_default_sheet_by_id(&mut self, id: SheetId) {
661 self.default_sheet_id = id;
662 }
663
664 pub fn sheet_id_mut(&mut self, name: &str) -> SheetId {
666 self.sheet_reg.id_for(name)
667 }
668
669 pub fn sheet_id(&self, name: &str) -> Option<SheetId> {
670 self.sheet_reg.get_id(name)
671 }
672
673 fn resolve_existing_sheet_id(&self, name: &str) -> Result<SheetId, ExcelError> {
675 self.sheet_id(name).ok_or_else(|| {
676 ExcelError::new(ExcelErrorKind::Ref).with_message(format!("Sheet not found: {name}"))
677 })
678 }
679
680 pub fn sheet_name(&self, id: SheetId) -> &str {
682 self.sheet_reg.name(id)
683 }
684
685 pub fn sheet_reg(&self) -> &SheetRegistry {
687 &self.sheet_reg
688 }
689
690 pub(crate) fn data_store(&self) -> &DataStore {
691 &self.data_store
692 }
693
694 pub fn to_a1(&self, cell_ref: CellRef) -> String {
696 format!("{}!{}", self.sheet_name(cell_ref.sheet_id), cell_ref.coord)
697 }
698
699 pub(crate) fn vertex_len(&self) -> usize {
700 self.store.len()
701 }
702
703 pub fn sheet_index_mut(&mut self, sheet_id: SheetId) -> &mut SheetIndex {
706 self.sheet_indexes.entry(sheet_id).or_default()
707 }
708
709 pub fn sheet_index(&self, sheet_id: SheetId) -> Option<&SheetIndex> {
711 self.sheet_indexes.get(&sheet_id)
712 }
713
714 pub fn set_cell_value(
716 &mut self,
717 sheet: &str,
718 row: u32,
719 col: u32,
720 value: LiteralValue,
721 ) -> Result<OperationSummary, ExcelError> {
722 let value = normalize_stored_literal(value);
723 let sheet_id = self.sheet_id_mut(sheet);
724 let coord = Coord::from_excel(row, col, true, true);
726 let addr = CellRef::new(sheet_id, coord);
727 let mut created_placeholders = Vec::new();
728
729 let vertex_id = if let Some(&existing_id) = self.cell_to_vertex.get(&addr) {
730 let is_formula = matches!(
732 self.store.kind(existing_id),
733 VertexKind::FormulaScalar | VertexKind::FormulaArray
734 );
735
736 if is_formula {
737 self.remove_dependent_edges(existing_id);
738 self.vertex_formulas.remove(&existing_id);
739 }
740
741 self.store.set_kind(existing_id, VertexKind::Cell);
743 if self.value_cache_enabled {
744 let value_ref = self.data_store.store_value(value);
745 self.vertex_values.insert(existing_id, value_ref);
746 } else {
747 self.vertex_values.remove(&existing_id);
749 }
750 existing_id
751 } else {
752 created_placeholders.push(addr);
754 let packed_coord = AbsCoord::from_excel(row, col);
755 let vertex_id = self.store.allocate(packed_coord, sheet_id, 0x01); self.edges.add_vertex(packed_coord, vertex_id.0);
759
760 self.sheet_index_mut(sheet_id)
762 .add_vertex(packed_coord, vertex_id);
763
764 self.store.set_kind(vertex_id, VertexKind::Cell);
765 if self.value_cache_enabled {
766 let value_ref = self.data_store.store_value(value);
767 self.vertex_values.insert(vertex_id, value_ref);
768 }
769 self.cell_to_vertex.insert(addr, vertex_id);
770 vertex_id
771 };
772
773 self.ref_error_vertices.remove(&vertex_id);
775
776 Ok(OperationSummary {
777 affected_vertices: self.mark_dirty(vertex_id),
778 created_placeholders,
779 })
780 }
781
782 pub fn reserve_cells(&mut self, additional: usize) {
784 self.store.reserve(additional);
785 if self.value_cache_enabled {
786 self.vertex_values.reserve(additional);
787 }
788 self.cell_to_vertex.reserve(additional);
789 }
791
792 pub fn set_cell_value_bulk_untracked(
794 &mut self,
795 sheet: &str,
796 row: u32,
797 col: u32,
798 value: LiteralValue,
799 ) {
800 let value = normalize_stored_literal(value);
801 let sheet_id = self.sheet_id_mut(sheet);
802 let coord = Coord::from_excel(row, col, true, true);
803 let addr = CellRef::new(sheet_id, coord);
804 if let Some(&existing_id) = self.cell_to_vertex.get(&addr) {
805 if self.value_cache_enabled {
807 let value_ref = self.data_store.store_value(value);
808 self.vertex_values.insert(existing_id, value_ref);
809 } else {
810 self.vertex_values.remove(&existing_id);
811 }
812 self.store.set_kind(existing_id, VertexKind::Cell);
813 self.ref_error_vertices.remove(&existing_id);
814 return;
815 }
816 let packed_coord = AbsCoord::from_excel(row, col);
817 let vertex_id = self.store.allocate(packed_coord, sheet_id, 0x00); self.edges.add_vertex(packed_coord, vertex_id.0);
819 self.sheet_index_mut(sheet_id)
820 .add_vertex(packed_coord, vertex_id);
821 self.store.set_kind(vertex_id, VertexKind::Cell);
822 self.ref_error_vertices.remove(&vertex_id);
823 if self.value_cache_enabled {
824 let value_ref = self.data_store.store_value(value);
825 self.vertex_values.insert(vertex_id, value_ref);
826 }
827 self.cell_to_vertex.insert(addr, vertex_id);
828 }
829
830 pub fn bulk_insert_values<I>(&mut self, sheet: &str, cells: I)
832 where
833 I: IntoIterator<Item = (u32, u32, LiteralValue)>,
834 {
835 use web_time::Instant;
836 let t0 = Instant::now();
837 let collected: Vec<(u32, u32, LiteralValue)> = cells.into_iter().collect();
839 if collected.is_empty() {
840 return;
841 }
842 let sheet_id = self.sheet_id_mut(sheet);
843 self.reserve_cells(collected.len());
844 let t_reserve = Instant::now();
845 let mut new_vertices: Vec<(AbsCoord, u32)> = Vec::with_capacity(collected.len());
846 let mut index_items: Vec<(AbsCoord, VertexId)> = Vec::with_capacity(collected.len());
847 let mut new_value_coords: Vec<(AbsCoord, VertexId)> = Vec::with_capacity(collected.len());
849 let mut new_value_literals: Vec<LiteralValue> = Vec::with_capacity(collected.len());
850 let assume_new = self.first_load_assume_new
852 && self
853 .sheet_id(sheet)
854 .map(|sid| !self.ensure_touched_sheets.contains(&sid))
855 .unwrap_or(false);
856
857 for (row, col, value) in collected {
858 let value = normalize_stored_literal(value);
859 let coord = Coord::from_excel(row, col, true, true);
860 let addr = CellRef::new(sheet_id, coord);
861 if !assume_new && let Some(&existing_id) = self.cell_to_vertex.get(&addr) {
862 if self.value_cache_enabled {
863 let value_ref = self.data_store.store_value(value);
864 self.vertex_values.insert(existing_id, value_ref);
865 } else {
866 self.vertex_values.remove(&existing_id);
867 }
868 self.store.set_kind(existing_id, VertexKind::Cell);
869 continue;
870 }
871 let packed = AbsCoord::from_excel(row, col);
872 let vertex_id = self.store.allocate(packed, sheet_id, 0x00);
873 self.store.set_kind(vertex_id, VertexKind::Cell);
874 new_value_coords.push((packed, vertex_id));
876 new_value_literals.push(value);
877 self.cell_to_vertex.insert(addr, vertex_id);
878 new_vertices.push((packed, vertex_id.0));
879 index_items.push((packed, vertex_id));
880 }
881 if self.value_cache_enabled && !new_value_literals.is_empty() {
883 let vrefs = self.data_store.store_values_batch(new_value_literals);
884 debug_assert_eq!(vrefs.len(), new_value_coords.len());
885 for (i, (_pc, vid)) in new_value_coords.iter().enumerate() {
886 self.vertex_values.insert(*vid, vrefs[i]);
887 }
888 }
889 let t_after_alloc = Instant::now();
890 if !new_vertices.is_empty() {
891 let t_edges_start = Instant::now();
892 self.edges.add_vertices_batch(&new_vertices);
893 let t_edges_done = Instant::now();
894
895 match self.config.sheet_index_mode {
896 crate::engine::SheetIndexMode::Eager => {
897 self.sheet_index_mut(sheet_id)
898 .add_vertices_batch(&index_items);
899 }
900 crate::engine::SheetIndexMode::Lazy => {
901 }
903 crate::engine::SheetIndexMode::FastBatch => {
904 self.sheet_index_mut(sheet_id)
906 .add_vertices_batch(&index_items);
907 }
908 }
909 let t_index_done = Instant::now();
910 }
911 }
912
913 pub fn set_cell_formula(
915 &mut self,
916 sheet: &str,
917 row: u32,
918 col: u32,
919 ast: ASTNode,
920 ) -> Result<OperationSummary, ExcelError> {
921 let volatile = self.is_ast_volatile(&ast);
922 self.set_cell_formula_with_volatility(sheet, row, col, ast, volatile)
923 }
924
925 pub fn set_cell_formula_with_volatility(
927 &mut self,
928 sheet: &str,
929 row: u32,
930 col: u32,
931 ast: ASTNode,
932 volatile: bool,
933 ) -> Result<OperationSummary, ExcelError> {
934 let dbg = std::env::var("FZ_DEBUG_LOAD")
935 .ok()
936 .is_some_and(|v| v != "0");
937 let dep_ms_thresh: u128 = std::env::var("FZ_DEBUG_DEP_MS")
938 .ok()
939 .and_then(|s| s.parse().ok())
940 .unwrap_or(0);
941 let sample_n: usize = std::env::var("FZ_DEBUG_SAMPLE_N")
942 .ok()
943 .and_then(|s| s.parse().ok())
944 .unwrap_or(0);
945 let t0 = if dbg {
946 Some(web_time::Instant::now())
947 } else {
948 None
949 };
950 let sheet_id = self.sheet_id_mut(sheet);
951 let coord = Coord::from_excel(row, col, true, true);
952 let addr = CellRef::new(sheet_id, coord);
953
954 let mut ast = ast;
957 self.rewrite_structured_references_for_cell(&mut ast, addr)?;
958
959 let t_dep0 = if dbg {
961 Some(web_time::Instant::now())
962 } else {
963 None
964 };
965 let (
966 new_dependencies,
967 new_range_dependencies,
968 mut created_placeholders,
969 named_dependencies,
970 ) = self.extract_dependencies(&ast, sheet_id)?;
971 if let (true, Some(t)) = (dbg, t_dep0) {
972 let elapsed = t.elapsed().as_millis();
973 let do_log = (dep_ms_thresh > 0 && elapsed >= dep_ms_thresh)
975 || (sample_n > 0 && (row as usize).is_multiple_of(sample_n));
976 if dep_ms_thresh == 0 && sample_n == 0 {
977 if row.is_multiple_of(1000) {
979 eprintln!(
980 "[fz][dep] {}!{} extracted: deps={}, ranges={}, placeholders={}, names={} in {} ms",
981 self.sheet_name(sheet_id),
982 crate::reference::Coord::from_excel(row, col, true, true),
983 new_dependencies.len(),
984 new_range_dependencies.len(),
985 created_placeholders.len(),
986 named_dependencies.len(),
987 elapsed
988 );
989 }
990 } else if do_log {
991 eprintln!(
992 "[fz][dep] {}!{} extracted: deps={}, ranges={}, placeholders={}, names={} in {} ms",
993 self.sheet_name(sheet_id),
994 crate::reference::Coord::from_excel(row, col, true, true),
995 new_dependencies.len(),
996 new_range_dependencies.len(),
997 created_placeholders.len(),
998 named_dependencies.len(),
999 elapsed
1000 );
1001 }
1002 }
1003
1004 let addr_vertex_id = self.get_or_create_vertex(&addr, &mut created_placeholders);
1006
1007 self.ref_error_vertices.remove(&addr_vertex_id);
1009
1010 if new_dependencies.contains(&addr_vertex_id) {
1011 return Err(ExcelError::new(ExcelErrorKind::Circ)
1012 .with_message("Self-reference detected".to_string()));
1013 }
1014
1015 for &name_vertex in &named_dependencies {
1016 let mut visited = FxHashSet::default();
1017 if self.name_depends_on_vertex(name_vertex, addr_vertex_id, &mut visited) {
1018 return Err(ExcelError::new(ExcelErrorKind::Circ)
1019 .with_message("Circular reference through named range".to_string()));
1020 }
1021 }
1022
1023 self.remove_dependent_edges(addr_vertex_id);
1025 self.detach_vertex_from_names(addr_vertex_id);
1026
1027 self.store
1029 .set_kind(addr_vertex_id, VertexKind::FormulaScalar);
1030 let ast_id = self.data_store.store_ast(&ast, &self.sheet_reg);
1031 self.vertex_formulas.insert(addr_vertex_id, ast_id);
1032 self.store.set_dirty(addr_vertex_id, true);
1033
1034 self.vertex_values.remove(&addr_vertex_id);
1036
1037 self.mark_volatile(addr_vertex_id, volatile);
1038
1039 if !named_dependencies.is_empty() {
1040 self.attach_vertex_to_names(addr_vertex_id, &named_dependencies);
1041 }
1042
1043 if let (true, Some(t)) = (dbg, t0) {
1044 let elapsed = t.elapsed().as_millis();
1045 let log_set = dep_ms_thresh > 0 && elapsed >= dep_ms_thresh;
1046 if log_set {
1047 eprintln!(
1048 "[fz][set] {}!{} total {} ms",
1049 self.sheet_name(sheet_id),
1050 crate::reference::Coord::from_excel(row, col, true, true),
1051 elapsed
1052 );
1053 }
1054 }
1055
1056 self.add_dependent_edges(addr_vertex_id, &new_dependencies);
1058 self.add_range_dependent_edges(addr_vertex_id, &new_range_dependencies, sheet_id);
1059
1060 Ok(OperationSummary {
1061 affected_vertices: self.mark_dirty(addr_vertex_id),
1062 created_placeholders,
1063 })
1064 }
1065
1066 pub(crate) fn rewrite_structured_references_for_cell(
1067 &self,
1068 ast: &mut ASTNode,
1069 cell: CellRef,
1070 ) -> Result<(), ExcelError> {
1071 self.rewrite_structured_references_node(ast, cell)
1072 }
1073
1074 fn rewrite_structured_references_node(
1075 &self,
1076 node: &mut ASTNode,
1077 cell: CellRef,
1078 ) -> Result<(), ExcelError> {
1079 match &mut node.node_type {
1080 ASTNodeType::Reference { reference, .. } => {
1081 self.rewrite_structured_reference(reference, cell)
1082 }
1083 ASTNodeType::UnaryOp { expr, .. } => {
1084 self.rewrite_structured_references_node(expr, cell)
1085 }
1086 ASTNodeType::BinaryOp { left, right, .. } => {
1087 self.rewrite_structured_references_node(left, cell)?;
1088 self.rewrite_structured_references_node(right, cell)
1089 }
1090 ASTNodeType::Function { args, .. } => {
1091 for a in args.iter_mut() {
1092 self.rewrite_structured_references_node(a, cell)?;
1093 }
1094 Ok(())
1095 }
1096 ASTNodeType::Array(rows) => {
1097 for r in rows.iter_mut() {
1098 for item in r.iter_mut() {
1099 self.rewrite_structured_references_node(item, cell)?;
1100 }
1101 }
1102 Ok(())
1103 }
1104 ASTNodeType::Literal(_) => Ok(()),
1105 }
1106 }
1107
1108 fn rewrite_structured_reference(
1109 &self,
1110 reference: &mut ReferenceType,
1111 cell: CellRef,
1112 ) -> Result<(), ExcelError> {
1113 use formualizer_parse::parser::{SpecialItem, TableSpecifier};
1114
1115 let ReferenceType::Table(tref) = reference else {
1116 return Ok(());
1117 };
1118
1119 if !tref.name.is_empty() {
1121 return Ok(());
1122 }
1123
1124 let col_name = match &tref.specifier {
1125 Some(TableSpecifier::Combination(parts)) => {
1126 let mut saw_this_row = false;
1127 let mut col: Option<&str> = None;
1128 for p in parts {
1129 match p.as_ref() {
1130 TableSpecifier::SpecialItem(SpecialItem::ThisRow) => {
1131 saw_this_row = true;
1132 }
1133 TableSpecifier::Column(c) => {
1134 if col.is_some() {
1135 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
1136 "This-row structured reference with multiple columns is not supported"
1137 .to_string(),
1138 ));
1139 }
1140 col = Some(c.as_str());
1141 }
1142 other => {
1143 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
1144 format!(
1145 "Unsupported this-row structured reference component: {other}"
1146 ),
1147 ));
1148 }
1149 }
1150 }
1151 if !saw_this_row {
1152 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
1153 "Unnamed structured reference requires a this-row selector".to_string(),
1154 ));
1155 }
1156 col.ok_or_else(|| {
1157 ExcelError::new(ExcelErrorKind::NImpl).with_message(
1158 "This-row structured reference missing column selector".to_string(),
1159 )
1160 })?
1161 }
1162 _ => {
1163 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
1164 "Unnamed structured reference form is not supported".to_string(),
1165 ));
1166 }
1167 };
1168
1169 let Some(table) = self.find_table_containing_cell(cell) else {
1170 return Err(ExcelError::new(ExcelErrorKind::Name)
1171 .with_message("This-row structured reference used outside a table".to_string()));
1172 };
1173
1174 let row0 = cell.coord.row();
1175 let col0 = cell.coord.col();
1176 let sr0 = table.range.start.coord.row();
1177 let sc0 = table.range.start.coord.col();
1178 let er0 = table.range.end.coord.row();
1179 let ec0 = table.range.end.coord.col();
1180
1181 if row0 < sr0 || row0 > er0 || col0 < sc0 || col0 > ec0 {
1182 return Err(ExcelError::new(ExcelErrorKind::Name)
1183 .with_message("This-row structured reference used outside a table".to_string()));
1184 }
1185
1186 if table.header_row && row0 == sr0 {
1187 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
1188 "This-row structured references are not valid in the table header row".to_string(),
1189 ));
1190 }
1191
1192 let data_start = if table.header_row { sr0 + 1 } else { sr0 };
1193 if row0 < data_start {
1194 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
1195 "This-row structured references require a data/totals row context".to_string(),
1196 ));
1197 }
1198
1199 let Some(idx) = table.col_index(col_name) else {
1200 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(format!(
1201 "Unknown table column in this-row reference: {col_name}"
1202 )));
1203 };
1204 let target_col0 = sc0 + (idx as u32);
1205 let target_row = row0 + 1;
1206 let target_col = target_col0 + 1;
1207
1208 *reference = ReferenceType::Cell {
1209 sheet: None,
1210 row: target_row,
1211 col: target_col,
1212 row_abs: true,
1213 col_abs: true,
1214 };
1215
1216 Ok(())
1217 }
1218
1219 fn find_table_containing_cell(&self, cell: CellRef) -> Option<&tables::TableEntry> {
1220 let row0 = cell.coord.row();
1221 let col0 = cell.coord.col();
1222
1223 let mut best: Option<&tables::TableEntry> = None;
1224 let mut best_area: u64 = u64::MAX;
1225 let mut best_name: &str = "";
1226
1227 for t in self.tables.values() {
1228 if t.sheet_id() != cell.sheet_id {
1229 continue;
1230 }
1231 let sr0 = t.range.start.coord.row();
1232 let sc0 = t.range.start.coord.col();
1233 let er0 = t.range.end.coord.row();
1234 let ec0 = t.range.end.coord.col();
1235 if row0 < sr0 || row0 > er0 || col0 < sc0 || col0 > ec0 {
1236 continue;
1237 }
1238
1239 let h = (er0 - sr0 + 1) as u64;
1240 let w = (ec0 - sc0 + 1) as u64;
1241 let area = h.saturating_mul(w);
1242 let name = t.name.as_str();
1243 let better = match best {
1244 None => true,
1245 Some(_) => area < best_area || (area == best_area && name < best_name),
1246 };
1247 if better {
1248 best = Some(t);
1249 best_area = area;
1250 best_name = name;
1251 }
1252 }
1253
1254 best
1255 }
1256
1257 pub fn set_cell_value_ref(
1258 &mut self,
1259 cell: formualizer_common::SheetCellRef<'_>,
1260 value: LiteralValue,
1261 ) -> Result<OperationSummary, ExcelError> {
1262 let owned = cell.into_owned();
1263 let sheet_id = match owned.sheet {
1264 formualizer_common::SheetLocator::Id(id) => id,
1265 formualizer_common::SheetLocator::Name(name) => self.sheet_id_mut(name.as_ref()),
1266 formualizer_common::SheetLocator::Current => self.default_sheet_id,
1267 };
1268 let sheet_name = self.sheet_name(sheet_id).to_string();
1269 self.set_cell_value(
1270 &sheet_name,
1271 owned.coord.row() + 1,
1272 owned.coord.col() + 1,
1273 value,
1274 )
1275 }
1276
1277 pub fn set_cell_formula_ref(
1278 &mut self,
1279 cell: formualizer_common::SheetCellRef<'_>,
1280 ast: ASTNode,
1281 ) -> Result<OperationSummary, ExcelError> {
1282 let owned = cell.into_owned();
1283 let sheet_id = match owned.sheet {
1284 formualizer_common::SheetLocator::Id(id) => id,
1285 formualizer_common::SheetLocator::Name(name) => self.sheet_id_mut(name.as_ref()),
1286 formualizer_common::SheetLocator::Current => self.default_sheet_id,
1287 };
1288 let sheet_name = self.sheet_name(sheet_id).to_string();
1289 self.set_cell_formula(
1290 &sheet_name,
1291 owned.coord.row() + 1,
1292 owned.coord.col() + 1,
1293 ast,
1294 )
1295 }
1296
1297 pub fn get_cell_value_ref(
1298 &self,
1299 cell: formualizer_common::SheetCellRef<'_>,
1300 ) -> Option<LiteralValue> {
1301 let owned = cell.into_owned();
1302 let sheet_id = match owned.sheet {
1303 formualizer_common::SheetLocator::Id(id) => id,
1304 formualizer_common::SheetLocator::Name(name) => self.sheet_id(name.as_ref())?,
1305 formualizer_common::SheetLocator::Current => self.default_sheet_id,
1306 };
1307 let sheet_name = self.sheet_name(sheet_id);
1308 self.get_cell_value(sheet_name, owned.coord.row() + 1, owned.coord.col() + 1)
1309 }
1310
1311 pub fn get_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
1313 if !self.value_cache_enabled {
1314 #[cfg(debug_assertions)]
1315 {
1316 self.graph_value_read_attempts
1317 .fetch_add(1, Ordering::Relaxed);
1318 }
1319 return None;
1320 }
1321 let sheet_id = self.sheet_reg.get_id(sheet)?;
1322 let coord = Coord::from_excel(row, col, true, true);
1323 let addr = CellRef::new(sheet_id, coord);
1324
1325 self.cell_to_vertex.get(&addr).and_then(|&vertex_id| {
1326 self.vertex_values
1328 .get(&vertex_id)
1329 .map(|&value_ref| self.data_store.retrieve_value(value_ref))
1330 })
1331 }
1332
1333 fn mark_dirty(&mut self, vertex_id: VertexId) -> Vec<VertexId> {
1335 let mut affected = FxHashSet::default();
1336 let mut to_visit = Vec::new();
1337 let mut visited_for_propagation = FxHashSet::default();
1338
1339 let is_formula = matches!(
1342 self.store.kind(vertex_id),
1343 VertexKind::FormulaScalar
1344 | VertexKind::FormulaArray
1345 | VertexKind::NamedScalar
1346 | VertexKind::NamedArray
1347 );
1348
1349 if is_formula {
1350 to_visit.push(vertex_id);
1351 } else {
1352 affected.insert(vertex_id);
1354 }
1355
1356 {
1358 let dependents = self.get_dependents(vertex_id);
1360 to_visit.extend(&dependents);
1361
1362 if let Some(name_set) = self.cell_to_name_dependents.get(&vertex_id) {
1363 for &name_vertex in name_set {
1364 to_visit.push(name_vertex);
1365 }
1366 }
1367
1368 let view = self.store.view(vertex_id);
1370 let row = view.row();
1371 let col = view.col();
1372 let dirty_sheet_id = view.sheet_id();
1373
1374 let mut potential_dependents = FxHashSet::default();
1376
1377 let column_key = StripeKey {
1379 sheet_id: dirty_sheet_id,
1380 stripe_type: StripeType::Column,
1381 index: col,
1382 };
1383 if let Some(dependents) = self.stripe_to_dependents.get(&column_key) {
1384 potential_dependents.extend(dependents);
1385 }
1386
1387 let row_key = StripeKey {
1389 sheet_id: dirty_sheet_id,
1390 stripe_type: StripeType::Row,
1391 index: row,
1392 };
1393 if let Some(dependents) = self.stripe_to_dependents.get(&row_key) {
1394 potential_dependents.extend(dependents);
1395 }
1396
1397 if self.config.enable_block_stripes {
1399 let block_key = StripeKey {
1400 sheet_id: dirty_sheet_id,
1401 stripe_type: StripeType::Block,
1402 index: block_index(row, col),
1403 };
1404 if let Some(dependents) = self.stripe_to_dependents.get(&block_key) {
1405 potential_dependents.extend(dependents);
1406 }
1407 }
1408
1409 for &dep_id in &potential_dependents {
1411 if let Some(ranges) = self.formula_to_range_deps.get(&dep_id) {
1412 for range in ranges {
1413 let range_sheet_id = match range.sheet {
1414 SharedSheetLocator::Id(id) => id,
1415 _ => dirty_sheet_id,
1416 };
1417 if range_sheet_id != dirty_sheet_id {
1418 continue;
1419 }
1420 let sr0 = range.start_row.map(|b| b.index).unwrap_or(0);
1421 let er0 = range.end_row.map(|b| b.index).unwrap_or(u32::MAX);
1422 let sc0 = range.start_col.map(|b| b.index).unwrap_or(0);
1423 let ec0 = range.end_col.map(|b| b.index).unwrap_or(u32::MAX);
1424 if row >= sr0 && row <= er0 && col >= sc0 && col <= ec0 {
1425 to_visit.push(dep_id);
1426 break;
1427 }
1428 }
1429 }
1430 }
1431 }
1432
1433 while let Some(id) = to_visit.pop() {
1434 if !visited_for_propagation.insert(id) {
1435 continue; }
1437 affected.insert(id);
1438
1439 self.store.set_dirty(id, true);
1441
1442 let dependents = self.get_dependents(id);
1444 to_visit.extend(&dependents);
1445 }
1446
1447 self.dirty_vertices.extend(&affected);
1449
1450 affected.into_iter().collect()
1452 }
1453
1454 pub fn get_evaluation_vertices(&self) -> Vec<VertexId> {
1456 let mut combined = FxHashSet::default();
1457 combined.extend(&self.dirty_vertices);
1458 combined.extend(&self.volatile_vertices);
1459
1460 let mut result: Vec<VertexId> = combined
1461 .into_iter()
1462 .filter(|&id| {
1463 matches!(
1465 self.store.kind(id),
1466 VertexKind::FormulaScalar
1467 | VertexKind::FormulaArray
1468 | VertexKind::NamedScalar
1469 | VertexKind::NamedArray
1470 )
1471 })
1472 .collect();
1473 result.sort_unstable();
1474 result
1475 }
1476
1477 pub fn clear_dirty_flags(&mut self, vertices: &[VertexId]) {
1479 for &vertex_id in vertices {
1480 self.store.set_dirty(vertex_id, false);
1481 self.dirty_vertices.remove(&vertex_id);
1482 }
1483 }
1484
1485 pub fn clear_volatile_flags(&mut self) {
1487 self.volatile_vertices.clear();
1488 }
1489
1490 pub(crate) fn redirty_volatiles(&mut self) {
1492 let volatile_ids: Vec<VertexId> = self.volatile_vertices.iter().copied().collect();
1493 for id in volatile_ids {
1494 self.mark_dirty(id);
1495 }
1496 }
1497
1498 fn get_or_create_vertex(
1499 &mut self,
1500 addr: &CellRef,
1501 created_placeholders: &mut Vec<CellRef>,
1502 ) -> VertexId {
1503 if let Some(&vertex_id) = self.cell_to_vertex.get(addr) {
1504 return vertex_id;
1505 }
1506
1507 created_placeholders.push(*addr);
1508 let packed_coord = AbsCoord::new(addr.coord.row(), addr.coord.col());
1509 let vertex_id = self.store.allocate(packed_coord, addr.sheet_id, 0x00);
1510
1511 self.edges.add_vertex(packed_coord, vertex_id.0);
1513
1514 self.sheet_index_mut(addr.sheet_id)
1516 .add_vertex(packed_coord, vertex_id);
1517
1518 self.store.set_kind(vertex_id, VertexKind::Empty);
1519 self.cell_to_vertex.insert(*addr, vertex_id);
1520 vertex_id
1521 }
1522
1523 fn add_dependent_edges(&mut self, dependent: VertexId, dependencies: &[VertexId]) {
1524 self.edges.begin_batch();
1526
1527 let mut skip_deps: rustc_hash::FxHashSet<VertexId> = rustc_hash::FxHashSet::default();
1530 if self.pk_order.is_some()
1531 && let Some(mut pk) = self.pk_order.take()
1532 {
1533 pk.ensure_nodes(std::iter::once(dependent));
1534 pk.ensure_nodes(dependencies.iter().copied());
1535 {
1536 let adapter = GraphAdapter { g: self };
1537 for &dep_id in dependencies {
1538 match pk.try_add_edge(&adapter, dep_id, dependent) {
1539 Ok(_) => {}
1540 Err(_cycle) => {
1541 if self.config.pk_reject_cycle_edges {
1542 skip_deps.insert(dep_id);
1543 } else {
1544 pk.rebuild_full(&adapter);
1545 }
1546 }
1547 }
1548 }
1549 } self.pk_order = Some(pk);
1551 }
1552
1553 for &dep_id in dependencies {
1555 if self.config.pk_reject_cycle_edges && skip_deps.contains(&dep_id) {
1556 continue;
1557 }
1558 self.edges.add_edge(dependent, dep_id);
1559 #[cfg(test)]
1560 {
1561 if let Ok(mut g) = self.instr.lock() {
1562 g.edges_added += 1;
1563 }
1564 }
1565 }
1566
1567 self.edges.end_batch();
1568 }
1569
1570 fn add_dependent_edges_nobatch(&mut self, dependent: VertexId, dependencies: &[VertexId]) {
1572 let mut skip_deps: rustc_hash::FxHashSet<VertexId> = rustc_hash::FxHashSet::default();
1574 if self.pk_order.is_some()
1575 && let Some(mut pk) = self.pk_order.take()
1576 {
1577 pk.ensure_nodes(std::iter::once(dependent));
1578 pk.ensure_nodes(dependencies.iter().copied());
1579 {
1580 let adapter = GraphAdapter { g: self };
1581 for &dep_id in dependencies {
1582 match pk.try_add_edge(&adapter, dep_id, dependent) {
1583 Ok(_) => {}
1584 Err(_cycle) => {
1585 if self.config.pk_reject_cycle_edges {
1586 skip_deps.insert(dep_id);
1587 } else {
1588 pk.rebuild_full(&adapter);
1589 }
1590 }
1591 }
1592 }
1593 }
1594 self.pk_order = Some(pk);
1595 }
1596
1597 for &dep_id in dependencies {
1598 if self.config.pk_reject_cycle_edges && skip_deps.contains(&dep_id) {
1599 continue;
1600 }
1601 self.edges.add_edge(dependent, dep_id);
1602 #[cfg(test)]
1603 {
1604 if let Ok(mut g) = self.instr.lock() {
1605 g.edges_added += 1;
1606 }
1607 }
1608 }
1609 }
1610
1611 pub fn bulk_set_formulas<I>(&mut self, sheet: &str, items: I) -> Result<usize, ExcelError>
1613 where
1614 I: IntoIterator<Item = (u32, u32, ASTNode)>,
1615 {
1616 let collected: Vec<(u32, u32, ASTNode)> = items.into_iter().collect();
1617 if collected.is_empty() {
1618 return Ok(0);
1619 }
1620 let vol_flags: Vec<bool> = collected
1621 .iter()
1622 .map(|(_, _, ast)| self.is_ast_volatile(ast))
1623 .collect();
1624 self.bulk_set_formulas_with_volatility(sheet, collected, vol_flags)
1625 }
1626
1627 pub fn bulk_set_formulas_with_volatility(
1628 &mut self,
1629 sheet: &str,
1630 collected: Vec<(u32, u32, ASTNode)>,
1631 vol_flags: Vec<bool>,
1632 ) -> Result<usize, ExcelError> {
1633 use formualizer_parse::parser::CollectPolicy;
1634 let sheet_id = self.sheet_id_mut(sheet);
1635
1636 if collected.is_empty() {
1637 return Ok(0);
1638 }
1639
1640 let tiny_refs = collected.iter().map(|(r, c, ast)| (sheet, *r, *c, ast));
1642 let policy = CollectPolicy {
1643 expand_small_ranges: true,
1644 range_expansion_limit: self.config.range_expansion_limit,
1645 include_names: true,
1646 };
1647 let plan = crate::engine::plan::build_dependency_plan(
1648 &mut self.sheet_reg,
1649 tiny_refs,
1650 &policy,
1651 Some(&vol_flags),
1652 )?;
1653
1654 let mut created_placeholders: Vec<CellRef> = Vec::new();
1656
1657 let mut target_vids: Vec<VertexId> = Vec::with_capacity(plan.formula_targets.len());
1659 for (sid, pc) in &plan.formula_targets {
1660 let addr = CellRef::new(*sid, Coord::new(pc.row(), pc.col(), true, true));
1661 let vid = if let Some(&existing) = self.cell_to_vertex.get(&addr) {
1662 existing
1663 } else {
1664 self.get_or_create_vertex(&addr, &mut created_placeholders)
1665 };
1666 target_vids.push(vid);
1667 }
1668
1669 let mut dep_vids: Vec<VertexId> = Vec::with_capacity(plan.global_cells.len());
1671 for (sid, pc) in &plan.global_cells {
1672 let addr = CellRef::new(*sid, Coord::new(pc.row(), pc.col(), true, true));
1673 let vid = if let Some(&existing) = self.cell_to_vertex.get(&addr) {
1674 existing
1675 } else {
1676 self.get_or_create_vertex(&addr, &mut created_placeholders)
1677 };
1678 dep_vids.push(vid);
1679 }
1680
1681 let ast_ids = self
1683 .data_store
1684 .store_asts_batch(collected.iter().map(|(_, _, ast)| ast), &self.sheet_reg);
1685
1686 for (i, &tvid) in target_vids.iter().enumerate() {
1687 if self.vertex_formulas.contains_key(&tvid) {
1689 self.remove_dependent_edges(tvid);
1690 }
1691 self.store.set_kind(tvid, VertexKind::FormulaScalar);
1692 self.store.set_dirty(tvid, true);
1693 self.vertex_values.remove(&tvid);
1694 self.vertex_formulas.insert(tvid, ast_ids[i]);
1695 self.mark_volatile(tvid, vol_flags.get(i).copied().unwrap_or(false));
1696 }
1697
1698 self.edges.begin_batch();
1700 for (i, tvid) in target_vids.iter().copied().enumerate() {
1701 let mut deps: Vec<VertexId> = Vec::new();
1702
1703 if let Some(indices) = plan.per_formula_cells.get(i) {
1705 deps.reserve(indices.len());
1706 for &idx in indices {
1707 if let Some(vid) = dep_vids.get(idx as usize) {
1708 deps.push(*vid);
1709 }
1710 }
1711 }
1712
1713 if let Some(names) = plan.per_formula_names.get(i)
1714 && !names.is_empty()
1715 {
1716 let mut name_vertices = Vec::new();
1717 let formula_sheet = plan
1718 .formula_targets
1719 .get(i)
1720 .map(|(sid, _)| *sid)
1721 .unwrap_or(sheet_id);
1722 for name in names {
1723 if let Some(named) = self.resolve_name_entry(name, formula_sheet) {
1724 deps.push(named.vertex);
1725 name_vertices.push(named.vertex);
1726 } else if let Some(source) = self.resolve_source_scalar_entry(name) {
1727 deps.push(source.vertex);
1728 } else {
1729 self.record_pending_name_reference(formula_sheet, name, tvid);
1730 }
1731 }
1732 if !name_vertices.is_empty() {
1733 self.attach_vertex_to_names(tvid, &name_vertices);
1734 }
1735 }
1736
1737 if let Some(tables) = plan.per_formula_tables.get(i)
1738 && !tables.is_empty()
1739 {
1740 for table_name in tables {
1741 if let Some(table) = self.resolve_table_entry(table_name) {
1742 deps.push(table.vertex);
1743 } else if let Some(source) = self.resolve_source_table_entry(table_name) {
1744 deps.push(source.vertex);
1745 }
1746 }
1747 }
1748
1749 if !deps.is_empty() {
1750 self.add_dependent_edges_nobatch(tvid, &deps);
1751 }
1752
1753 if let Some(rks) = plan.per_formula_ranges.get(i) {
1755 self.add_range_deps_from_keys(tvid, rks, sheet_id);
1756 }
1757 }
1758 self.edges.end_batch();
1759
1760 Ok(collected.len())
1761 }
1762
1763 pub fn add_dependency_edge(&mut self, dependent: VertexId, dependency: VertexId) {
1765 if dependent == dependency {
1766 return;
1767 }
1768 if self.pk_order.is_some()
1770 && let Some(mut pk) = self.pk_order.take()
1771 {
1772 pk.ensure_nodes(std::iter::once(dependent));
1773 pk.ensure_nodes(std::iter::once(dependency));
1774 let adapter = GraphAdapter { g: self };
1775 if pk.try_add_edge(&adapter, dependency, dependent).is_err() {
1776 pk.rebuild_full(&adapter);
1778 }
1779 self.pk_order = Some(pk);
1780 }
1781 self.edges.add_edge(dependent, dependency);
1782 self.store.set_dirty(dependent, true);
1783 self.dirty_vertices.insert(dependent);
1784 }
1785
1786 fn remove_dependent_edges(&mut self, vertex: VertexId) {
1787 let dependencies = self.edges.out_edges(vertex);
1789
1790 self.edges.begin_batch();
1791 if self.pk_order.is_some()
1792 && let Some(mut pk) = self.pk_order.take()
1793 {
1794 for dep in &dependencies {
1795 pk.remove_edge(*dep, vertex);
1796 }
1797 self.pk_order = Some(pk);
1798 }
1799 for dep in dependencies {
1800 self.edges.remove_edge(vertex, dep);
1801 }
1802 self.edges.end_batch();
1803
1804 if let Some(old_ranges) = self.formula_to_range_deps.remove(&vertex) {
1806 let old_sheet_id = self.store.sheet_id(vertex);
1807
1808 for range in &old_ranges {
1809 let sheet_id = match range.sheet {
1810 SharedSheetLocator::Id(id) => id,
1811 _ => old_sheet_id,
1812 };
1813 let s_row = range.start_row.map(|b| b.index);
1814 let e_row = range.end_row.map(|b| b.index);
1815 let s_col = range.start_col.map(|b| b.index);
1816 let e_col = range.end_col.map(|b| b.index);
1817
1818 let mut keys_to_clean = FxHashSet::default();
1819
1820 let col_stripes = (s_row.is_none() && e_row.is_none())
1821 || (s_col.is_some() && e_col.is_some() && (s_row.is_none() || e_row.is_none()));
1822 let row_stripes = (s_col.is_none() && e_col.is_none())
1823 || (s_row.is_some() && e_row.is_some() && (s_col.is_none() || e_col.is_none()));
1824
1825 if col_stripes && !row_stripes {
1826 let sc = s_col.unwrap_or(0);
1827 let ec = e_col.unwrap_or(sc);
1828 for col in sc..=ec {
1829 keys_to_clean.insert(StripeKey {
1830 sheet_id,
1831 stripe_type: StripeType::Column,
1832 index: col,
1833 });
1834 }
1835 } else if row_stripes && !col_stripes {
1836 let sr = s_row.unwrap_or(0);
1837 let er = e_row.unwrap_or(sr);
1838 for row in sr..=er {
1839 keys_to_clean.insert(StripeKey {
1840 sheet_id,
1841 stripe_type: StripeType::Row,
1842 index: row,
1843 });
1844 }
1845 } else {
1846 let start_row = s_row.unwrap_or(0);
1847 let start_col = s_col.unwrap_or(0);
1848 let end_row = e_row.unwrap_or(start_row);
1849 let end_col = e_col.unwrap_or(start_col);
1850
1851 let height = end_row.saturating_sub(start_row) + 1;
1852 let width = end_col.saturating_sub(start_col) + 1;
1853
1854 if self.config.enable_block_stripes && height > 1 && width > 1 {
1855 let start_block_row = start_row / BLOCK_H;
1856 let end_block_row = end_row / BLOCK_H;
1857 let start_block_col = start_col / BLOCK_W;
1858 let end_block_col = end_col / BLOCK_W;
1859
1860 for block_row in start_block_row..=end_block_row {
1861 for block_col in start_block_col..=end_block_col {
1862 keys_to_clean.insert(StripeKey {
1863 sheet_id,
1864 stripe_type: StripeType::Block,
1865 index: block_index(block_row * BLOCK_H, block_col * BLOCK_W),
1866 });
1867 }
1868 }
1869 } else if height > width {
1870 for col in start_col..=end_col {
1871 keys_to_clean.insert(StripeKey {
1872 sheet_id,
1873 stripe_type: StripeType::Column,
1874 index: col,
1875 });
1876 }
1877 } else {
1878 for row in start_row..=end_row {
1879 keys_to_clean.insert(StripeKey {
1880 sheet_id,
1881 stripe_type: StripeType::Row,
1882 index: row,
1883 });
1884 }
1885 }
1886 }
1887
1888 for key in keys_to_clean {
1889 if let Some(dependents) = self.stripe_to_dependents.get_mut(&key) {
1890 dependents.remove(&vertex);
1891 if dependents.is_empty() {
1892 self.stripe_to_dependents.remove(&key);
1893 #[cfg(test)]
1894 {
1895 if let Ok(mut g) = self.instr.lock() {
1896 g.stripe_removes += 1;
1897 }
1898 }
1899 }
1900 }
1901 }
1902 }
1903 }
1904 }
1905
1906 pub(crate) fn update_vertex_value(&mut self, vertex_id: VertexId, value: LiteralValue) {
1912 if !self.value_cache_enabled {
1913 match self.store.kind(vertex_id) {
1915 VertexKind::Cell
1916 | VertexKind::FormulaScalar
1917 | VertexKind::FormulaArray
1918 | VertexKind::Empty => {
1919 self.vertex_values.remove(&vertex_id);
1920 return;
1921 }
1922 _ => {
1923 }
1925 }
1926 }
1927 let value_ref = self.data_store.store_value(normalize_stored_literal(value));
1928 self.vertex_values.insert(vertex_id, value_ref);
1929 }
1930
1931 pub fn plan_spill_region(
1933 &self,
1934 anchor: VertexId,
1935 target_cells: &[CellRef],
1936 ) -> Result<(), ExcelError> {
1937 self.plan_spill_region_allowing_formula_overwrite(anchor, target_cells, None)
1938 }
1939
1940 pub(crate) fn plan_spill_region_allowing_formula_overwrite(
1945 &self,
1946 anchor: VertexId,
1947 target_cells: &[CellRef],
1948 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
1949 ) -> Result<(), ExcelError> {
1950 use formualizer_common::{ExcelErrorExtra, ExcelErrorKind};
1951 let (expected_rows, expected_cols) = if target_cells.is_empty() {
1953 (0u32, 0u32)
1954 } else {
1955 let mut min_r = u32::MAX;
1956 let mut max_r = 0u32;
1957 let mut min_c = u32::MAX;
1958 let mut max_c = 0u32;
1959 for cell in target_cells {
1960 let r = cell.coord.row();
1961 let c = cell.coord.col();
1962 if r < min_r {
1963 min_r = r;
1964 }
1965 if r > max_r {
1966 max_r = r;
1967 }
1968 if c < min_c {
1969 min_c = c;
1970 }
1971 if c > max_c {
1972 max_c = c;
1973 }
1974 }
1975 (
1976 max_r.saturating_sub(min_r).saturating_add(1),
1977 max_c.saturating_sub(min_c).saturating_add(1),
1978 )
1979 };
1980 for cell in target_cells {
1982 let owned_by_anchor = match self.spill_cell_to_anchor.get(cell) {
1984 Some(&existing_anchor) if existing_anchor == anchor => true,
1985 Some(_other) => {
1986 return Err(ExcelError::new(ExcelErrorKind::Spill)
1987 .with_message("BlockedBySpill")
1988 .with_extra(ExcelErrorExtra::Spill {
1989 expected_rows,
1990 expected_cols,
1991 }));
1992 }
1993 None => false,
1994 };
1995
1996 if owned_by_anchor {
1997 continue;
1998 }
1999
2000 if let Some(&vid) = self.cell_to_vertex.get(cell)
2002 && vid != anchor
2003 {
2004 match self.store.kind(vid) {
2006 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
2007 if let Some(allow) = overwritable_formulas
2008 && allow.contains(&vid)
2009 {
2010 continue;
2011 }
2012 return Err(ExcelError::new(ExcelErrorKind::Spill)
2013 .with_message("BlockedByFormula")
2014 .with_extra(ExcelErrorExtra::Spill {
2015 expected_rows,
2016 expected_cols,
2017 }));
2018 }
2019 _ => {
2020 if let Some(vref) = self.vertex_values.get(&vid) {
2022 let v = self.data_store.retrieve_value(*vref);
2023 if !matches!(v, LiteralValue::Empty) {
2024 return Err(ExcelError::new(ExcelErrorKind::Spill)
2025 .with_message("BlockedByValue")
2026 .with_extra(ExcelErrorExtra::Spill {
2027 expected_rows,
2028 expected_cols,
2029 }));
2030 }
2031 }
2032 }
2033 }
2034 }
2035 }
2036 Ok(())
2037 }
2038
2039 pub fn commit_spill_region_atomic_with_fault(
2046 &mut self,
2047 anchor: VertexId,
2048 target_cells: Vec<CellRef>,
2049 values: Vec<Vec<LiteralValue>>,
2050 fault_after_ops: Option<usize>,
2051 ) -> Result<(), ExcelError> {
2052 use rustc_hash::FxHashSet;
2053
2054 let anchor_cell = self
2058 .get_cell_ref(anchor)
2059 .expect("anchor cell ref for spill commit");
2060 let anchor_sheet_name = self.sheet_name(anchor_cell.sheet_id).to_string();
2061 let anchor_row = anchor_cell.coord.row();
2062 let anchor_col = anchor_cell.coord.col();
2063
2064 let prev_cells = self
2066 .spill_anchor_to_cells
2067 .get(&anchor)
2068 .cloned()
2069 .unwrap_or_default();
2070 let new_set: FxHashSet<CellRef> = target_cells.iter().copied().collect();
2071 let prev_set: FxHashSet<CellRef> = prev_cells.iter().copied().collect();
2072
2073 #[derive(Clone)]
2075 struct Op {
2076 sheet: String,
2077 row: u32,
2078 col: u32,
2079 new_value: LiteralValue,
2080 }
2081 let mut ops: Vec<Op> = Vec::new();
2082
2083 for cell in prev_cells.iter() {
2085 if !new_set.contains(cell) {
2086 let sheet = self.sheet_name(cell.sheet_id).to_string();
2087 ops.push(Op {
2088 sheet,
2089 row: cell.coord.row(),
2090 col: cell.coord.col(),
2091 new_value: LiteralValue::Empty,
2092 });
2093 }
2094 }
2095
2096 if !target_cells.is_empty() {
2098 let first = target_cells.first().copied().unwrap();
2099 let row0 = first.coord.row();
2100 let col0 = first.coord.col();
2101 let sheet = self.sheet_name(first.sheet_id).to_string();
2102 for (r_off, row_vals) in values.iter().enumerate() {
2103 for (c_off, v) in row_vals.iter().enumerate() {
2104 ops.push(Op {
2105 sheet: sheet.clone(),
2106 row: row0 + r_off as u32,
2107 col: col0 + c_off as u32,
2108 new_value: v.clone(),
2109 });
2110 }
2111 }
2112 }
2113
2114 #[derive(Clone)]
2116 struct OldVal {
2117 present: bool,
2118 value: LiteralValue,
2119 }
2120 let mut old_values: Vec<((String, u32, u32), OldVal)> = Vec::with_capacity(ops.len());
2121
2122 for op in &ops {
2124 let old = self
2126 .get_cell_value(&op.sheet, op.row + 1, op.col + 1)
2127 .unwrap_or(LiteralValue::Empty);
2128 let present = true; old_values.push((
2130 (op.sheet.clone(), op.row, op.col),
2131 OldVal {
2132 present,
2133 value: old,
2134 },
2135 ));
2136 }
2137
2138 for (applied, op) in ops.iter().enumerate() {
2140 if let Some(n) = fault_after_ops
2141 && applied == n
2142 {
2143 for idx in (0..applied).rev() {
2144 let ((ref sheet, row, col), ref old) = old_values[idx];
2145 if sheet == &anchor_sheet_name && row == anchor_row && col == anchor_col {
2146 self.update_vertex_value(anchor, old.value.clone());
2147 } else {
2148 let _ = self.set_cell_value(sheet, row + 1, col + 1, old.value.clone());
2149 }
2150 }
2151 return Err(ExcelError::new(ExcelErrorKind::Error)
2152 .with_message("Injected persistence fault during spill commit"));
2153 }
2154 if op.sheet == anchor_sheet_name && op.row == anchor_row && op.col == anchor_col {
2155 self.update_vertex_value(anchor, op.new_value.clone());
2156 } else {
2157 let _ =
2158 self.set_cell_value(&op.sheet, op.row + 1, op.col + 1, op.new_value.clone());
2159 }
2160 }
2161
2162 for cell in prev_cells.iter() {
2165 if !new_set.contains(cell) {
2166 self.spill_cell_to_anchor.remove(cell);
2167 }
2168 }
2169 for cell in &target_cells {
2171 self.spill_cell_to_anchor.insert(*cell, anchor);
2172 }
2173 self.spill_anchor_to_cells.insert(anchor, target_cells);
2174 Ok(())
2175 }
2176
2177 pub(crate) fn spill_cells_for_anchor(&self, anchor: VertexId) -> Option<&[CellRef]> {
2178 self.spill_anchor_to_cells
2179 .get(&anchor)
2180 .map(|v| v.as_slice())
2181 }
2182
2183 pub(crate) fn spill_registry_has_anchor(&self, anchor: VertexId) -> bool {
2184 self.spill_anchor_to_cells.contains_key(&anchor)
2185 }
2186
2187 pub(crate) fn spill_registry_anchor_for_cell(&self, cell: CellRef) -> Option<VertexId> {
2188 self.spill_cell_to_anchor.get(&cell).copied()
2189 }
2190
2191 pub(crate) fn spill_registry_counts(&self) -> (usize, usize) {
2192 (
2193 self.spill_anchor_to_cells.len(),
2194 self.spill_cell_to_anchor.len(),
2195 )
2196 }
2197
2198 pub fn clear_spill_region(&mut self, anchor: VertexId) {
2200 let _ = self.clear_spill_region_bulk(anchor);
2201 }
2202
2203 pub fn clear_spill_region_bulk(&mut self, anchor: VertexId) -> Vec<CellRef> {
2212 let anchor_cell = self.get_cell_ref(anchor);
2213 let Some(cells) = self.spill_anchor_to_cells.remove(&anchor) else {
2214 return Vec::new();
2215 };
2216
2217 for cell in cells.iter() {
2219 self.spill_cell_to_anchor.remove(cell);
2220 }
2221
2222 let empty_ref = if self.value_cache_enabled {
2224 Some(self.data_store.store_value(LiteralValue::Empty))
2225 } else {
2226 None
2227 };
2228
2229 let mut changed_vertices: Vec<VertexId> = Vec::new();
2231 for cell in cells.iter().copied() {
2232 let is_anchor = anchor_cell.map(|a| a == cell).unwrap_or(false);
2233 if is_anchor {
2234 continue;
2235 }
2236 let Some(&vid) = self.cell_to_vertex.get(&cell) else {
2237 continue;
2238 };
2239 if self.vertex_formulas.remove(&vid).is_some() {
2241 self.remove_dependent_edges(vid);
2244 }
2245 self.store.set_kind(vid, VertexKind::Cell);
2246 if let Some(er) = empty_ref {
2247 self.vertex_values.insert(vid, er);
2248 } else {
2249 self.vertex_values.remove(&vid);
2250 }
2251 self.store.set_dirty(vid, false);
2252 self.dirty_vertices.remove(&vid);
2253 changed_vertices.push(vid);
2254 }
2255
2256 if !changed_vertices.is_empty() {
2258 self.mark_dirty_many_value_cells(&changed_vertices);
2259 }
2260
2261 cells
2262 }
2263
2264 fn mark_dirty_many_value_cells(&mut self, vertex_ids: &[VertexId]) -> Vec<VertexId> {
2265 if vertex_ids.is_empty() {
2266 return Vec::new();
2267 }
2268
2269 if self.edges.delta_size() > 0 {
2271 self.edges.rebuild();
2272 }
2273
2274 let mut affected: FxHashSet<VertexId> = FxHashSet::default();
2275 let mut to_visit: Vec<VertexId> = Vec::new();
2276 let mut visited_for_propagation: FxHashSet<VertexId> = FxHashSet::default();
2277
2278 for &src in vertex_ids {
2280 affected.insert(src);
2281 }
2282
2283 for &src in vertex_ids {
2285 to_visit.extend(self.edges.in_edges(src));
2286 if let Some(name_set) = self.cell_to_name_dependents.get(&src) {
2287 for &name_vertex in name_set {
2288 to_visit.push(name_vertex);
2289 }
2290 }
2291 }
2292
2293 let mut bounds_by_sheet: FxHashMap<SheetId, (u32, u32, u32, u32)> = FxHashMap::default();
2295 for &src in vertex_ids {
2296 let view = self.store.view(src);
2297 let sid = view.sheet_id();
2298 let r = view.row();
2299 let c = view.col();
2300 bounds_by_sheet
2301 .entry(sid)
2302 .and_modify(|b| {
2303 b.0 = b.0.min(r);
2304 b.1 = b.1.max(r);
2305 b.2 = b.2.min(c);
2306 b.3 = b.3.max(c);
2307 })
2308 .or_insert((r, r, c, c));
2309 }
2310
2311 for (sid, (sr, er, sc, ec)) in bounds_by_sheet {
2312 to_visit.extend(self.collect_range_dependents_for_rect(sid, sr, sc, er, ec));
2313 }
2314
2315 while let Some(id) = to_visit.pop() {
2316 if !visited_for_propagation.insert(id) {
2317 continue;
2318 }
2319 affected.insert(id);
2320 self.store.set_dirty(id, true);
2321 to_visit.extend(self.edges.in_edges(id));
2322 }
2323
2324 self.dirty_vertices.extend(&affected);
2325 affected.into_iter().collect()
2326 }
2327
2328 fn collect_range_dependents_for_rect(
2329 &self,
2330 sheet_id: SheetId,
2331 start_row: u32,
2332 start_col: u32,
2333 end_row: u32,
2334 end_col: u32,
2335 ) -> Vec<VertexId> {
2336 if self.stripe_to_dependents.is_empty() {
2337 return Vec::new();
2338 }
2339 let mut candidates: FxHashSet<VertexId> = FxHashSet::default();
2340
2341 for col in start_col..=end_col {
2342 let key = StripeKey {
2343 sheet_id,
2344 stripe_type: StripeType::Column,
2345 index: col,
2346 };
2347 if let Some(deps) = self.stripe_to_dependents.get(&key) {
2348 candidates.extend(deps);
2349 }
2350 }
2351 for row in start_row..=end_row {
2352 let key = StripeKey {
2353 sheet_id,
2354 stripe_type: StripeType::Row,
2355 index: row,
2356 };
2357 if let Some(deps) = self.stripe_to_dependents.get(&key) {
2358 candidates.extend(deps);
2359 }
2360 }
2361 if self.config.enable_block_stripes {
2362 let br0 = start_row / BLOCK_H;
2363 let br1 = end_row / BLOCK_H;
2364 let bc0 = start_col / BLOCK_W;
2365 let bc1 = end_col / BLOCK_W;
2366 for br in br0..=br1 {
2367 for bc in bc0..=bc1 {
2368 let key = StripeKey {
2369 sheet_id,
2370 stripe_type: StripeType::Block,
2371 index: block_index(br * BLOCK_H, bc * BLOCK_W),
2372 };
2373 if let Some(deps) = self.stripe_to_dependents.get(&key) {
2374 candidates.extend(deps);
2375 }
2376 }
2377 }
2378 }
2379
2380 let mut out: Vec<VertexId> = Vec::new();
2382 for dep_id in candidates {
2383 let Some(ranges) = self.formula_to_range_deps.get(&dep_id) else {
2384 continue;
2385 };
2386 let mut hit = false;
2387 for range in ranges {
2388 let range_sheet_id = match range.sheet {
2389 SharedSheetLocator::Id(id) => id,
2390 _ => sheet_id,
2391 };
2392 if range_sheet_id != sheet_id {
2393 continue;
2394 }
2395 let sr0 = range.start_row.map(|b| b.index).unwrap_or(0);
2396 let er0 = range.end_row.map(|b| b.index).unwrap_or(u32::MAX);
2397 let sc0 = range.start_col.map(|b| b.index).unwrap_or(0);
2398 let ec0 = range.end_col.map(|b| b.index).unwrap_or(u32::MAX);
2399 let overlap =
2400 sr0 <= end_row && er0 >= start_row && sc0 <= end_col && ec0 >= start_col;
2401 if overlap {
2402 hit = true;
2403 break;
2404 }
2405 }
2406 if hit {
2407 out.push(dep_id);
2408 }
2409 }
2410 out
2411 }
2412
2413 pub(crate) fn vertex_exists(&self, vertex_id: VertexId) -> bool {
2415 if vertex_id.0 < FIRST_NORMAL_VERTEX {
2416 return false;
2417 }
2418 let index = (vertex_id.0 - FIRST_NORMAL_VERTEX) as usize;
2419 index < self.store.len()
2420 }
2421
2422 pub(crate) fn get_vertex_kind(&self, vertex_id: VertexId) -> VertexKind {
2424 self.store.kind(vertex_id)
2425 }
2426
2427 pub(crate) fn get_vertex_sheet_id(&self, vertex_id: VertexId) -> SheetId {
2429 self.store.sheet_id(vertex_id)
2430 }
2431
2432 pub fn get_formula_id(&self, vertex_id: VertexId) -> Option<AstNodeId> {
2433 self.vertex_formulas.get(&vertex_id).copied()
2434 }
2435
2436 pub fn get_formula_id_and_volatile(&self, vertex_id: VertexId) -> Option<(AstNodeId, bool)> {
2437 let ast_id = self.get_formula_id(vertex_id)?;
2438 Some((ast_id, self.is_volatile(vertex_id)))
2439 }
2440
2441 pub fn get_formula_node(&self, vertex_id: VertexId) -> Option<&super::arena::AstNodeData> {
2442 let ast_id = self.get_formula_id(vertex_id)?;
2443 self.data_store.get_node(ast_id)
2444 }
2445
2446 pub fn get_formula_node_and_volatile(
2447 &self,
2448 vertex_id: VertexId,
2449 ) -> Option<(&super::arena::AstNodeData, bool)> {
2450 let (ast_id, vol) = self.get_formula_id_and_volatile(vertex_id)?;
2451 let node = self.data_store.get_node(ast_id)?;
2452 Some((node, vol))
2453 }
2454
2455 pub fn get_formula(&self, vertex_id: VertexId) -> Option<ASTNode> {
2459 let ast_id = self.get_formula_id(vertex_id)?;
2460 self.data_store.retrieve_ast(ast_id, &self.sheet_reg)
2461 }
2462
2463 pub fn get_value(&self, vertex_id: VertexId) -> Option<LiteralValue> {
2465 if !self.value_cache_enabled {
2466 match self.store.kind(vertex_id) {
2469 VertexKind::Cell
2470 | VertexKind::FormulaScalar
2471 | VertexKind::FormulaArray
2472 | VertexKind::Empty => {
2473 #[cfg(debug_assertions)]
2474 {
2475 self.graph_value_read_attempts
2476 .fetch_add(1, Ordering::Relaxed);
2477 }
2478 return None;
2479 }
2480 _ => {
2481 }
2483 }
2484 }
2485 self.vertex_values
2486 .get(&vertex_id)
2487 .map(|&value_ref| self.data_store.retrieve_value(value_ref))
2488 }
2489
2490 pub(crate) fn get_cell_ref(&self, vertex_id: VertexId) -> Option<CellRef> {
2492 let packed_coord = self.store.coord(vertex_id);
2493 let sheet_id = self.store.sheet_id(vertex_id);
2494 let coord = Coord::new(packed_coord.row(), packed_coord.col(), true, true);
2495 Some(CellRef::new(sheet_id, coord))
2496 }
2497
2498 pub(crate) fn make_cell_ref_internal(&self, sheet_id: SheetId, row: u32, col: u32) -> CellRef {
2500 let coord = Coord::new(row, col, true, true);
2501 CellRef::new(sheet_id, coord)
2502 }
2503
2504 pub fn make_cell_ref(&self, sheet_name: &str, row: u32, col: u32) -> CellRef {
2506 let sheet_id = self.sheet_reg.get_id(sheet_name).unwrap_or(0);
2507 let coord = Coord::from_excel(row, col, true, true);
2508 CellRef::new(sheet_id, coord)
2509 }
2510
2511 pub(crate) fn is_dirty(&self, vertex_id: VertexId) -> bool {
2513 self.store.is_dirty(vertex_id)
2514 }
2515
2516 pub(crate) fn is_volatile(&self, vertex_id: VertexId) -> bool {
2518 self.store.is_volatile(vertex_id)
2519 }
2520
2521 pub fn get_vertex_id_for_address(&self, addr: &CellRef) -> Option<&VertexId> {
2523 self.cell_to_vertex.get(addr)
2524 }
2525
2526 #[cfg(test)]
2527 pub fn cell_to_vertex(&self) -> &FxHashMap<CellRef, VertexId> {
2528 &self.cell_to_vertex
2529 }
2530
2531 pub(crate) fn get_dependencies(&self, vertex_id: VertexId) -> Vec<VertexId> {
2533 self.edges.out_edges(vertex_id)
2534 }
2535
2536 pub(crate) fn has_self_loop(&self, vertex_id: VertexId) -> bool {
2538 self.edges.out_edges(vertex_id).contains(&vertex_id)
2539 }
2540
2541 pub(crate) fn get_dependents(&self, vertex_id: VertexId) -> Vec<VertexId> {
2544 if self.edges.delta_size() > 0 {
2547 #[cfg(test)]
2548 {
2549 if let Ok(mut g) = self.instr.lock() {
2552 g.dependents_scan_fallback_calls += 1;
2553 g.dependents_scan_vertices_scanned += self.cell_to_vertex.len() as u64;
2554 }
2555 }
2556 let mut dependents = Vec::new();
2558 for (&_addr, &vid) in &self.cell_to_vertex {
2559 let out_edges = self.edges.out_edges(vid);
2560 if out_edges.contains(&vertex_id) {
2561 dependents.push(vid);
2562 }
2563 }
2564 for named in self.named_ranges.values() {
2565 let vid = named.vertex;
2566 let out_edges = self.edges.out_edges(vid);
2567 if out_edges.contains(&vertex_id) {
2568 dependents.push(vid);
2569 }
2570 }
2571 for named in self.sheet_named_ranges.values() {
2572 let vid = named.vertex;
2573 let out_edges = self.edges.out_edges(vid);
2574 if out_edges.contains(&vertex_id) {
2575 dependents.push(vid);
2576 }
2577 }
2578 dependents
2579 } else {
2580 self.edges.in_edges(vertex_id).to_vec()
2582 }
2583 }
2584
2585 #[doc(hidden)]
2589 pub fn snapshot_vertex(&self, id: VertexId) -> crate::engine::VertexSnapshot {
2590 let coord = self.store.coord(id);
2591 let sheet_id = self.store.sheet_id(id);
2592 let kind = self.store.kind(id);
2593 let flags = self.store.flags(id);
2594
2595 let value_ref = self.vertex_values.get(&id).copied();
2597 let formula_ref = self.vertex_formulas.get(&id).copied();
2598
2599 let out_edges = self.get_dependencies(id);
2601
2602 crate::engine::VertexSnapshot {
2603 coord,
2604 sheet_id,
2605 kind,
2606 flags,
2607 value_ref,
2608 formula_ref,
2609 out_edges,
2610 }
2611 }
2612
2613 #[doc(hidden)]
2615 pub fn remove_all_edges(&mut self, id: VertexId) {
2616 self.edges.begin_batch();
2618
2619 self.remove_dependent_edges(id);
2621
2622 self.edges.rebuild();
2625
2626 let dependents = self.get_dependents(id);
2628 if self.pk_order.is_some()
2629 && let Some(mut pk) = self.pk_order.take()
2630 {
2631 for dependent in &dependents {
2632 pk.remove_edge(id, *dependent);
2633 }
2634 self.pk_order = Some(pk);
2635 }
2636 for dependent in dependents {
2637 self.edges.remove_edge(dependent, id);
2638 }
2639
2640 self.edges.end_batch();
2642 }
2643
2644 #[doc(hidden)]
2646 pub fn mark_as_ref_error(&mut self, id: VertexId) {
2647 if !self.value_cache_enabled {
2648 match self.store.kind(id) {
2649 VertexKind::Cell
2650 | VertexKind::FormulaScalar
2651 | VertexKind::FormulaArray
2652 | VertexKind::Empty => {
2653 self.ref_error_vertices.insert(id);
2654 self.vertex_values.remove(&id);
2657 let _ = self.mark_dirty(id);
2658 return;
2659 }
2660 _ => {
2661 }
2663 }
2664 }
2665 let error = LiteralValue::Error(ExcelError::new(ExcelErrorKind::Ref));
2666 let value_ref = self.data_store.store_value(error);
2667 self.vertex_values.insert(id, value_ref);
2668 let _ = self.mark_dirty(id);
2669 }
2670
2671 pub fn is_ref_error(&self, id: VertexId) -> bool {
2673 if !self.value_cache_enabled {
2674 match self.store.kind(id) {
2675 VertexKind::Cell
2676 | VertexKind::FormulaScalar
2677 | VertexKind::FormulaArray
2678 | VertexKind::Empty => {
2679 return self.ref_error_vertices.contains(&id);
2680 }
2681 _ => {
2682 }
2684 }
2685 }
2686 if let Some(value_ref) = self.vertex_values.get(&id) {
2687 let value = self.data_store.retrieve_value(*value_ref);
2688 if let LiteralValue::Error(err) = value {
2689 return err.kind == ExcelErrorKind::Ref;
2690 }
2691 }
2692 false
2693 }
2694
2695 #[doc(hidden)]
2697 pub fn mark_dependents_dirty(&mut self, id: VertexId) {
2698 let dependents = self.get_dependents(id);
2699 for dep_id in dependents {
2700 self.store.set_dirty(dep_id, true);
2701 self.dirty_vertices.insert(dep_id);
2702 }
2703 }
2704
2705 #[doc(hidden)]
2707 pub fn mark_volatile(&mut self, id: VertexId, volatile: bool) {
2708 self.store.set_volatile(id, volatile);
2709 if volatile {
2710 self.volatile_vertices.insert(id);
2711 } else {
2712 self.volatile_vertices.remove(&id);
2713 }
2714 }
2715
2716 #[doc(hidden)]
2718 pub fn set_coord(&mut self, id: VertexId, coord: AbsCoord) {
2719 self.store.set_coord(id, coord);
2720 }
2721
2722 #[doc(hidden)]
2724 pub fn update_edge_coord(&mut self, id: VertexId, coord: AbsCoord) {
2725 self.edges.update_coord(id, coord);
2726 }
2727
2728 #[doc(hidden)]
2730 pub fn mark_deleted(&mut self, id: VertexId, deleted: bool) {
2731 self.store.mark_deleted(id, deleted);
2732 }
2733
2734 #[doc(hidden)]
2736 pub fn set_kind(&mut self, id: VertexId, kind: VertexKind) {
2737 self.store.set_kind(id, kind);
2738 }
2739
2740 #[doc(hidden)]
2742 pub fn set_dirty(&mut self, id: VertexId, dirty: bool) {
2743 self.store.set_dirty(id, dirty);
2744 if dirty {
2745 self.dirty_vertices.insert(id);
2746 } else {
2747 self.dirty_vertices.remove(&id);
2748 }
2749 }
2750
2751 #[cfg(test)]
2753 pub(crate) fn get_kind(&self, id: VertexId) -> VertexKind {
2754 self.store.kind(id)
2755 }
2756
2757 #[cfg(test)]
2759 pub(crate) fn get_flags(&self, id: VertexId) -> u8 {
2760 self.store.flags(id)
2761 }
2762
2763 #[cfg(test)]
2765 pub(crate) fn is_deleted(&self, id: VertexId) -> bool {
2766 self.store.is_deleted(id)
2767 }
2768
2769 #[doc(hidden)]
2771 pub fn rebuild_edges(&mut self) {
2772 self.edges.rebuild();
2773 }
2774
2775 #[doc(hidden)]
2777 pub fn edges_delta_size(&self) -> usize {
2778 self.edges.delta_size()
2779 }
2780
2781 pub fn get_vertex_for_cell(&self, addr: &CellRef) -> Option<VertexId> {
2783 self.cell_to_vertex.get(addr).copied()
2784 }
2785
2786 pub fn get_coord(&self, id: VertexId) -> AbsCoord {
2788 self.store.coord(id)
2789 }
2790
2791 pub fn get_sheet_id(&self, id: VertexId) -> SheetId {
2793 self.store.sheet_id(id)
2794 }
2795
2796 pub fn vertices_in_sheet(&self, sheet_id: SheetId) -> impl Iterator<Item = VertexId> + '_ {
2798 self.store
2799 .all_vertices()
2800 .filter(move |&id| self.vertex_exists(id) && self.store.sheet_id(id) == sheet_id)
2801 }
2802
2803 pub fn vertex_has_formula(&self, id: VertexId) -> bool {
2805 self.vertex_formulas.contains_key(&id)
2806 }
2807
2808 pub fn vertices_with_formulas(&self) -> impl Iterator<Item = VertexId> + '_ {
2810 self.vertex_formulas.keys().copied()
2811 }
2812
2813 pub fn update_vertex_formula(&mut self, id: VertexId, ast: ASTNode) -> Result<(), ExcelError> {
2815 let sheet_id = self.store.sheet_id(id);
2817
2818 let has_ref_marker = ast.get_dependencies().into_iter().any(|r| {
2822 matches!(
2823 r,
2824 ReferenceType::Cell { sheet: Some(s), .. }
2825 | ReferenceType::Range { sheet: Some(s), .. } if s == "#REF"
2826 )
2827 });
2828 if has_ref_marker {
2829 let ast_id = self.data_store.store_ast(&ast, &self.sheet_reg);
2831 self.vertex_formulas.insert(id, ast_id);
2832 self.mark_as_ref_error(id);
2833 self.store.set_kind(id, VertexKind::FormulaScalar);
2834 return Ok(());
2835 }
2836
2837 let (new_dependencies, new_range_dependencies, _, named_dependencies) =
2839 self.extract_dependencies(&ast, sheet_id)?;
2840
2841 self.remove_dependent_edges(id);
2843 self.detach_vertex_from_names(id);
2844
2845 let ast_id = self.data_store.store_ast(&ast, &self.sheet_reg);
2847 self.vertex_formulas.insert(id, ast_id);
2848
2849 self.add_dependent_edges(id, &new_dependencies);
2851 self.add_range_dependent_edges(id, &new_range_dependencies, sheet_id);
2852
2853 if !named_dependencies.is_empty() {
2854 self.attach_vertex_to_names(id, &named_dependencies);
2855 }
2856
2857 self.store.set_kind(id, VertexKind::FormulaScalar);
2859
2860 Ok(())
2861 }
2862
2863 pub fn mark_vertex_dirty(&mut self, vertex_id: VertexId) {
2865 self.store.set_dirty(vertex_id, true);
2866 self.dirty_vertices.insert(vertex_id);
2867 }
2868
2869 pub fn update_cell_mapping(
2871 &mut self,
2872 id: VertexId,
2873 old_addr: Option<CellRef>,
2874 new_addr: CellRef,
2875 ) {
2876 if let Some(old) = old_addr {
2878 self.cell_to_vertex.remove(&old);
2879 }
2880 self.cell_to_vertex.insert(new_addr, id);
2882 }
2883
2884 pub fn remove_cell_mapping(&mut self, addr: &CellRef) {
2886 self.cell_to_vertex.remove(addr);
2887 }
2888
2889 pub fn get_cell_ref_for_vertex(&self, id: VertexId) -> Option<CellRef> {
2891 let coord = self.store.coord(id);
2892 let sheet_id = self.store.sheet_id(id);
2893 let cell_ref = CellRef::new(sheet_id, Coord::new(coord.row(), coord.col(), true, true));
2895 if self.cell_to_vertex.get(&cell_ref) == Some(&id) {
2897 Some(cell_ref)
2898 } else {
2899 None
2900 }
2901 }
2902
2903 }
2905
2906