1use super::*;
2use crate::engine::used_extent::{ExtentPolicy, OpenRangeBounds, resolve_used_extent};
3use formualizer_common::LiteralValue;
4use formualizer_parse::parser::{ASTNode, ASTNodeType, ReferenceType};
5
6#[derive(Clone, Copy, PartialEq, Eq)]
7enum RangeSelfUse {
8 NoMatch,
9 Excluded,
10 IncludedOrUnknown,
11}
12
13impl RangeSelfUse {
14 fn merge(self, other: Self) -> Self {
15 match (self, other) {
16 (Self::IncludedOrUnknown, _) | (_, Self::IncludedOrUnknown) => Self::IncludedOrUnknown,
17 (Self::Excluded, _) | (_, Self::Excluded) => Self::Excluded,
18 _ => Self::NoMatch,
19 }
20 }
21}
22
23impl DependencyGraph {
24 pub(crate) fn visit_range_dependents_covering_bounded(
33 &self,
34 sheet_id: SheetId,
35 row0: u32,
36 col0: u32,
37 remaining_work: &mut u64,
38 visitor: &mut dyn FnMut(VertexId) -> bool,
39 ) -> bool {
40 if self.stripe_to_dependents.is_empty() {
41 return true;
42 }
43
44 let mut seen = FxHashSet::default();
45 let keys = [
46 StripeKey {
47 sheet_id,
48 stripe_type: StripeType::Column,
49 index: col0,
50 },
51 StripeKey {
52 sheet_id,
53 stripe_type: StripeType::Row,
54 index: row0,
55 },
56 StripeKey {
57 sheet_id,
58 stripe_type: StripeType::Block,
59 index: block_index(row0, col0),
60 },
61 ];
62
63 for key in keys {
64 if key.stripe_type == StripeType::Block && !self.config.enable_block_stripes {
65 continue;
66 }
67 let Some(candidates) = self.stripe_to_dependents.get(&key) else {
68 continue;
69 };
70 for &dependent in candidates {
71 if *remaining_work == 0 {
72 return false;
73 }
74 *remaining_work -= 1;
75 if !seen.insert(dependent) {
76 continue;
77 }
78 let Some(ranges) = self.formula_to_range_deps.get(&dependent) else {
79 continue;
80 };
81 let mut covered = false;
82 for range in ranges {
83 if *remaining_work == 0 {
84 return false;
85 }
86 *remaining_work -= 1;
87 let range_sheet = match range.sheet {
90 SharedSheetLocator::Id(id) => id,
91 _ => sheet_id,
92 };
93 if range_sheet != sheet_id {
94 continue;
95 }
96 let start_row = range.start_row.map(|bound| bound.index).unwrap_or(0);
97 let end_row = range.end_row.map(|bound| bound.index).unwrap_or(u32::MAX);
98 let start_col = range.start_col.map(|bound| bound.index).unwrap_or(0);
99 let end_col = range.end_col.map(|bound| bound.index).unwrap_or(u32::MAX);
100 if start_row <= row0 && row0 <= end_row && start_col <= col0 && col0 <= end_col
101 {
102 covered = true;
103 break;
104 }
105 }
106 if covered && !visitor(dependent) {
107 return false;
108 }
109 }
110 }
111 true
112 }
113
114 pub fn add_range_edges(
116 &mut self,
117 dependent: VertexId,
118 ranges: &[SharedRangeRef<'static>],
119 current_sheet_id: SheetId,
120 ) {
121 self.add_range_dependent_edges(dependent, ranges, current_sheet_id);
122 }
123
124 pub fn get_range_dependencies(
128 &self,
129 vertex: VertexId,
130 ) -> Option<&Vec<SharedRangeRef<'static>>> {
131 self.formula_to_range_deps.get(&vertex)
132 }
133
134 #[cfg(test)]
135 pub(crate) fn formula_to_range_deps(
136 &self,
137 ) -> &FxHashMap<VertexId, Vec<SharedRangeRef<'static>>> {
138 &self.formula_to_range_deps
139 }
140
141 #[cfg(test)]
142 pub(crate) fn stripe_to_dependents(&self) -> &FxHashMap<StripeKey, FxHashSet<VertexId>> {
143 &self.stripe_to_dependents
144 }
145
146 fn range_region_contains_self(
153 &self,
154 dependent: VertexId,
155 sheet_id: SheetId,
156 s_row: Option<u32>,
157 e_row: Option<u32>,
158 s_col: Option<u32>,
159 e_col: Option<u32>,
160 ) -> bool {
161 if self.store.sheet_id(dependent) != sheet_id {
162 return false;
163 }
164 let coord = self.store.coord(dependent);
165 let r0 = coord.row();
166 let c0 = coord.col();
167 s_row.is_none_or(|s| r0 >= s)
168 && e_row.is_none_or(|e| r0 <= e)
169 && s_col.is_none_or(|s| c0 >= s)
170 && e_col.is_none_or(|e| c0 <= e)
171 }
172
173 fn record_self_loop(&mut self, vertex: VertexId) {
176 if !self.has_self_loop(vertex) {
177 self.edges.add_edge(vertex, vertex);
178 }
179 }
180
181 pub(crate) fn compressed_range_resolved_bounds(
182 &self,
183 sheet: SheetId,
184 range: (Option<u32>, Option<u32>, Option<u32>, Option<u32>),
185 ) -> Option<(u32, u32, u32, u32)> {
186 let (start_row, end_row, start_col, end_col) = range;
187 let extent = resolve_used_extent(
188 OpenRangeBounds {
189 start_row,
190 start_column: start_col,
191 end_row,
192 end_column: end_col,
193 },
194 ExtentPolicy::GraphCompat {
195 fallback_row: self.config.max_open_ended_rows.saturating_sub(1),
196 fallback_column: self.config.max_open_ended_cols.saturating_sub(1),
197 },
198 |first, last| self.used_row_bounds_for_columns(sheet, first, last),
199 |first, last| self.used_col_bounds_for_rows(sheet, first, last),
200 )?;
201 Some((
202 extent.start_row,
203 extent.end_row,
204 extent.start_column,
205 extent.end_column,
206 ))
207 }
208
209 fn compressed_range_self_use(
216 &self,
217 dependent: VertexId,
218 range_sheet: SheetId,
219 range: (Option<u32>, Option<u32>, Option<u32>, Option<u32>),
220 ) -> RangeSelfUse {
221 let Some(ast) = self.get_formula(dependent) else {
222 return RangeSelfUse::IncludedOrUnknown;
223 };
224
225 fn static_index(node: &ASTNode) -> Option<i64> {
226 match &node.node_type {
227 ASTNodeType::Literal(LiteralValue::Int(value)) => Some(*value),
228 ASTNodeType::Literal(LiteralValue::Number(value)) if value.is_finite() => {
229 Some(*value as i64)
230 }
231 ASTNodeType::UnaryOp { op, expr } if op == "+" => static_index(expr),
232 ASTNodeType::UnaryOp { op, expr } if op == "-" => static_index(expr)?.checked_neg(),
233 _ => None,
234 }
235 }
236
237 fn matching_range(
238 graph: &DependencyGraph,
239 node: &ASTNode,
240 dependent: VertexId,
241 range_sheet: SheetId,
242 range: (Option<u32>, Option<u32>, Option<u32>, Option<u32>),
243 ) -> bool {
244 let ASTNodeType::Reference {
245 reference:
246 ReferenceType::Range {
247 sheet,
248 start_row,
249 start_col,
250 end_row,
251 end_col,
252 ..
253 },
254 ..
255 } = &node.node_type
256 else {
257 return false;
258 };
259 let sheet_id = match sheet.as_deref() {
260 Some(name) => match graph.sheet_id(name) {
261 Some(id) => id,
262 None => return false,
263 },
264 None => graph.get_vertex_sheet_id(dependent),
265 };
266 sheet_id == range_sheet
267 && start_row.map(|index| index.saturating_sub(1)) == range.0
268 && end_row.map(|index| index.saturating_sub(1)) == range.1
269 && start_col.map(|index| index.saturating_sub(1)) == range.2
270 && end_col.map(|index| index.saturating_sub(1)) == range.3
271 }
272
273 fn selected_region_contains_self(
274 graph: &DependencyGraph,
275 dependent: VertexId,
276 range_sheet: SheetId,
277 range: (Option<u32>, Option<u32>, Option<u32>, Option<u32>),
278 position: i64,
279 explicit_col: Option<i64>,
280 ) -> Option<bool> {
281 let (sr, er, sc, ec) = graph.compressed_range_resolved_bounds(range_sheet, range)?;
282 let (row, col) = match explicit_col {
283 Some(col) => (position, col),
284 None if sr == er => (1, position),
285 None => (position, 1),
286 };
287 if row < 0 || col < 0 {
288 return Some(false);
289 }
290 let coord = graph.store.coord(dependent);
291 let contains = if row == 0 && col == 0 {
292 coord.row() >= sr && coord.row() <= er && coord.col() >= sc && coord.col() <= ec
293 } else if col == 0 {
294 let selected_row = sr.checked_add(u32::try_from(row).ok()?.saturating_sub(1))?;
295 selected_row <= er
296 && coord.row() == selected_row
297 && coord.col() >= sc
298 && coord.col() <= ec
299 } else if row == 0 {
300 let selected_col = sc.checked_add(u32::try_from(col).ok()?.saturating_sub(1))?;
301 selected_col <= ec
302 && coord.col() == selected_col
303 && coord.row() >= sr
304 && coord.row() <= er
305 } else {
306 let selected_row = sr.checked_add(u32::try_from(row).ok()?.saturating_sub(1))?;
307 let selected_col = sc.checked_add(u32::try_from(col).ok()?.saturating_sub(1))?;
308 selected_row <= er
309 && selected_col <= ec
310 && coord.row() == selected_row
311 && coord.col() == selected_col
312 };
313 Some(contains)
314 }
315
316 fn visit(
317 graph: &DependencyGraph,
318 node: &ASTNode,
319 dependent: VertexId,
320 range_sheet: SheetId,
321 range: (Option<u32>, Option<u32>, Option<u32>, Option<u32>),
322 index: Option<(i64, Option<i64>)>,
323 ) -> RangeSelfUse {
324 if matching_range(graph, node, dependent, range_sheet, range) {
325 return match index.and_then(|(row, col)| {
326 selected_region_contains_self(graph, dependent, range_sheet, range, row, col)
327 }) {
328 Some(false) => RangeSelfUse::Excluded,
329 Some(true) | None => RangeSelfUse::IncludedOrUnknown,
330 };
331 }
332 match &node.node_type {
333 ASTNodeType::Function { name, args }
334 if name.eq_ignore_ascii_case("INDEX") && (2..=3).contains(&args.len()) =>
335 {
336 let row = static_index(&args[1]);
337 let col = args.get(2).and_then(static_index);
338 let selection = row.and_then(|row| {
339 if args.len() == 2 || col.is_some() {
340 Some((row, col))
341 } else {
342 None
343 }
344 });
345 let mut use_kind =
346 visit(graph, &args[0], dependent, range_sheet, range, selection);
347 for arg in &args[1..] {
348 use_kind =
349 use_kind.merge(visit(graph, arg, dependent, range_sheet, range, None));
350 }
351 use_kind
352 }
353 ASTNodeType::Function { args, .. } => {
354 args.iter().fold(RangeSelfUse::NoMatch, |kind, arg| {
355 kind.merge(visit(graph, arg, dependent, range_sheet, range, None))
356 })
357 }
358 ASTNodeType::UnaryOp { expr, .. } => {
359 visit(graph, expr, dependent, range_sheet, range, None)
360 }
361 ASTNodeType::BinaryOp { left, right, .. } => visit(
362 graph,
363 left,
364 dependent,
365 range_sheet,
366 range,
367 None,
368 )
369 .merge(visit(graph, right, dependent, range_sheet, range, None)),
370 ASTNodeType::Call { callee, args } => {
371 let mut kind = visit(graph, callee, dependent, range_sheet, range, None);
372 for arg in args {
373 kind = kind.merge(visit(graph, arg, dependent, range_sheet, range, None));
374 }
375 kind
376 }
377 ASTNodeType::Array(rows) => {
378 rows.iter()
379 .flatten()
380 .fold(RangeSelfUse::NoMatch, |kind, item| {
381 kind.merge(visit(graph, item, dependent, range_sheet, range, None))
382 })
383 }
384 ASTNodeType::Literal(_) | ASTNodeType::Omitted | ASTNodeType::Reference { .. } => {
385 RangeSelfUse::NoMatch
386 }
387 }
388 }
389
390 visit(self, &ast, dependent, range_sheet, range, None)
391 }
392
393 pub(super) fn add_range_dependent_edges(
394 &mut self,
395 dependent: VertexId,
396 ranges: &[SharedRangeRef<'static>],
397 current_sheet_id: SheetId,
398 ) {
399 if ranges.is_empty() {
400 return;
401 }
402
403 self.formula_to_range_deps
404 .insert(dependent, ranges.to_vec());
405
406 for range in ranges {
407 let sheet_id = match range.sheet {
408 SharedSheetLocator::Id(id) => id,
409 _ => current_sheet_id,
410 };
411
412 let s_row = range.start_row.map(|b| b.index);
413 let e_row = range.end_row.map(|b| b.index);
414 let s_col = range.start_col.map(|b| b.index);
415 let e_col = range.end_col.map(|b| b.index);
416
417 if self.range_region_contains_self(dependent, sheet_id, s_row, e_row, s_col, e_col)
422 && self.compressed_range_self_use(dependent, sheet_id, (s_row, e_row, s_col, e_col))
423 != RangeSelfUse::Excluded
424 {
425 self.record_self_loop(dependent);
426 }
427
428 let col_stripes = (s_row.is_none() && e_row.is_none())
429 || (s_col.is_some() && e_col.is_some() && (s_row.is_none() || e_row.is_none()));
430 let row_stripes = (s_col.is_none() && e_col.is_none())
431 || (s_row.is_some() && e_row.is_some() && (s_col.is_none() || e_col.is_none()));
432
433 if col_stripes && !row_stripes {
434 let sc = s_col.unwrap_or(0);
435 let ec = e_col.unwrap_or(sc);
436 for col in sc..=ec {
437 let key = StripeKey {
438 sheet_id,
439 stripe_type: StripeType::Column,
440 index: col,
441 };
442 self.stripe_to_dependents
443 .entry(key.clone())
444 .or_default()
445 .insert(dependent);
446 #[cfg(test)]
447 {
448 if self.stripe_to_dependents.get(&key).map(|s| s.len()) == Some(1)
449 && let Ok(mut g) = self.instr.lock()
450 {
451 g.stripe_inserts += 1;
452 }
453 }
454 }
455 continue;
456 }
457
458 if row_stripes && !col_stripes {
459 let sr = s_row.unwrap_or(0);
460 let er = e_row.unwrap_or(sr);
461 for row in sr..=er {
462 let key = StripeKey {
463 sheet_id,
464 stripe_type: StripeType::Row,
465 index: row,
466 };
467 self.stripe_to_dependents
468 .entry(key.clone())
469 .or_default()
470 .insert(dependent);
471 #[cfg(test)]
472 {
473 if self.stripe_to_dependents.get(&key).map(|s| s.len()) == Some(1)
474 && let Ok(mut g) = self.instr.lock()
475 {
476 g.stripe_inserts += 1;
477 }
478 }
479 }
480 continue;
481 }
482
483 let start_row = s_row.unwrap_or(0);
484 let start_col = s_col.unwrap_or(0);
485 let end_row = e_row.unwrap_or(start_row);
486 let end_col = e_col.unwrap_or(start_col);
487
488 let height = end_row.saturating_sub(start_row) + 1;
489 let width = end_col.saturating_sub(start_col) + 1;
490
491 if self.config.enable_block_stripes && height > 1 && width > 1 {
492 let start_block_row = start_row / BLOCK_H;
493 let end_block_row = end_row / BLOCK_H;
494 let start_block_col = start_col / BLOCK_W;
495 let end_block_col = end_col / BLOCK_W;
496
497 for block_row in start_block_row..=end_block_row {
498 for block_col in start_block_col..=end_block_col {
499 let key = StripeKey {
500 sheet_id,
501 stripe_type: StripeType::Block,
502 index: block_index(block_row * BLOCK_H, block_col * BLOCK_W),
503 };
504 self.stripe_to_dependents
505 .entry(key.clone())
506 .or_default()
507 .insert(dependent);
508 #[cfg(test)]
509 {
510 if self.stripe_to_dependents.get(&key).map(|s| s.len()) == Some(1)
511 && let Ok(mut g) = self.instr.lock()
512 {
513 g.stripe_inserts += 1;
514 }
515 }
516 }
517 }
518 } else if height > width {
519 for col in start_col..=end_col {
520 let key = StripeKey {
521 sheet_id,
522 stripe_type: StripeType::Column,
523 index: col,
524 };
525 self.stripe_to_dependents
526 .entry(key.clone())
527 .or_default()
528 .insert(dependent);
529 #[cfg(test)]
530 {
531 if self.stripe_to_dependents.get(&key).map(|s| s.len()) == Some(1)
532 && let Ok(mut g) = self.instr.lock()
533 {
534 g.stripe_inserts += 1;
535 }
536 }
537 }
538 } else {
539 for row in start_row..=end_row {
540 let key = StripeKey {
541 sheet_id,
542 stripe_type: StripeType::Row,
543 index: row,
544 };
545 self.stripe_to_dependents
546 .entry(key.clone())
547 .or_default()
548 .insert(dependent);
549 #[cfg(test)]
550 {
551 if self.stripe_to_dependents.get(&key).map(|s| s.len()) == Some(1)
552 && let Ok(mut g) = self.instr.lock()
553 {
554 g.stripe_inserts += 1;
555 }
556 }
557 }
558 }
559 }
560 }
561
562 pub fn add_range_deps_from_keys(
564 &mut self,
565 dependent: VertexId,
566 keys: &[crate::engine::plan::RangeKey],
567 current_sheet_id: SheetId,
568 ) {
569 use crate::engine::plan::RangeKey as RK;
570 if keys.is_empty() {
571 return;
572 }
573
574 let mut shared_ranges: Vec<SharedRangeRef<'static>> = Vec::with_capacity(keys.len());
575 for k in keys {
576 let sheet_loc = SharedSheetLocator::Id(match k {
577 RK::Rect { sheet, .. }
578 | RK::WholeRow { sheet, .. }
579 | RK::WholeCol { sheet, .. }
580 | RK::OpenRect { sheet, .. } => *sheet,
581 });
582
583 let mk_axis = |idx0: u32| formualizer_common::AxisBound::new(idx0, false);
584
585 let built = match k {
586 RK::Rect { start, end, .. } => {
587 let sr = mk_axis(start.row());
588 let sc = mk_axis(start.col());
589 let er = mk_axis(end.row());
590 let ec = mk_axis(end.col());
591 SharedRangeRef::from_parts(sheet_loc, Some(sr), Some(sc), Some(er), Some(ec))
592 .ok()
593 }
594 RK::WholeRow { row, .. } => {
595 let r0 = row.saturating_sub(1);
596 let b = mk_axis(r0);
597 SharedRangeRef::from_parts(sheet_loc, Some(b), None, Some(b), None).ok()
598 }
599 RK::WholeCol { col, .. } => {
600 let c0 = col.saturating_sub(1);
601 let b = mk_axis(c0);
602 SharedRangeRef::from_parts(sheet_loc, None, Some(b), None, Some(b)).ok()
603 }
604 RK::OpenRect { start, end, .. } => {
605 let (sr, sc) = match start {
606 Some(p) => (Some(mk_axis(p.row())), Some(mk_axis(p.col()))),
607 None => (None, None),
608 };
609 let (er, ec) = match end {
610 Some(p) => (Some(mk_axis(p.row())), Some(mk_axis(p.col()))),
611 None => (None, None),
612 };
613 SharedRangeRef::from_parts(sheet_loc, sr, sc, er, ec).ok()
614 }
615 };
616
617 if let Some(r) = built {
618 shared_ranges.push(r.into_owned());
619 }
620 }
621
622 if shared_ranges.is_empty() {
623 return;
624 }
625
626 self.formula_to_range_deps
627 .insert(dependent, shared_ranges.clone());
628
629 for range in &shared_ranges {
630 let sheet_id = match range.sheet {
631 SharedSheetLocator::Id(id) => id,
632 _ => current_sheet_id,
633 };
634
635 let s_row = range.start_row.map(|b| b.index);
636 let e_row = range.end_row.map(|b| b.index);
637 let s_col = range.start_col.map(|b| b.index);
638 let e_col = range.end_col.map(|b| b.index);
639
640 if self.range_region_contains_self(dependent, sheet_id, s_row, e_row, s_col, e_col)
643 && self.compressed_range_self_use(dependent, sheet_id, (s_row, e_row, s_col, e_col))
644 != RangeSelfUse::Excluded
645 {
646 self.record_self_loop(dependent);
647 }
648
649 let col_stripes = (s_row.is_none() && e_row.is_none())
650 || (s_col.is_some() && e_col.is_some() && (s_row.is_none() || e_row.is_none()));
651 let row_stripes = (s_col.is_none() && e_col.is_none())
652 || (s_row.is_some() && e_row.is_some() && (s_col.is_none() || e_col.is_none()));
653
654 if col_stripes && !row_stripes {
655 let sc = s_col.unwrap_or(0);
656 let ec = e_col.unwrap_or(sc);
657 for col in sc..=ec {
658 let key = StripeKey {
659 sheet_id,
660 stripe_type: StripeType::Column,
661 index: col,
662 };
663 self.stripe_to_dependents
664 .entry(key)
665 .or_default()
666 .insert(dependent);
667 }
668 continue;
669 }
670
671 if row_stripes && !col_stripes {
672 let sr = s_row.unwrap_or(0);
673 let er = e_row.unwrap_or(sr);
674 for row in sr..=er {
675 let key = StripeKey {
676 sheet_id,
677 stripe_type: StripeType::Row,
678 index: row,
679 };
680 self.stripe_to_dependents
681 .entry(key)
682 .or_default()
683 .insert(dependent);
684 }
685 continue;
686 }
687
688 let start_row = s_row.unwrap_or(0);
689 let start_col = s_col.unwrap_or(0);
690 let end_row = e_row.unwrap_or(start_row);
691 let end_col = e_col.unwrap_or(start_col);
692
693 let height = end_row.saturating_sub(start_row) + 1;
694 let width = end_col.saturating_sub(start_col) + 1;
695
696 if self.config.enable_block_stripes && height > 1 && width > 1 {
697 let start_block_row = start_row / BLOCK_H;
698 let end_block_row = end_row / BLOCK_H;
699 let start_block_col = start_col / BLOCK_W;
700 let end_block_col = end_col / BLOCK_W;
701
702 for block_row in start_block_row..=end_block_row {
703 for block_col in start_block_col..=end_block_col {
704 let key = StripeKey {
705 sheet_id,
706 stripe_type: StripeType::Block,
707 index: block_index(block_row * BLOCK_H, block_col * BLOCK_W),
708 };
709 self.stripe_to_dependents
710 .entry(key)
711 .or_default()
712 .insert(dependent);
713 }
714 }
715 } else if height > width {
716 for col in start_col..=end_col {
717 let key = StripeKey {
718 sheet_id,
719 stripe_type: StripeType::Column,
720 index: col,
721 };
722 self.stripe_to_dependents
723 .entry(key)
724 .or_default()
725 .insert(dependent);
726 }
727 } else {
728 for row in start_row..=end_row {
729 let key = StripeKey {
730 sheet_id,
731 stripe_type: StripeType::Row,
732 index: row,
733 };
734 self.stripe_to_dependents
735 .entry(key)
736 .or_default()
737 .insert(dependent);
738 }
739 }
740 }
741 }
742}