1use std::collections::{HashMap, VecDeque};
15use std::error::Error;
16use std::fmt;
17
18use formualizer_common::{
19 CellAddress, ExcelError, ExcelErrorKind, LiteralValue, RangeAddress, RangeArea, SheetId,
20};
21use formualizer_parse::parser::{
22 ASTNode, ReferenceType, SpecialItem, TableReference, TableSpecifier,
23};
24use rustc_hash::FxHashMap;
25
26use crate::engine::named_range::NamedDefinition;
27use crate::engine::refs;
28use crate::engine::used_extent::{
29 ExtentPolicy, OpenRangeBounds, ResolvedExtent, resolve_used_extent,
30};
31use crate::engine::{Engine, FormulaPlaneMode, VertexId, VertexKind};
32use crate::formula_plane::producer::{
33 AxisProjection, DirtyProjectionRule, FormulaProducerId, ProducerDirtyDomain, ProjectionResult,
34 compute_dirty_closure,
35};
36use crate::formula_plane::region_index::{BoundedRegionQueryResult, Region, RegionKey};
37use crate::formula_plane::runtime::{FormulaResolution, FormulaSpanRef, PlacementCoord};
38use crate::reference::{CellRef, Coord};
39use crate::traits::EvaluationContext;
40
41const DEFAULT_MAX_LINKS: u32 = 256;
42const DEFAULT_MAX_WORK: u64 = 100_000;
43
44#[cfg(test)]
45#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
46pub(crate) struct FormulaPlaneReferencePathCounts {
47 pub(crate) template: u32,
48 pub(crate) ast_fallback: u32,
49}
50
51#[cfg(test)]
52thread_local! {
53 static FORMULA_PLANE_REFERENCE_PATH_COUNTS:
54 std::cell::Cell<FormulaPlaneReferencePathCounts> = const {
55 std::cell::Cell::new(FormulaPlaneReferencePathCounts {
56 template: 0,
57 ast_fallback: 0,
58 })
59 };
60}
61
62#[cfg(test)]
63pub(crate) fn reset_formula_plane_reference_path_counts() {
64 FORMULA_PLANE_REFERENCE_PATH_COUNTS.with(|counts| counts.set(Default::default()));
65}
66
67#[cfg(test)]
68pub(crate) fn formula_plane_reference_path_counts() -> FormulaPlaneReferencePathCounts {
69 FORMULA_PLANE_REFERENCE_PATH_COUNTS.with(std::cell::Cell::get)
70}
71
72#[cfg(test)]
73fn record_formula_plane_template_path() {
74 FORMULA_PLANE_REFERENCE_PATH_COUNTS.with(|counts| {
75 let mut current = counts.get();
76 current.template += 1;
77 counts.set(current);
78 });
79}
80
81#[cfg(test)]
82fn record_formula_plane_ast_fallback_path() {
83 FORMULA_PLANE_REFERENCE_PATH_COUNTS.with(|counts| {
84 let mut current = counts.get();
85 current.ast_fallback += 1;
86 counts.set(current);
87 });
88}
89
90#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
93#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
94pub struct StateStamp {
95 pub mutation_revision: u64,
96 pub recalc_epoch: u64,
97}
98
99#[cfg_attr(feature = "serde", derive(serde::Serialize))]
100#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
101#[non_exhaustive]
102pub enum Staleness {
103 Current,
104 Dirty,
105 NeverEvaluated,
106}
107
108#[cfg_attr(feature = "serde", derive(serde::Serialize))]
114#[derive(Clone, Debug, Eq, PartialEq, Hash)]
115#[non_exhaustive]
116pub enum SpillRole {
117 Anchor { extent: RangeAddress },
118 Member { anchor: CellAddress },
119}
120
121#[cfg_attr(feature = "serde", derive(serde::Serialize))]
122#[derive(Clone, Debug, PartialEq)]
123#[non_exhaustive]
124pub struct CellSnapshot {
125 pub address: CellAddress,
126 pub formula: Option<String>,
127 pub value: Option<LiteralValue>,
128 pub value_included: bool,
129 pub staleness: Staleness,
130 pub volatile: bool,
131 pub spill: Option<SpillRole>,
134}
135
136#[cfg_attr(feature = "serde", derive(serde::Serialize))]
137#[derive(Clone, Debug, PartialEq)]
138#[non_exhaustive]
139pub struct CellSnapshotReport {
140 pub stamp: StateStamp,
141 pub cell: CellSnapshot,
142}
143
144#[cfg_attr(feature = "serde", derive(serde::Serialize))]
145#[derive(Clone, Debug, PartialEq)]
146#[non_exhaustive]
147pub enum NameResolution {
148 Cell(CellAddress),
149 Range {
150 declared: RangeArea,
151 resolved: Option<RangeAddress>,
152 },
153 Literal(LiteralValue),
154 Formula {
155 formula: String,
156 value: Option<LiteralValue>,
157 },
158 Unresolved,
159}
160
161#[cfg_attr(feature = "serde", derive(serde::Serialize))]
162#[derive(Clone, Debug, PartialEq)]
163#[non_exhaustive]
164pub enum SemanticReference {
165 Cell(CellAddress),
166 Range {
167 declared: RangeArea,
168 resolved: Option<RangeAddress>,
169 cell_count: u64,
170 },
171 Name {
172 name: String,
173 resolution: NameResolution,
174 },
175 Table {
176 name: String,
177 specifier: String,
178 resolved: RangeAddress,
179 },
180 External {
181 raw: String,
182 },
183 Unsupported {
184 text: String,
185 reason: String,
186 },
187}
188
189#[cfg_attr(feature = "serde", derive(serde::Serialize))]
190#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
191#[non_exhaustive]
192pub enum Provenance {
193 Declared,
194 Observed,
195}
196
197#[cfg_attr(feature = "serde", derive(serde::Serialize))]
198#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
199#[non_exhaustive]
200pub enum TraceLinkKind {
201 Formula { provenance: Provenance },
202 SpillAnchor,
203 SpillReader,
204}
205
206#[cfg_attr(feature = "serde", derive(serde::Serialize))]
207#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
208#[non_exhaustive]
209pub enum LinkDisposition {
210 Expanded,
211 Convergent,
212 Cycle,
213 Elided,
214}
215
216#[cfg_attr(feature = "serde", derive(serde::Serialize))]
217#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
218#[non_exhaustive]
219pub enum OmittedCount {
220 Exact(u64),
221 AtLeast(u64),
222}
223
224#[cfg_attr(feature = "serde", derive(serde::Serialize))]
225#[derive(Clone, Debug, Default, Eq, PartialEq)]
226#[non_exhaustive]
227pub struct TruncationReport {
228 pub incomplete: bool,
229 pub omitted: Option<OmittedCount>,
233}
234
235#[cfg_attr(feature = "serde", derive(serde::Serialize))]
236#[derive(Clone, Debug, PartialEq)]
237#[non_exhaustive]
238pub struct Precedent {
239 pub reference: SemanticReference,
240 pub provenance: Provenance,
241}
242
243#[cfg_attr(feature = "serde", derive(serde::Serialize))]
244#[derive(Clone, Debug, PartialEq)]
245#[non_exhaustive]
246pub struct PrecedentReport {
247 pub stamp: StateStamp,
248 pub cell: CellAddress,
249 pub precedents: Vec<Precedent>,
250 pub truncation: TruncationReport,
251}
252
253#[cfg_attr(feature = "serde", derive(serde::Serialize))]
254#[derive(Clone, Debug, Eq, PartialEq)]
255#[non_exhaustive]
256pub struct Dependent {
257 pub cell: CellAddress,
258 pub via: Vec<CellAddress>,
262}
263
264#[cfg_attr(feature = "serde", derive(serde::Serialize))]
265#[derive(Clone, Debug, Eq, PartialEq)]
266#[non_exhaustive]
267pub struct DependentsReport {
268 pub stamp: StateStamp,
269 pub cell: CellAddress,
270 pub dependents: Vec<Dependent>,
271 pub truncation: TruncationReport,
272}
273
274#[cfg_attr(feature = "serde", derive(serde::Serialize))]
275#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
276pub struct TraceNodeId(pub u32);
277
278#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
279#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
280#[non_exhaustive]
281pub enum TraceDirection {
282 Precedents,
283 Dependents,
284}
285
286#[cfg_attr(feature = "serde", derive(serde::Serialize))]
287#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
288#[non_exhaustive]
289pub struct TraceLinkTarget {
290 pub node: TraceNodeId,
291 pub disposition: LinkDisposition,
292}
293
294#[cfg_attr(feature = "serde", derive(serde::Serialize))]
295#[derive(Clone, Debug, PartialEq)]
296#[non_exhaustive]
297pub struct TraceLink {
298 pub reference: SemanticReference,
299 pub kind: TraceLinkKind,
300 pub targets: Vec<TraceLinkTarget>,
301 pub omitted: Option<OmittedCount>,
302}
303
304#[cfg_attr(feature = "serde", derive(serde::Serialize))]
305#[derive(Clone, Debug, PartialEq)]
306#[non_exhaustive]
307pub struct TraceNode {
308 pub id: TraceNodeId,
309 pub cell: CellSnapshot,
310 pub links: Vec<TraceLink>,
311}
312
313#[cfg_attr(feature = "serde", derive(serde::Serialize))]
314#[derive(Clone, Debug, PartialEq)]
315#[non_exhaustive]
316pub struct TraceGraph {
317 pub stamp: StateStamp,
318 pub direction: TraceDirection,
319 pub roots: Vec<TraceNodeId>,
322 pub nodes: Vec<TraceNode>,
323 pub truncation: TruncationReport,
324}
325
326#[cfg_attr(feature = "serde", derive(serde::Serialize))]
327#[derive(Clone, Debug, PartialEq)]
328#[non_exhaustive]
329pub struct RangePage {
330 pub stamp: StateStamp,
331 pub declared: RangeArea,
332 pub resolved: Option<RangeAddress>,
333 pub total: u64,
334 pub offset: u64,
337 pub items: Vec<CellSnapshot>,
338 pub next_offset: Option<u64>,
339}
340
341#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
347#[derive(Clone, Copy, Debug, Eq, PartialEq)]
348pub struct SnapshotOptions {
349 pub include_values: bool,
350}
351
352impl Default for SnapshotOptions {
353 fn default() -> Self {
354 Self {
355 include_values: true,
356 }
357 }
358}
359
360impl SnapshotOptions {
361 pub fn with_include_values(mut self, include_values: bool) -> Self {
362 self.include_values = include_values;
363 self
364 }
365}
366
367#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
373#[derive(Clone, Copy, Debug, Eq, PartialEq)]
374pub struct PrecedentOptions {
375 pub max_links: u32,
376 pub max_work: u64,
377}
378
379impl Default for PrecedentOptions {
380 fn default() -> Self {
381 Self {
382 max_links: DEFAULT_MAX_LINKS,
383 max_work: DEFAULT_MAX_WORK,
384 }
385 }
386}
387
388impl PrecedentOptions {
389 pub fn with_max_links(mut self, max_links: u32) -> Self {
390 self.max_links = max_links;
391 self
392 }
393
394 pub fn with_max_work(mut self, max_work: u64) -> Self {
395 self.max_work = max_work;
396 self
397 }
398}
399
400#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
406#[derive(Clone, Copy, Debug, Eq, PartialEq)]
407pub struct DependentsOptions {
408 pub max_results: u32,
413 pub max_work: u64,
414}
415
416impl Default for DependentsOptions {
417 fn default() -> Self {
418 Self {
419 max_results: 256,
420 max_work: DEFAULT_MAX_WORK,
421 }
422 }
423}
424
425impl DependentsOptions {
426 pub fn with_max_results(mut self, max_results: u32) -> Self {
427 self.max_results = max_results;
428 self
429 }
430
431 pub fn with_max_work(mut self, max_work: u64) -> Self {
432 self.max_work = max_work;
433 self
434 }
435}
436
437#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
443#[derive(Clone, Copy, Debug, Eq, PartialEq)]
444pub struct TraceOptions {
445 pub direction: TraceDirection,
446 pub max_depth: u32,
449 pub max_nodes: u32,
452 pub max_links: u32,
453 pub max_work: u64,
454 pub range_member_budget: u32,
455 pub include_values: bool,
456}
457
458impl Default for TraceOptions {
459 fn default() -> Self {
460 Self {
461 direction: TraceDirection::Precedents,
462 max_depth: 6,
463 max_nodes: 512,
464 max_links: 1_024,
465 max_work: DEFAULT_MAX_WORK,
466 range_member_budget: 256,
467 include_values: true,
468 }
469 }
470}
471
472impl TraceOptions {
473 pub fn with_direction(mut self, direction: TraceDirection) -> Self {
474 self.direction = direction;
475 self
476 }
477
478 pub fn with_max_depth(mut self, max_depth: u32) -> Self {
479 self.max_depth = max_depth;
480 self
481 }
482
483 pub fn with_max_nodes(mut self, max_nodes: u32) -> Self {
484 self.max_nodes = max_nodes;
485 self
486 }
487
488 pub fn with_max_links(mut self, max_links: u32) -> Self {
489 self.max_links = max_links;
490 self
491 }
492
493 pub fn with_max_work(mut self, max_work: u64) -> Self {
494 self.max_work = max_work;
495 self
496 }
497
498 pub fn with_range_member_budget(mut self, range_member_budget: u32) -> Self {
499 self.range_member_budget = range_member_budget;
500 self
501 }
502
503 pub fn with_include_values(mut self, include_values: bool) -> Self {
504 self.include_values = include_values;
505 self
506 }
507}
508
509#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
515#[derive(Clone, Copy, Debug, Eq, PartialEq)]
516pub struct RangePageOptions {
517 pub offset: u64,
518 pub limit: u32,
519 pub include_values: bool,
520 pub expected_stamp: Option<StateStamp>,
521}
522
523impl Default for RangePageOptions {
524 fn default() -> Self {
525 Self {
526 offset: 0,
527 limit: 100,
528 include_values: true,
529 expected_stamp: None,
530 }
531 }
532}
533
534impl RangePageOptions {
535 pub fn with_offset(mut self, offset: u64) -> Self {
536 self.offset = offset;
537 self
538 }
539
540 pub fn with_limit(mut self, limit: u32) -> Self {
541 self.limit = limit;
542 self
543 }
544
545 pub fn with_include_values(mut self, include_values: bool) -> Self {
546 self.include_values = include_values;
547 self
548 }
549
550 pub fn with_expected_stamp(mut self, expected_stamp: StateStamp) -> Self {
551 self.expected_stamp = Some(expected_stamp);
552 self
553 }
554}
555
556#[cfg_attr(feature = "serde", derive(serde::Serialize))]
557#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
558#[non_exhaustive]
559pub enum InspectionUnavailableReason {
560 DeferredDependencyGraph,
561}
562
563#[cfg_attr(feature = "serde", derive(serde::Serialize))]
564#[derive(Clone, Debug, Eq, PartialEq)]
565#[non_exhaustive]
566pub enum InspectError {
567 SheetNotFound {
568 sheet: String,
569 },
570 InvalidAddress {
571 message: String,
572 },
573 InvalidOptions {
574 message: String,
575 },
576 DependencyStateUnavailable {
577 reason: InspectionUnavailableReason,
578 },
579 RevisionMismatch {
580 expected: StateStamp,
581 actual: StateStamp,
582 },
583 ResourceExhausted {
585 resource: &'static str,
586 },
587}
588
589impl fmt::Display for InspectError {
590 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
591 match self {
592 Self::SheetNotFound { sheet } => write!(f, "sheet not found: {sheet}"),
593 Self::InvalidAddress { message } => write!(f, "invalid address: {message}"),
594 Self::InvalidOptions { message } => write!(f, "invalid inspection options: {message}"),
595 Self::DependencyStateUnavailable { reason } => {
596 write!(f, "dependency state unavailable: {reason:?}")
597 }
598 Self::RevisionMismatch { expected, actual } => write!(
599 f,
600 "inspection revision mismatch: expected {expected:?}, actual {actual:?}"
601 ),
602 Self::ResourceExhausted { resource } => {
603 write!(f, "inspection resource exhausted: {resource}")
604 }
605 }
606 }
607}
608
609impl Error for InspectError {}
610
611#[cfg_attr(feature = "serde", derive(serde::Serialize))]
612#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
613pub(crate) struct CellKey {
614 sheet_id: SheetId,
615 row0: u32,
616 col0: u32,
617}
618
619#[derive(Clone, Debug)]
620pub(crate) struct FormulaView {
621 ast: ASTNode,
622 volatile: bool,
623 dirty: bool,
624}
625
626#[derive(Clone, Debug)]
627pub(crate) enum InternalSpillRole {
628 Anchor { extent: RangeAddress },
629 Member { anchor: CellKey },
630}
631
632#[derive(Clone, Copy, Debug, Eq, PartialEq)]
633pub(crate) enum QueryCompleteness {
634 Complete,
635 Incomplete,
636}
637
638#[derive(Clone, Copy, Debug)]
639pub(crate) struct WorkBudget {
640 remaining: u64,
641}
642
643impl WorkBudget {
644 fn new(limit: u64) -> Self {
645 Self { remaining: limit }
646 }
647
648 fn charge(&mut self) -> bool {
649 if self.remaining == 0 {
650 false
651 } else {
652 self.remaining -= 1;
653 true
654 }
655 }
656}
657
658pub(crate) trait ReferenceVisitor {
659 fn visit(&mut self, reference: refs::SemanticReference<'_>) -> bool;
661}
662
663pub(crate) trait DependentVisitor {
664 fn visit(&mut self, dependent: CellKey) -> bool;
666}
667
668pub(crate) trait InspectSource {
672 fn formula_at(&self, cell: CellKey) -> Result<Option<FormulaView>, InspectError>;
673 fn visit_declared_references(
674 &self,
675 cell: CellKey,
676 visitor: &mut dyn ReferenceVisitor,
677 ) -> Result<(), InspectError>;
678 fn visit_dependents_covering(
679 &self,
680 cell: CellKey,
681 budget: &mut WorkBudget,
682 visitor: &mut dyn DependentVisitor,
683 ) -> Result<QueryCompleteness, InspectError>;
684 fn spill_role(&self, cell: CellKey) -> Option<InternalSpillRole>;
685}
686
687struct LegacyInspectSource<'a, R> {
688 engine: &'a Engine<R>,
689}
690
691impl<R: EvaluationContext> LegacyInspectSource<'_, R> {
692 fn cell_ref(&self, key: CellKey) -> CellRef {
693 CellRef::new(key.sheet_id, Coord::new(key.row0, key.col0, true, true))
694 }
695}
696
697fn visit_formula_ast_references(
698 ast: &ASTNode,
699 visitor: &mut dyn ReferenceVisitor,
700) -> Result<(), InspectError> {
701 struct Context<'a> {
702 visitor: &'a mut dyn ReferenceVisitor,
703 stopped: bool,
704 }
705 fn local_bindings(_: &Context<'_>, name: &str, _: usize) -> refs::LocalBindingStyle {
706 match name
707 .rsplit('.')
708 .next()
709 .unwrap_or(name)
710 .to_ascii_uppercase()
711 .as_str()
712 {
713 "LET" => refs::LocalBindingStyle::LocalBindingPairs,
714 "LAMBDA" => refs::LocalBindingStyle::LambdaParameters,
715 _ => refs::LocalBindingStyle::None,
716 }
717 }
718 fn consume(
719 context: &mut Context<'_>,
720 reference: refs::SemanticReference<'_>,
721 ) -> Result<(), ExcelError> {
722 if context.visitor.visit(reference) {
723 Ok(())
724 } else {
725 context.stopped = true;
726 Err(ExcelError::new(ExcelErrorKind::NImpl)
727 .with_message("inspection visitor requested stop"))
728 }
729 }
730
731 let mut context = Context {
732 visitor,
733 stopped: false,
734 };
735 let result = refs::visit_tree_references(ast, &mut context, local_bindings, consume);
736 if context.stopped {
737 Ok(())
738 } else {
739 result.map_err(|error| InspectError::InvalidAddress {
740 message: error.to_string(),
741 })
742 }
743}
744
745impl<R: EvaluationContext> InspectSource for LegacyInspectSource<'_, R> {
746 fn formula_at(&self, cell: CellKey) -> Result<Option<FormulaView>, InspectError> {
747 let sheet = self.engine.graph.sheet_name(cell.sheet_id);
748 let row = cell.row0 + 1;
749 let col = cell.col0 + 1;
750 if let Some(text) = self.engine.get_staged_formula_text(sheet, row, col) {
751 let ast =
752 formualizer_parse::parse(&text).map_err(|error| InspectError::InvalidAddress {
753 message: format!("staged formula at {sheet}!R{row}C{col} is invalid: {error}"),
754 })?;
755 let volatile = self.engine.graph.fp8_parity_is_ast_volatile(&ast);
756 return Ok(Some(FormulaView {
757 ast,
758 volatile,
759 dirty: true,
760 }));
761 }
762
763 let cell_ref = self.cell_ref(cell);
764 let Some(vertex) = self.engine.graph.get_vertex_for_cell(&cell_ref) else {
765 return Ok(None);
766 };
767 let Some(ast) = self.engine.graph.get_formula(vertex) else {
768 return Ok(None);
769 };
770 Ok(Some(FormulaView {
771 ast,
772 volatile: self.engine.graph.is_volatile(vertex),
773 dirty: self.engine.graph.is_dirty(vertex),
774 }))
775 }
776
777 fn visit_declared_references(
778 &self,
779 cell: CellKey,
780 visitor: &mut dyn ReferenceVisitor,
781 ) -> Result<(), InspectError> {
782 let Some(formula) = self.formula_at(cell)? else {
783 return Ok(());
784 };
785
786 visit_formula_ast_references(&formula.ast, visitor)
787 }
788
789 fn visit_dependents_covering(
790 &self,
791 cell: CellKey,
792 budget: &mut WorkBudget,
793 visitor: &mut dyn DependentVisitor,
794 ) -> Result<QueryCompleteness, InspectError> {
795 let complete = self.engine.graph.visit_range_dependents_covering_bounded(
796 cell.sheet_id,
797 cell.row0,
798 cell.col0,
799 &mut budget.remaining,
800 &mut |vertex| {
801 self.engine
802 .graph
803 .get_cell_ref(vertex)
804 .is_none_or(|cell_ref| {
805 visitor.visit(CellKey {
806 sheet_id: cell_ref.sheet_id,
807 row0: cell_ref.coord.row(),
808 col0: cell_ref.coord.col(),
809 })
810 })
811 },
812 );
813 Ok(if complete {
814 QueryCompleteness::Complete
815 } else {
816 QueryCompleteness::Incomplete
817 })
818 }
819
820 fn spill_role(&self, cell: CellKey) -> Option<InternalSpillRole> {
821 let cell_ref = self.cell_ref(cell);
822 if let Some(vertex) = self.engine.graph.get_vertex_for_cell(&cell_ref)
823 && let Some(cells) = self.engine.graph.spill_cells_for_anchor(vertex)
824 {
825 let mut bounds: Option<(u32, u32, u32, u32)> = None;
826 for member in cells {
827 bounds = Some(match bounds {
828 None => (
829 member.coord.row(),
830 member.coord.col(),
831 member.coord.row(),
832 member.coord.col(),
833 ),
834 Some((sr, sc, er, ec)) => (
835 sr.min(member.coord.row()),
836 sc.min(member.coord.col()),
837 er.max(member.coord.row()),
838 ec.max(member.coord.col()),
839 ),
840 });
841 }
842 if let Some((sr, sc, er, ec)) = bounds {
843 let sheet = self.engine.graph.sheet_name(cell.sheet_id).to_string();
844 return Some(InternalSpillRole::Anchor {
845 extent: RangeAddress {
846 sheet,
847 start_row: sr + 1,
848 start_col: sc + 1,
849 end_row: er + 1,
850 end_col: ec + 1,
851 },
852 });
853 }
854 }
855 let anchor = self.engine.graph.spill_registry_anchor_for_cell(cell_ref)?;
856 let anchor_ref = self.engine.graph.get_cell_ref(anchor)?;
857 Some(InternalSpillRole::Member {
858 anchor: CellKey {
859 sheet_id: anchor_ref.sheet_id,
860 row0: anchor_ref.coord.row(),
861 col0: anchor_ref.coord.col(),
862 },
863 })
864 }
865}
866
867struct FormulaPlaneInspectSource<'a, R> {
872 engine: &'a Engine<R>,
873 legacy: LegacyInspectSource<'a, R>,
874}
875
876impl<'a, R: EvaluationContext> FormulaPlaneInspectSource<'a, R> {
877 fn new(engine: &'a Engine<R>) -> Self {
878 Self {
879 engine,
880 legacy: LegacyInspectSource { engine },
881 }
882 }
883
884 fn cell_ref(&self, key: CellKey) -> CellRef {
885 CellRef::new(key.sheet_id, Coord::new(key.row0, key.col0, true, true))
886 }
887
888 fn span_placement(&self, key: CellKey) -> Option<(FormulaSpanRef, PlacementCoord)> {
889 if self.engine.config.formula_plane_mode != FormulaPlaneMode::AuthoritativeExperimental {
893 return None;
894 }
895 let placement = PlacementCoord::new(key.sheet_id, key.row0, key.col0);
896 let legacy_vertex = self.engine.graph.get_vertex_for_cell(&self.cell_ref(key));
897 let handle = self
898 .engine
899 .graph
900 .formula_authority()
901 .plane
902 .resolve_formula_at(placement, legacy_vertex);
903 match handle.resolution {
904 FormulaResolution::SpanPlacement {
905 span, placement, ..
906 } => Some((span, placement)),
907 FormulaResolution::StagedFormula { .. }
908 | FormulaResolution::Overlay(_)
909 | FormulaResolution::LegacyVertex(_)
910 | FormulaResolution::Empty
911 | FormulaResolution::Stale => None,
912 }
913 }
914
915 fn dirty_domain_contains(dirty: &ProducerDirtyDomain, placement: PlacementCoord) -> bool {
916 match dirty {
917 ProducerDirtyDomain::Whole => true,
918 ProducerDirtyDomain::Cells(cells) => cells.contains(&RegionKey::from(placement)),
919 ProducerDirtyDomain::Regions(regions) => {
920 let key = RegionKey::from(placement);
921 regions.iter().any(|region| region.contains_key(key))
922 }
923 }
924 }
925
926 fn span_placement_is_dirty(&self, span_ref: FormulaSpanRef, placement: PlacementCoord) -> bool {
927 if self
928 .engine
929 .graph
930 .pending_formula_dirty_whole_spans()
931 .any(|pending| pending == span_ref)
932 {
933 return true;
934 }
935 if self
936 .engine
937 .graph
938 .pending_formula_dirty_span_regions()
939 .any(|(pending, region)| {
940 pending == span_ref && region.contains_key(RegionKey::from(placement))
941 })
942 {
943 return true;
944 }
945
946 let changed = self
947 .engine
948 .graph
949 .pending_formula_dirty_regions()
950 .collect::<Vec<_>>();
951 if changed.is_empty() {
952 return false;
953 }
954 let authority = self.engine.graph.formula_authority();
955 let closure = compute_dirty_closure(&authority.consumer_reads, changed, |producer| {
956 authority.producer_results.producer_result_region(producer)
957 });
958 if closure.incomplete {
959 return true;
960 }
961 let producer = FormulaProducerId::Span(span_ref.id);
962 closure.work.iter().any(|work| {
963 work.producer == producer && Self::dirty_domain_contains(&work.dirty, placement)
964 }) || closure
965 .fallbacks
966 .iter()
967 .any(|fallback| fallback.consumer == producer)
968 }
969
970 fn instantiate_axis(projection: AxisProjection, placement: u32) -> Option<(u32, bool)> {
971 match projection {
972 AxisProjection::Relative { offset } => {
973 let value = i64::from(placement).checked_add(offset)?;
974 let value = u32::try_from(value).ok()?.checked_add(1)?;
975 Some((value, false))
976 }
977 AxisProjection::Absolute { index } => index.checked_add(1).map(|value| (value, true)),
978 }
979 }
980
981 fn instantiated_span_references(
982 &self,
983 span_ref: FormulaSpanRef,
984 placement: PlacementCoord,
985 ) -> Option<Vec<ReferenceType>> {
986 let authority = self.engine.graph.formula_authority();
987 let span = authority.plane.spans.get(span_ref)?;
988 let summary = authority
989 .plane
990 .span_read_summaries
991 .get(span.read_summary_id?)?;
992 let mut references = Vec::with_capacity(summary.dependencies.len());
993 for dependency in &summary.dependencies {
994 let sheet = Some(
995 self.engine
996 .graph
997 .sheet_name(dependency.read_region.sheet_id())
998 .to_string(),
999 );
1000 let reference = match dependency.projection {
1001 DirtyProjectionRule::AffineCell { row, col } => {
1002 let (row, row_abs) = Self::instantiate_axis(row, placement.row)?;
1003 let (col, col_abs) = Self::instantiate_axis(col, placement.col)?;
1004 ReferenceType::Cell {
1005 sheet,
1006 row,
1007 col,
1008 row_abs,
1009 col_abs,
1010 }
1011 }
1012 DirtyProjectionRule::AffineRange {
1013 row_start,
1014 row_end,
1015 col_start,
1016 col_end,
1017 } => {
1018 let (start_row, start_row_abs) =
1019 Self::instantiate_axis(row_start, placement.row)?;
1020 let (end_row, end_row_abs) = Self::instantiate_axis(row_end, placement.row)?;
1021 let (start_col, start_col_abs) =
1022 Self::instantiate_axis(col_start, placement.col)?;
1023 let (end_col, end_col_abs) = Self::instantiate_axis(col_end, placement.col)?;
1024 ReferenceType::Range {
1025 sheet,
1026 start_row: Some(start_row),
1027 start_col: Some(start_col),
1028 end_row: Some(end_row),
1029 end_col: Some(end_col),
1030 start_row_abs,
1031 start_col_abs,
1032 end_row_abs,
1033 end_col_abs,
1034 }
1035 }
1036 DirtyProjectionRule::WholeColumnRange { col_start, col_end } => {
1037 let (start_col, start_col_abs) =
1038 Self::instantiate_axis(col_start, placement.col)?;
1039 let (end_col, end_col_abs) = Self::instantiate_axis(col_end, placement.col)?;
1040 ReferenceType::Range {
1041 sheet,
1042 start_row: None,
1043 start_col: Some(start_col),
1044 end_row: None,
1045 end_col: Some(end_col),
1046 start_row_abs: true,
1047 start_col_abs,
1048 end_row_abs: true,
1049 end_col_abs,
1050 }
1051 }
1052 DirtyProjectionRule::WholeResult => return None,
1055 };
1056 references.push(reference);
1057 }
1058 #[cfg(test)]
1059 record_formula_plane_template_path();
1060 Some(references)
1061 }
1062
1063 fn visit_formula_plane_dependents(
1064 &self,
1065 cell: CellKey,
1066 budget: &mut WorkBudget,
1067 visitor: &mut dyn DependentVisitor,
1068 ) -> QueryCompleteness {
1069 if self.engine.config.formula_plane_mode != FormulaPlaneMode::AuthoritativeExperimental {
1070 return QueryCompleteness::Complete;
1071 }
1072 let authority = self.engine.graph.formula_authority();
1073 let candidate_limit = usize::try_from(budget.remaining).unwrap_or(usize::MAX);
1074 let query = authority.consumer_reads.query_changed_region_bounded(
1075 Region::point(cell.sheet_id, cell.row0, cell.col0),
1076 candidate_limit,
1077 );
1078 let mut query = match query {
1079 BoundedRegionQueryResult::Complete(query) => query,
1080 BoundedRegionQueryResult::Incomplete {
1081 observed_candidates,
1082 } => {
1083 budget.remaining = budget.remaining.saturating_sub(observed_candidates as u64);
1084 return QueryCompleteness::Incomplete;
1085 }
1086 };
1087 budget.remaining = budget
1088 .remaining
1089 .saturating_sub(query.stats.candidate_count as u64);
1090 query.matches.sort_by(|left, right| {
1091 let key = |producer| {
1092 let FormulaProducerId::Span(span_id) = producer else {
1093 return None;
1094 };
1095 let span_ref = authority.plane.spans.current_ref(span_id)?;
1096 let span = authority.plane.spans.get(span_ref)?;
1097 let placement = span.domain.iter().next()?;
1098 Some((
1099 self.engine.graph.sheet_name(placement.sheet_id),
1100 placement.row,
1101 placement.col,
1102 ))
1103 };
1104 key(left.value.consumer).cmp(&key(right.value.consumer))
1105 });
1106
1107 for matched in query.matches {
1108 let FormulaProducerId::Span(span_id) = matched.value.consumer else {
1109 continue;
1110 };
1111 let Some(span_ref) = authority.plane.spans.current_ref(span_id) else {
1112 continue;
1113 };
1114 let Some(span) = authority.plane.spans.get(span_ref) else {
1115 continue;
1116 };
1117 let whole = ProducerDirtyDomain::Whole;
1118 let dirty = match &matched.value.dirty {
1119 ProjectionResult::Exact(dirty) | ProjectionResult::Conservative { dirty, .. } => {
1120 dirty
1121 }
1122 ProjectionResult::NoIntersection => continue,
1123 ProjectionResult::Unsupported(_) => &whole,
1124 };
1125 for placement in span.domain.iter() {
1126 if !Self::dirty_domain_contains(dirty, placement) {
1127 continue;
1128 }
1129 if !budget.charge()
1130 || !visitor.visit(CellKey {
1131 sheet_id: placement.sheet_id,
1132 row0: placement.row,
1133 col0: placement.col,
1134 })
1135 {
1136 return QueryCompleteness::Incomplete;
1137 }
1138 }
1139 }
1140 QueryCompleteness::Complete
1141 }
1142}
1143
1144impl<R: EvaluationContext> InspectSource for FormulaPlaneInspectSource<'_, R> {
1145 fn formula_at(&self, cell: CellKey) -> Result<Option<FormulaView>, InspectError> {
1146 let Some((span_ref, placement)) = self.span_placement(cell) else {
1147 return self.legacy.formula_at(cell);
1148 };
1149 let sheet = self.engine.graph.sheet_name(cell.sheet_id);
1150 let ast = self
1153 .engine
1154 .get_cell(sheet, cell.row0 + 1, cell.col0 + 1)
1155 .and_then(|(ast, _)| ast);
1156 Ok(ast.map(|ast| FormulaView {
1157 ast,
1158 volatile: false,
1161 dirty: self.span_placement_is_dirty(span_ref, placement),
1162 }))
1163 }
1164
1165 fn visit_declared_references(
1166 &self,
1167 cell: CellKey,
1168 visitor: &mut dyn ReferenceVisitor,
1169 ) -> Result<(), InspectError> {
1170 let Some((span_ref, placement)) = self.span_placement(cell) else {
1171 return self.legacy.visit_declared_references(cell, visitor);
1172 };
1173 let Some(references) = self.instantiated_span_references(span_ref, placement) else {
1174 #[cfg(test)]
1175 record_formula_plane_ast_fallback_path();
1176 let Some(formula) = self.formula_at(cell)? else {
1179 return Ok(());
1180 };
1181 return visit_formula_ast_references(&formula.ast, visitor);
1182 };
1183 for reference in &references {
1184 if !visitor.visit(refs::classify(reference)) {
1185 break;
1186 }
1187 }
1188 Ok(())
1189 }
1190
1191 fn visit_dependents_covering(
1192 &self,
1193 cell: CellKey,
1194 budget: &mut WorkBudget,
1195 visitor: &mut dyn DependentVisitor,
1196 ) -> Result<QueryCompleteness, InspectError> {
1197 if self
1198 .legacy
1199 .visit_dependents_covering(cell, budget, visitor)?
1200 == QueryCompleteness::Incomplete
1201 {
1202 return Ok(QueryCompleteness::Incomplete);
1203 }
1204 Ok(self.visit_formula_plane_dependents(cell, budget, visitor))
1205 }
1206
1207 fn spill_role(&self, cell: CellKey) -> Option<InternalSpillRole> {
1208 self.legacy.spill_role(cell)
1211 }
1212}
1213
1214fn merge_omitted(target: &mut Option<OmittedCount>, addition: OmittedCount) {
1215 *target = Some(match (target.take(), addition) {
1216 (None, value) => value,
1217 (Some(OmittedCount::Exact(a)), OmittedCount::Exact(b)) => {
1218 OmittedCount::Exact(a.saturating_add(b))
1219 }
1220 (Some(OmittedCount::Exact(a)), OmittedCount::AtLeast(b))
1221 | (Some(OmittedCount::AtLeast(a)), OmittedCount::Exact(b))
1222 | (Some(OmittedCount::AtLeast(a)), OmittedCount::AtLeast(b)) => {
1223 OmittedCount::AtLeast(a.saturating_add(b))
1224 }
1225 });
1226}
1227
1228fn address_cmp(left: &CellAddress, right: &CellAddress) -> std::cmp::Ordering {
1229 left.sheet
1230 .cmp(&right.sheet)
1231 .then_with(|| left.row.cmp(&right.row))
1232 .then_with(|| left.column.cmp(&right.column))
1233}
1234
1235impl<R: EvaluationContext> Engine<R> {
1236 fn inspect_stamp(&self) -> StateStamp {
1237 StateStamp {
1238 mutation_revision: self.inspection_mutation_revision(),
1239 recalc_epoch: self.recalc_epoch,
1240 }
1241 }
1242
1243 fn inspect_source(&self) -> FormulaPlaneInspectSource<'_, R> {
1244 FormulaPlaneInspectSource::new(self)
1245 }
1246
1247 fn canonical_cell(
1248 &self,
1249 address: &CellAddress,
1250 ) -> Result<(CellKey, CellAddress), InspectError> {
1251 CellAddress::new(address.sheet.clone(), address.row, address.column).map_err(|error| {
1252 InspectError::InvalidAddress {
1253 message: error.to_string(),
1254 }
1255 })?;
1256 let Some(sheet_id) = self.graph.sheet_id(&address.sheet) else {
1257 return Err(InspectError::SheetNotFound {
1258 sheet: address.sheet.clone(),
1259 });
1260 };
1261 let canonical = CellAddress {
1262 sheet: self.graph.sheet_name(sheet_id).to_string(),
1263 row: address.row,
1264 column: address.column,
1265 };
1266 Ok((
1267 CellKey {
1268 sheet_id,
1269 row0: address.row - 1,
1270 col0: address.column - 1,
1271 },
1272 canonical,
1273 ))
1274 }
1275
1276 fn canonical_area(&self, area: &RangeArea) -> Result<(SheetId, RangeArea), InspectError> {
1277 RangeArea::new(
1278 area.sheet.clone(),
1279 area.start_row,
1280 area.start_column,
1281 area.end_row,
1282 area.end_column,
1283 )
1284 .map_err(|error| InspectError::InvalidAddress {
1285 message: error.to_string(),
1286 })?;
1287 let Some(sheet_id) = self.graph.sheet_id(&area.sheet) else {
1288 return Err(InspectError::SheetNotFound {
1289 sheet: area.sheet.clone(),
1290 });
1291 };
1292 Ok((
1293 sheet_id,
1294 RangeArea {
1295 sheet: self.graph.sheet_name(sheet_id).to_string(),
1296 start_row: area.start_row,
1297 start_column: area.start_column,
1298 end_row: area.end_row,
1299 end_column: area.end_column,
1300 },
1301 ))
1302 }
1303
1304 fn address_for_key(&self, key: CellKey) -> CellAddress {
1305 CellAddress {
1306 sheet: self.graph.sheet_name(key.sheet_id).to_string(),
1307 row: key.row0 + 1,
1308 column: key.col0 + 1,
1309 }
1310 }
1311
1312 fn key_for_vertex(&self, vertex: VertexId) -> Option<CellKey> {
1313 if !matches!(
1314 self.graph.get_vertex_kind(vertex),
1315 VertexKind::FormulaScalar | VertexKind::FormulaArray
1316 ) {
1317 return None;
1318 }
1319 let cell = self.graph.get_cell_ref(vertex)?;
1320 Some(CellKey {
1321 sheet_id: cell.sheet_id,
1322 row0: cell.coord.row(),
1323 col0: cell.coord.col(),
1324 })
1325 }
1326
1327 fn resolve_semantic_area(&self, area: &RangeArea) -> Option<RangeAddress> {
1328 let extent = resolve_used_extent(
1329 OpenRangeBounds {
1330 start_row: area.start_row,
1331 start_column: area.start_column,
1332 end_row: area.end_row,
1333 end_column: area.end_column,
1334 },
1335 ExtentPolicy::Semantic,
1336 |first, last| self.semantic_used_rows_for_columns(&area.sheet, first, last),
1337 |first, last| self.semantic_used_cols_for_rows(&area.sheet, first, last),
1338 )?;
1339 Some(Self::range_from_extent(&area.sheet, extent))
1340 }
1341
1342 fn range_from_extent(sheet: &str, extent: ResolvedExtent) -> RangeAddress {
1343 RangeAddress {
1344 sheet: sheet.to_string(),
1345 start_row: extent.start_row,
1346 start_col: extent.start_column,
1347 end_row: extent.end_row,
1348 end_col: extent.end_column,
1349 }
1350 }
1351
1352 fn snapshot_for_key(
1353 &self,
1354 key: CellKey,
1355 include_value: bool,
1356 ) -> Result<CellSnapshot, InspectError> {
1357 let source = self.inspect_source();
1358 let formula = source.formula_at(key)?;
1359 let address = self.address_for_key(key);
1360 let cached_value = self.read_cell_value(&address.sheet, address.row, address.column);
1361 let (canonical_formula, volatile, staleness) = match formula {
1362 Some(view) => {
1363 let staleness = if cached_value.is_none() {
1368 Staleness::NeverEvaluated
1369 } else if view.dirty {
1370 Staleness::Dirty
1371 } else {
1372 Staleness::Current
1373 };
1374 (
1375 Some(formualizer_parse::pretty::canonical_formula(&view.ast)),
1376 view.volatile,
1377 staleness,
1378 )
1379 }
1380 None => (None, false, Staleness::Current),
1381 };
1382 let spill = source.spill_role(key).map(|role| match role {
1383 InternalSpillRole::Anchor { extent } => SpillRole::Anchor { extent },
1384 InternalSpillRole::Member { anchor } => SpillRole::Member {
1385 anchor: self.address_for_key(anchor),
1386 },
1387 });
1388 Ok(CellSnapshot {
1389 address,
1390 formula: canonical_formula,
1391 value: include_value.then_some(cached_value).flatten(),
1392 value_included: include_value,
1393 staleness,
1394 volatile,
1395 spill,
1396 })
1397 }
1398
1399 pub fn inspect_cell(
1401 &self,
1402 cell: &CellAddress,
1403 options: &SnapshotOptions,
1404 ) -> Result<CellSnapshotReport, InspectError> {
1405 let (key, _) = self.canonical_cell(cell)?;
1406 Ok(CellSnapshotReport {
1407 stamp: self.inspect_stamp(),
1408 cell: self.snapshot_for_key(key, options.include_values)?,
1409 })
1410 }
1411
1412 fn resolve_name(&self, key: CellKey, name: &str) -> NameResolution {
1413 let Some(named) = self.graph.resolve_name_entry(name, key.sheet_id) else {
1414 return NameResolution::Unresolved;
1415 };
1416 match &named.definition {
1417 NamedDefinition::Cell(cell) => NameResolution::Cell(CellAddress {
1418 sheet: self.graph.sheet_name(cell.sheet_id).to_string(),
1419 row: cell.coord.row() + 1,
1420 column: cell.coord.col() + 1,
1421 }),
1422 NamedDefinition::Range(range) => {
1423 let resolved = RangeAddress {
1424 sheet: self.graph.sheet_name(range.start.sheet_id).to_string(),
1425 start_row: range.start.coord.row() + 1,
1426 start_col: range.start.coord.col() + 1,
1427 end_row: range.end.coord.row() + 1,
1428 end_col: range.end.coord.col() + 1,
1429 };
1430 NameResolution::Range {
1431 declared: RangeArea::from_finite(&resolved),
1432 resolved: Some(resolved),
1433 }
1434 }
1435 NamedDefinition::Literal(value) => NameResolution::Literal(value.clone()),
1436 NamedDefinition::Formula { ast, .. } => NameResolution::Formula {
1437 formula: formualizer_parse::pretty::canonical_formula(ast),
1438 value: self.graph.get_value(named.vertex),
1439 },
1440 }
1441 }
1442
1443 fn resolve_table_area(
1444 &self,
1445 key: CellKey,
1446 table_ref: &TableReference,
1447 ) -> Option<(String, String, RangeAddress)> {
1448 let metadata = self.table_metadata(&table_ref.name)?;
1449 let canonical_name = metadata.name.clone();
1450 let specifier = table_ref
1451 .specifier
1452 .as_ref()
1453 .map(ToString::to_string)
1454 .unwrap_or_default();
1455 let mut start_row = metadata.start_row;
1456 let mut end_row = metadata.end_row;
1457 let mut start_col = metadata.start_col;
1458 let mut end_col = metadata.end_col;
1459 let data_start = start_row + u32::from(metadata.header_row);
1460 let data_end = end_row.saturating_sub(u32::from(metadata.totals_row));
1461
1462 fn col_index(headers: &[String], name: &str) -> Option<u32> {
1463 headers
1464 .iter()
1465 .position(|header| header.eq_ignore_ascii_case(name))
1466 .and_then(|index| u32::try_from(index).ok())
1467 }
1468
1469 match table_ref.specifier.as_ref()? {
1470 TableSpecifier::All | TableSpecifier::SpecialItem(SpecialItem::All) => {}
1471 TableSpecifier::Data | TableSpecifier::SpecialItem(SpecialItem::Data) => {
1472 start_row = data_start;
1473 end_row = data_end;
1474 }
1475 TableSpecifier::Headers | TableSpecifier::SpecialItem(SpecialItem::Headers) => {
1476 if !metadata.header_row {
1477 return None;
1478 }
1479 end_row = start_row;
1480 }
1481 TableSpecifier::Totals | TableSpecifier::SpecialItem(SpecialItem::Totals) => {
1482 if !metadata.totals_row {
1483 return None;
1484 }
1485 start_row = end_row;
1486 }
1487 TableSpecifier::Column(name) => {
1488 let index = col_index(&metadata.headers, name)?;
1489 start_col += index;
1490 end_col = start_col;
1491 start_row = data_start;
1492 end_row = data_end;
1493 }
1494 TableSpecifier::ColumnRange(first, last) => {
1495 let mut first = col_index(&metadata.headers, first)?;
1496 let mut last = col_index(&metadata.headers, last)?;
1497 if first > last {
1498 std::mem::swap(&mut first, &mut last);
1499 }
1500 start_col += first;
1501 end_col = metadata.start_col + last;
1502 start_row = data_start;
1503 end_row = data_end;
1504 }
1505 TableSpecifier::SpecialItem(SpecialItem::ThisRow)
1506 | TableSpecifier::Row(formualizer_parse::parser::TableRowSpecifier::Current) => {
1507 let row = key.row0 + 1;
1508 if row < data_start || row > data_end {
1509 return None;
1510 }
1511 start_row = row;
1512 end_row = row;
1513 }
1514 TableSpecifier::Row(_) => return None,
1515 TableSpecifier::Combination(parts) => {
1516 let mut this_row = false;
1517 let mut selected_column: Option<(u32, u32)> = None;
1518 for part in parts {
1519 match part.as_ref() {
1520 TableSpecifier::SpecialItem(SpecialItem::ThisRow) => this_row = true,
1521 TableSpecifier::Column(name) => {
1522 let column = col_index(&metadata.headers, name)?;
1523 selected_column = Some((column, column));
1524 }
1525 TableSpecifier::ColumnRange(first, last) => {
1526 let mut first = col_index(&metadata.headers, first)?;
1527 let mut last = col_index(&metadata.headers, last)?;
1528 if first > last {
1529 std::mem::swap(&mut first, &mut last);
1530 }
1531 selected_column = Some((first, last));
1532 }
1533 TableSpecifier::Data | TableSpecifier::SpecialItem(SpecialItem::Data) => {
1534 start_row = data_start;
1535 end_row = data_end;
1536 }
1537 TableSpecifier::Headers
1538 | TableSpecifier::SpecialItem(SpecialItem::Headers) => {
1539 if !metadata.header_row {
1540 return None;
1541 }
1542 end_row = start_row;
1543 }
1544 TableSpecifier::Totals
1545 | TableSpecifier::SpecialItem(SpecialItem::Totals) => {
1546 if !metadata.totals_row {
1547 return None;
1548 }
1549 start_row = end_row;
1550 }
1551 TableSpecifier::All | TableSpecifier::SpecialItem(SpecialItem::All) => {}
1552 TableSpecifier::Row(_) | TableSpecifier::Combination(_) => return None,
1553 }
1554 }
1555 if this_row {
1556 let row = key.row0 + 1;
1557 if row < data_start || row > data_end {
1558 return None;
1559 }
1560 start_row = row;
1561 end_row = row;
1562 }
1563 if let Some((first, last)) = selected_column {
1564 start_col = metadata.start_col + first;
1565 end_col = metadata.start_col + last;
1566 if !this_row {
1567 start_row = data_start;
1568 end_row = data_end;
1569 }
1570 }
1571 }
1572 }
1573 if start_row > end_row || start_col > end_col {
1574 return None;
1575 }
1576 Some((
1577 canonical_name,
1578 specifier,
1579 RangeAddress {
1580 sheet: metadata.sheet,
1581 start_row,
1582 start_col,
1583 end_row,
1584 end_col,
1585 },
1586 ))
1587 }
1588
1589 fn own_reference(
1590 &self,
1591 key: CellKey,
1592 reference: refs::SemanticReference<'_>,
1593 ) -> SemanticReference {
1594 match reference {
1595 refs::SemanticReference::Cell(cell) => {
1596 let sheet_id = cell
1597 .sheet
1598 .name()
1599 .and_then(|name| self.graph.sheet_id(name))
1600 .unwrap_or(key.sheet_id);
1601 SemanticReference::Cell(CellAddress {
1602 sheet: self.graph.sheet_name(sheet_id).to_string(),
1603 row: cell.row,
1604 column: cell.col,
1605 })
1606 }
1607 refs::SemanticReference::FiniteRange(range)
1608 | refs::SemanticReference::OpenRange(range) => {
1609 let sheet_id = range
1610 .sheet
1611 .name()
1612 .and_then(|name| self.graph.sheet_id(name))
1613 .unwrap_or(key.sheet_id);
1614 let declared = RangeArea {
1615 sheet: self.graph.sheet_name(sheet_id).to_string(),
1616 start_row: range.start_row,
1617 start_column: range.start_col,
1618 end_row: range.end_row,
1619 end_column: range.end_col,
1620 };
1621 let resolved = self.resolve_semantic_area(&declared);
1622 let cell_count = resolved.as_ref().map_or(0, |range| {
1623 u64::from(range.width()) * u64::from(range.height())
1624 });
1625 SemanticReference::Range {
1626 declared,
1627 resolved,
1628 cell_count,
1629 }
1630 }
1631 refs::SemanticReference::Name(name) => SemanticReference::Name {
1632 name: name.to_string(),
1633 resolution: self.resolve_name(key, name),
1634 },
1635 refs::SemanticReference::Table(table) => {
1636 if let Some((name, specifier, resolved)) = self.resolve_table_area(key, table) {
1637 SemanticReference::Table {
1638 name,
1639 specifier,
1640 resolved,
1641 }
1642 } else {
1643 SemanticReference::Unsupported {
1644 text: ReferenceType::Table(table.clone()).to_string(),
1645 reason: "structured reference could not be resolved at this placement"
1646 .to_string(),
1647 }
1648 }
1649 }
1650 refs::SemanticReference::ExternalSource(external) => SemanticReference::External {
1651 raw: external.raw.clone(),
1652 },
1653 refs::SemanticReference::ThreeDimensional(reference) => {
1654 SemanticReference::Unsupported {
1655 text: reference.to_string(),
1656 reason: "3D references are not supported by phase-1 introspection".to_string(),
1657 }
1658 }
1659 refs::SemanticReference::Unsupported(reference) => SemanticReference::Unsupported {
1660 text: reference.to_string(),
1661 reason: "reference form is unsupported by introspection".to_string(),
1662 },
1663 }
1664 }
1665
1666 fn collect_precedents(
1667 &self,
1668 key: CellKey,
1669 max_links: u32,
1670 work: &mut WorkBudget,
1671 ) -> Result<(Vec<Precedent>, TruncationReport), InspectError> {
1672 struct Collector<'a, R> {
1673 engine: &'a Engine<R>,
1674 key: CellKey,
1675 max_links: usize,
1676 work: &'a mut WorkBudget,
1677 precedents: Vec<Precedent>,
1678 truncated: bool,
1679 }
1680 impl<R: EvaluationContext> ReferenceVisitor for Collector<'_, R> {
1681 fn visit(&mut self, reference: refs::SemanticReference<'_>) -> bool {
1682 if !self.work.charge() {
1683 self.truncated = true;
1684 return false;
1685 }
1686 let reference = self.engine.own_reference(self.key, reference);
1687 if self
1688 .precedents
1689 .iter()
1690 .any(|existing| existing.reference == reference)
1691 {
1692 return true;
1693 }
1694 if self.precedents.len() >= self.max_links {
1695 self.truncated = true;
1696 return false;
1697 }
1698 self.precedents.push(Precedent {
1699 reference,
1700 provenance: Provenance::Declared,
1701 });
1702 true
1703 }
1704 }
1705
1706 let source = self.inspect_source();
1707 let mut collector = Collector {
1708 engine: self,
1709 key,
1710 max_links: max_links as usize,
1711 work,
1712 precedents: Vec::new(),
1713 truncated: false,
1714 };
1715 source.visit_declared_references(key, &mut collector)?;
1716 let truncation = if collector.truncated {
1717 TruncationReport {
1718 incomplete: true,
1719 omitted: Some(OmittedCount::AtLeast(1)),
1720 }
1721 } else {
1722 TruncationReport::default()
1723 };
1724 Ok((collector.precedents, truncation))
1725 }
1726
1727 pub fn precedents(
1730 &self,
1731 cell: &CellAddress,
1732 options: &PrecedentOptions,
1733 ) -> Result<PrecedentReport, InspectError> {
1734 let (key, canonical) = self.canonical_cell(cell)?;
1735 let mut work = WorkBudget::new(options.max_work);
1736 let (precedents, truncation) =
1737 self.collect_precedents(key, options.max_links, &mut work)?;
1738 Ok(PrecedentReport {
1739 stamp: self.inspect_stamp(),
1740 cell: canonical,
1741 precedents,
1742 truncation,
1743 })
1744 }
1745
1746 fn dependency_state_available(&self) -> Result<(), InspectError> {
1747 if self.has_staged_formulas() {
1748 Err(InspectError::DependencyStateUnavailable {
1749 reason: InspectionUnavailableReason::DeferredDependencyGraph,
1750 })
1751 } else {
1752 Ok(())
1753 }
1754 }
1755
1756 fn spill_query_members(&self, key: CellKey) -> Vec<CellKey> {
1757 let source = self.inspect_source();
1758 let Some(InternalSpillRole::Anchor { .. }) = source.spill_role(key) else {
1759 return vec![key];
1760 };
1761 let cell_ref = CellRef::new(key.sheet_id, Coord::new(key.row0, key.col0, true, true));
1762 let Some(vertex) = self.graph.get_vertex_for_cell(&cell_ref) else {
1763 return vec![key];
1764 };
1765 self.graph
1766 .spill_cells_for_anchor(vertex)
1767 .unwrap_or(&[])
1768 .iter()
1769 .map(|member| CellKey {
1770 sheet_id: member.sheet_id,
1771 row0: member.coord.row(),
1772 col0: member.coord.col(),
1773 })
1774 .collect()
1775 }
1776
1777 fn collect_dependents(
1778 &self,
1779 key: CellKey,
1780 max_results: u32,
1781 work: &mut WorkBudget,
1782 ) -> Result<(Vec<Dependent>, TruncationReport), InspectError> {
1783 self.dependency_state_available()?;
1784 let source = self.inspect_source();
1785 let mut found: FxHashMap<CellAddress, Vec<CellAddress>> = FxHashMap::default();
1786 let mut incomplete = false;
1787 let max_results = max_results as usize;
1788
1789 let spill_anchor_query = matches!(
1790 source.spill_role(key),
1791 Some(InternalSpillRole::Anchor { .. })
1792 );
1793 let members = self.spill_query_members(key);
1794 for member in members {
1795 if !work.charge() {
1796 incomplete = true;
1797 break;
1798 }
1799 let via = self.address_for_key(member);
1800 let mut record = |dependent_key: CellKey| {
1801 let address = self.address_for_key(dependent_key);
1802 if let Some(via_members) = found.get_mut(&address) {
1803 if spill_anchor_query && !via_members.contains(&via) {
1804 via_members.push(via.clone());
1805 }
1806 return true;
1807 }
1808 found.insert(
1809 address,
1810 if spill_anchor_query {
1811 vec![via.clone()]
1812 } else {
1813 Vec::new()
1814 },
1815 );
1816 true
1817 };
1818
1819 let member_ref = CellRef::new(
1820 member.sheet_id,
1821 Coord::new(member.row0, member.col0, true, true),
1822 );
1823 if let Some(vertex) = self.graph.get_vertex_for_cell(&member_ref) {
1824 let complete = self.graph.visit_direct_dependents_bounded(
1825 vertex,
1826 &mut work.remaining,
1827 &mut |dependent| self.key_for_vertex(dependent).is_none_or(&mut record),
1828 );
1829 if !complete {
1830 incomplete = true;
1831 break;
1832 }
1833 }
1834
1835 struct Visitor<'a, F>(&'a mut F);
1836 impl<F: FnMut(CellKey) -> bool> DependentVisitor for Visitor<'_, F> {
1837 fn visit(&mut self, dependent: CellKey) -> bool {
1838 (self.0)(dependent)
1839 }
1840 }
1841 let mut visitor = Visitor(&mut record);
1842 if source.visit_dependents_covering(member, work, &mut visitor)?
1843 == QueryCompleteness::Incomplete
1844 {
1845 incomplete = true;
1846 break;
1847 }
1848 }
1849
1850 let mut dependents: Vec<_> = found
1851 .into_iter()
1852 .map(|(cell, mut via)| {
1853 via.sort_by(address_cmp);
1854 via.dedup();
1855 Dependent { cell, via }
1856 })
1857 .collect();
1858 dependents.sort_by(|left, right| address_cmp(&left.cell, &right.cell));
1859 let known_omitted_dependent = dependents.len() > max_results;
1860 if known_omitted_dependent {
1861 dependents.truncate(max_results);
1862 incomplete = true;
1863 }
1864 Ok((
1865 dependents,
1866 if incomplete {
1867 TruncationReport {
1868 incomplete: true,
1869 omitted: known_omitted_dependent.then_some(OmittedCount::AtLeast(1)),
1870 }
1871 } else {
1872 TruncationReport::default()
1873 },
1874 ))
1875 }
1876
1877 pub fn dependents(
1880 &self,
1881 cell: &CellAddress,
1882 options: &DependentsOptions,
1883 ) -> Result<DependentsReport, InspectError> {
1884 let (key, canonical) = self.canonical_cell(cell)?;
1885 let mut work = WorkBudget::new(options.max_work);
1886 let (dependents, truncation) =
1887 self.collect_dependents(key, options.max_results, &mut work)?;
1888 Ok(DependentsReport {
1889 stamp: self.inspect_stamp(),
1890 cell: canonical,
1891 dependents,
1892 truncation,
1893 })
1894 }
1895
1896 fn target_addresses(reference: &SemanticReference) -> Option<&RangeAddress> {
1897 match reference {
1898 SemanticReference::Range { resolved, .. } => resolved.as_ref(),
1899 SemanticReference::Name {
1900 resolution: NameResolution::Range { resolved, .. },
1901 ..
1902 } => resolved.as_ref(),
1903 SemanticReference::Table { resolved, .. } => Some(resolved),
1904 _ => None,
1905 }
1906 }
1907
1908 fn target_cell(reference: &SemanticReference) -> Option<&CellAddress> {
1909 match reference {
1910 SemanticReference::Cell(cell) => Some(cell),
1911 SemanticReference::Name {
1912 resolution: NameResolution::Cell(cell),
1913 ..
1914 } => Some(cell),
1915 _ => None,
1916 }
1917 }
1918
1919 fn classify_cycle_dispositions(nodes: &mut [TraceNode]) {
1923 let adjacency: Vec<Vec<usize>> = nodes
1924 .iter()
1925 .map(|node| {
1926 node.links
1927 .iter()
1928 .flat_map(|link| link.targets.iter())
1929 .map(|target| target.node.0 as usize)
1930 .collect()
1931 })
1932 .collect();
1933 let mut reachability = vec![vec![false; nodes.len()]; nodes.len()];
1934 for start in 0..nodes.len() {
1935 let mut stack = adjacency[start].clone();
1936 while let Some(next) = stack.pop() {
1937 if reachability[start][next] {
1938 continue;
1939 }
1940 reachability[start][next] = true;
1941 stack.extend(adjacency[next].iter().copied());
1942 }
1943 }
1944
1945 for (source, node) in nodes.iter_mut().enumerate() {
1946 for target in node
1947 .links
1948 .iter_mut()
1949 .flat_map(|link| link.targets.iter_mut())
1950 {
1951 let target_index = target.node.0 as usize;
1952 if source == target_index || reachability[target_index][source] {
1953 target.disposition = LinkDisposition::Cycle;
1954 } else if target.disposition == LinkDisposition::Cycle {
1955 target.disposition = LinkDisposition::Convergent;
1956 }
1957 }
1958 }
1959 }
1960
1961 pub fn trace(
1963 &self,
1964 roots: &[CellAddress],
1965 options: &TraceOptions,
1966 ) -> Result<TraceGraph, InspectError> {
1967 if roots.is_empty() {
1968 return Err(InspectError::InvalidOptions {
1969 message: "trace requires at least one root".to_string(),
1970 });
1971 }
1972 let canonical_roots: Vec<_> = roots
1973 .iter()
1974 .map(|root| self.canonical_cell(root))
1975 .collect::<Result<_, _>>()?;
1976 let mut unique_roots = FxHashMap::default();
1977 for (key, canonical) in &canonical_roots {
1978 unique_roots.entry(canonical.clone()).or_insert(*key);
1979 }
1980 if unique_roots.len() > options.max_nodes as usize {
1981 return Err(InspectError::InvalidOptions {
1982 message: format!(
1983 "max_nodes ({}) must hold all {} unique roots",
1984 options.max_nodes,
1985 unique_roots.len()
1986 ),
1987 });
1988 }
1989 if options.direction == TraceDirection::Dependents {
1990 self.dependency_state_available()?;
1991 }
1992
1993 let mut nodes = Vec::new();
1994 let mut node_by_address: FxHashMap<CellAddress, TraceNodeId> = FxHashMap::default();
1995 let mut root_ids = Vec::new();
1996 let mut queue = VecDeque::new();
1997 let mut parents: HashMap<TraceNodeId, Option<TraceNodeId>> = HashMap::new();
1998 let mut truncation = TruncationReport::default();
1999
2000 for (key, canonical) in canonical_roots {
2003 if let Some(&id) = node_by_address.get(&canonical) {
2004 root_ids.push(id);
2005 continue;
2006 }
2007 let id = TraceNodeId(nodes.len() as u32);
2008 nodes.push(TraceNode {
2009 id,
2010 cell: self.snapshot_for_key(key, options.include_values)?,
2011 links: Vec::new(),
2012 });
2013 node_by_address.insert(canonical, id);
2014 root_ids.push(id);
2015 queue.push_back((id, key, 0u32));
2016 parents.insert(id, None);
2017 }
2018
2019 let mut work = WorkBudget::new(options.max_work);
2020 let mut links_used = 0u32;
2021 let mut range_members_used = 0u32;
2022
2023 while let Some((source_id, source_key, depth)) = queue.pop_front() {
2024 let can_follow = depth < options.max_depth;
2025 let mut links = Vec::new();
2026 if options.direction == TraceDirection::Precedents {
2027 if let Some(InternalSpillRole::Member { anchor }) =
2028 self.inspect_source().spill_role(source_key)
2029 {
2030 if links_used < options.max_links {
2031 links_used += 1;
2032 let anchor_address = self.address_for_key(anchor);
2033 let mut link = TraceLink {
2034 reference: SemanticReference::Cell(anchor_address.clone()),
2035 kind: TraceLinkKind::SpillAnchor,
2036 targets: Vec::new(),
2037 omitted: None,
2038 };
2039 self.attach_cell_target(
2040 anchor_address,
2041 source_id,
2042 anchor,
2043 can_follow,
2044 depth,
2045 options,
2046 false,
2047 &mut nodes,
2048 &mut node_by_address,
2049 &mut parents,
2050 &mut queue,
2051 &mut link,
2052 &mut truncation,
2053 )?;
2054 links.push(link);
2055 } else {
2056 truncation.incomplete = true;
2057 merge_omitted(&mut truncation.omitted, OmittedCount::AtLeast(1));
2058 }
2059 } else {
2060 let available_links = options.max_links.saturating_sub(links_used);
2061 let (precedents, local_truncation) =
2062 self.collect_precedents(source_key, available_links, &mut work)?;
2063 if local_truncation.incomplete {
2064 truncation.incomplete = true;
2065 if let Some(omitted) = local_truncation.omitted {
2066 merge_omitted(&mut truncation.omitted, omitted);
2067 }
2068 }
2069 for precedent in precedents {
2070 links_used += 1;
2071 let mut link = TraceLink {
2072 reference: precedent.reference,
2073 kind: TraceLinkKind::Formula {
2074 provenance: precedent.provenance,
2075 },
2076 targets: Vec::new(),
2077 omitted: None,
2078 };
2079 if let Some(cell) = Self::target_cell(&link.reference).cloned() {
2080 let (target_key, canonical) = self.canonical_cell(&cell)?;
2081 self.attach_cell_target(
2082 canonical,
2083 source_id,
2084 target_key,
2085 can_follow,
2086 depth,
2087 options,
2088 false,
2089 &mut nodes,
2090 &mut node_by_address,
2091 &mut parents,
2092 &mut queue,
2093 &mut link,
2094 &mut truncation,
2095 )?;
2096 } else if let Some(range) = Self::target_addresses(&link.reference).cloned()
2097 {
2098 self.attach_range_targets(
2099 &range,
2100 source_id,
2101 can_follow,
2102 depth,
2103 options,
2104 &mut range_members_used,
2105 &mut nodes,
2106 &mut node_by_address,
2107 &mut parents,
2108 &mut queue,
2109 &mut link,
2110 &mut truncation,
2111 )?;
2112 }
2113 links.push(link);
2114 }
2115 }
2116 } else {
2117 let available = options.max_links.saturating_sub(links_used);
2118 let (dependents, local_truncation) =
2119 self.collect_dependents(source_key, available, &mut work)?;
2120 if local_truncation.incomplete {
2121 truncation.incomplete = true;
2122 if let Some(omitted) = local_truncation.omitted {
2123 merge_omitted(&mut truncation.omitted, omitted);
2124 }
2125 }
2126 for dependent in dependents {
2127 links_used += 1;
2128 let spill_reader = dependent
2129 .via
2130 .iter()
2131 .any(|via| via != &self.address_for_key(source_key));
2132 let (target_key, canonical) = self.canonical_cell(&dependent.cell)?;
2133 let mut link = TraceLink {
2134 reference: SemanticReference::Cell(canonical.clone()),
2135 kind: if spill_reader {
2136 TraceLinkKind::SpillReader
2137 } else {
2138 TraceLinkKind::Formula {
2139 provenance: Provenance::Declared,
2140 }
2141 },
2142 targets: Vec::new(),
2143 omitted: None,
2144 };
2145 self.attach_cell_target(
2146 canonical,
2147 source_id,
2148 target_key,
2149 can_follow,
2150 depth,
2151 options,
2152 false,
2153 &mut nodes,
2154 &mut node_by_address,
2155 &mut parents,
2156 &mut queue,
2157 &mut link,
2158 &mut truncation,
2159 )?;
2160 links.push(link);
2161 }
2162 }
2163 nodes[source_id.0 as usize].links = links;
2164 }
2165
2166 Self::classify_cycle_dispositions(&mut nodes);
2167
2168 Ok(TraceGraph {
2169 stamp: self.inspect_stamp(),
2170 direction: options.direction,
2171 roots: root_ids,
2172 nodes,
2173 truncation,
2174 })
2175 }
2176
2177 #[allow(clippy::too_many_arguments)]
2178 fn attach_cell_target(
2179 &self,
2180 address: CellAddress,
2181 source_id: TraceNodeId,
2182 key: CellKey,
2183 can_follow: bool,
2184 depth: u32,
2185 options: &TraceOptions,
2186 defer_missing_omission: bool,
2187 nodes: &mut Vec<TraceNode>,
2188 node_by_address: &mut FxHashMap<CellAddress, TraceNodeId>,
2189 parents: &mut HashMap<TraceNodeId, Option<TraceNodeId>>,
2190 queue: &mut VecDeque<(TraceNodeId, CellKey, u32)>,
2191 link: &mut TraceLink,
2192 truncation: &mut TruncationReport,
2193 ) -> Result<(), InspectError> {
2194 if let Some(&target_id) = node_by_address.get(&address) {
2195 link.targets.push(TraceLinkTarget {
2196 node: target_id,
2197 disposition: LinkDisposition::Convergent,
2200 });
2201 return Ok(());
2202 }
2203 if nodes.len() >= options.max_nodes as usize {
2204 link.omitted = Some(OmittedCount::AtLeast(1));
2205 truncation.incomplete = true;
2206 if !defer_missing_omission {
2207 merge_omitted(&mut truncation.omitted, OmittedCount::AtLeast(1));
2208 }
2209 return Ok(());
2210 }
2211 let target_id = TraceNodeId(nodes.len() as u32);
2212 nodes.push(TraceNode {
2213 id: target_id,
2214 cell: self.snapshot_for_key(key, options.include_values)?,
2215 links: Vec::new(),
2216 });
2217 node_by_address.insert(address, target_id);
2218 parents.insert(target_id, Some(source_id));
2219 link.targets.push(TraceLinkTarget {
2220 node: target_id,
2221 disposition: if can_follow {
2222 LinkDisposition::Expanded
2223 } else {
2224 LinkDisposition::Elided
2225 },
2226 });
2227 if can_follow {
2228 queue.push_back((target_id, key, depth + 1));
2229 } else {
2230 truncation.incomplete = true;
2233 }
2234 Ok(())
2235 }
2236
2237 #[allow(clippy::too_many_arguments)]
2238 fn attach_range_targets(
2239 &self,
2240 range: &RangeAddress,
2241 source_id: TraceNodeId,
2242 can_follow: bool,
2243 depth: u32,
2244 options: &TraceOptions,
2245 range_members_used: &mut u32,
2246 nodes: &mut Vec<TraceNode>,
2247 node_by_address: &mut FxHashMap<CellAddress, TraceNodeId>,
2248 parents: &mut HashMap<TraceNodeId, Option<TraceNodeId>>,
2249 queue: &mut VecDeque<(TraceNodeId, CellKey, u32)>,
2250 link: &mut TraceLink,
2251 truncation: &mut TruncationReport,
2252 ) -> Result<(), InspectError> {
2253 let total = u64::from(range.width()) * u64::from(range.height());
2254 let mut attached = 0u64;
2255 let mut compressed_ancestors = Vec::new();
2256 let mut cursor = Some(source_id);
2257 while let Some(ancestor) = cursor {
2258 let address = &nodes[ancestor.0 as usize].cell.address;
2259 if range.sheet == address.sheet
2260 && range.start_row <= address.row
2261 && address.row <= range.end_row
2262 && range.start_col <= address.column
2263 && address.column <= range.end_col
2264 {
2265 compressed_ancestors.push((ancestor, address.row, address.column));
2266 }
2267 cursor = parents.get(&ancestor).copied().flatten();
2268 }
2269
2270 for (ancestor, _, _) in &compressed_ancestors {
2273 link.targets.push(TraceLinkTarget {
2274 node: *ancestor,
2275 disposition: LinkDisposition::Convergent,
2278 });
2279 attached += 1;
2280 }
2281
2282 'rows: for row in range.start_row..=range.end_row {
2283 for column in range.start_col..=range.end_col {
2284 if compressed_ancestors
2285 .iter()
2286 .any(|(_, ancestor_row, ancestor_column)| {
2287 row == *ancestor_row && column == *ancestor_column
2288 })
2289 {
2290 continue;
2291 }
2292 if *range_members_used >= options.range_member_budget {
2293 break 'rows;
2294 }
2295 *range_members_used += 1;
2296 let address = CellAddress {
2297 sheet: range.sheet.clone(),
2298 row,
2299 column,
2300 };
2301 let (key, canonical) = self.canonical_cell(&address)?;
2302 let before = link.targets.len();
2303 self.attach_cell_target(
2304 canonical,
2305 source_id,
2306 key,
2307 can_follow,
2308 depth,
2309 options,
2310 true,
2311 nodes,
2312 node_by_address,
2313 parents,
2314 queue,
2315 link,
2316 truncation,
2317 )?;
2318 if link.targets.len() > before {
2319 attached += 1;
2320 } else if link.omitted.is_some() {
2321 break 'rows;
2322 }
2323 }
2324 }
2325 if attached < total {
2326 let omitted = OmittedCount::Exact(total - attached);
2327 link.omitted = Some(omitted);
2328 truncation.incomplete = true;
2329 merge_omitted(&mut truncation.omitted, omitted);
2330 }
2331 Ok(())
2332 }
2333
2334 pub fn range_page(
2336 &self,
2337 area: &RangeArea,
2338 options: &RangePageOptions,
2339 ) -> Result<RangePage, InspectError> {
2340 if options.limit == 0 {
2341 return Err(InspectError::InvalidOptions {
2342 message: "range page limit must be at least one".to_string(),
2343 });
2344 }
2345 let stamp = self.inspect_stamp();
2346 if let Some(expected) = options.expected_stamp
2347 && expected != stamp
2348 {
2349 return Err(InspectError::RevisionMismatch {
2350 expected,
2351 actual: stamp,
2352 });
2353 }
2354 let (_, declared) = self.canonical_area(area)?;
2355 let resolved = self.resolve_semantic_area(&declared);
2356 let total = resolved.as_ref().map_or(0, |range| {
2357 u64::from(range.width()) * u64::from(range.height())
2358 });
2359 let start = options.offset.min(total);
2360 let end = start.saturating_add(u64::from(options.limit)).min(total);
2361 let mut items = Vec::new();
2362 items
2363 .try_reserve((end - start) as usize)
2364 .map_err(|_| InspectError::ResourceExhausted {
2365 resource: "range page items",
2366 })?;
2367 if let Some(range) = &resolved {
2368 let width = u64::from(range.width());
2369 for offset in start..end {
2370 let row = range.start_row + u32::try_from(offset / width).unwrap_or(u32::MAX);
2371 let column = range.start_col + u32::try_from(offset % width).unwrap_or(u32::MAX);
2372 let (key, _) = self.canonical_cell(&CellAddress {
2373 sheet: range.sheet.clone(),
2374 row,
2375 column,
2376 })?;
2377 items.push(self.snapshot_for_key(key, options.include_values)?);
2378 }
2379 }
2380 Ok(RangePage {
2381 stamp,
2382 declared,
2383 resolved,
2384 total,
2385 offset: options.offset,
2386 items,
2387 next_offset: (end < total).then_some(end),
2388 })
2389 }
2390}