1use std::sync::Mutex;
46
47use formualizer_common::{ExcelError, LiteralValue};
48use formualizer_parse::parser::{ReferenceType, TableReference};
49use rustc_hash::{FxHashMap, FxHashSet};
50
51use crate::engine::eval::Engine;
52use crate::engine::range_view::RangeView;
53use crate::reference::{CellRef, SheetId};
54use crate::traits::{
55 EvaluationContext, FunctionProvider, NamedRangeResolver, Range, RangeResolver, ReferenceInfo,
56 ReferenceResolver, Resolver, SourceResolver, Table, TableResolver,
57};
58
59#[derive(Clone, Copy, Debug, Eq, PartialEq)]
63struct MemberCell {
64 sheet_id: SheetId,
65 row: u32,
66 col: u32,
67}
68
69#[derive(Default)]
70struct CollectorState {
71 current: Option<u32>,
75 edges: FxHashSet<(u32, u32)>,
78}
79
80pub struct LiveEdgeCollector {
93 members: Vec<MemberCell>,
95 index: FxHashMap<(SheetId, u32, u32), u32>,
97 name_index: FxHashMap<String, u32>,
100 total_members: usize,
102 state: Mutex<CollectorState>,
105}
106
107impl LiveEdgeCollector {
108 pub fn new(members: &[CellRef]) -> Self {
111 Self::new_with_names(members, &[])
112 }
113
114 pub fn new_with_names(cells: &[CellRef], names: &[String]) -> Self {
119 let members: Vec<MemberCell> = cells
120 .iter()
121 .map(|c| MemberCell {
122 sheet_id: c.sheet_id,
123 row: c.coord.row(),
124 col: c.coord.col(),
125 })
126 .collect();
127 let mut index = FxHashMap::default();
128 index.reserve(members.len());
129 for (i, m) in members.iter().enumerate() {
130 index.insert((m.sheet_id, m.row, m.col), i as u32);
131 }
132 let mut name_index = FxHashMap::default();
133 name_index.reserve(names.len());
134 for (j, name) in names.iter().enumerate() {
135 name_index.insert(name.clone(), (members.len() + j) as u32);
136 }
137 let total_members = members.len() + names.len();
138 Self {
139 members,
140 index,
141 name_index,
142 total_members,
143 state: Mutex::new(CollectorState::default()),
144 }
145 }
146
147 pub fn member_count(&self) -> usize {
148 self.total_members
149 }
150
151 pub fn set_current(&self, member_idx: u32) {
154 debug_assert!((member_idx as usize) < self.total_members);
155 self.state.lock().unwrap().current = Some(member_idx);
156 }
157
158 pub fn clear_current(&self) {
161 self.state.lock().unwrap().current = None;
162 }
163
164 pub fn record_scalar(&self, sheet_id: SheetId, row: u32, col: u32) {
166 let Some(&to) = self.index.get(&(sheet_id, row, col)) else {
167 return;
168 };
169 let mut st = self.state.lock().unwrap();
170 if let Some(from) = st.current {
171 st.edges.insert((from, to));
172 }
173 }
174
175 pub fn record_rect(&self, sheet_id: SheetId, sr: u32, sc: u32, er: u32, ec: u32) {
179 let mut st = self.state.lock().unwrap();
180 let Some(from) = st.current else {
181 return;
182 };
183 for (i, m) in self.members.iter().enumerate() {
184 if m.sheet_id == sheet_id && m.row >= sr && m.row <= er && m.col >= sc && m.col <= ec {
185 st.edges.insert((from, i as u32));
186 }
187 }
188 }
189
190 pub fn record_name(&self, folded_name: &str) {
193 let Some(&to) = self.name_index.get(folded_name) else {
194 return;
195 };
196 let mut st = self.state.lock().unwrap();
197 if let Some(from) = st.current {
198 st.edges.insert((from, to));
199 }
200 }
201
202 pub fn take_edges(&self) -> FxHashSet<(u32, u32)> {
205 std::mem::take(&mut self.state.lock().unwrap().edges)
206 }
207}
208
209pub trait ReadSink: Send + Sync {
213 fn record_scalar(&self, sheet_id: SheetId, row: u32, col: u32);
214 fn record_rect(&self, sheet_id: SheetId, sr: u32, sc: u32, er: u32, ec: u32);
215 fn record_name(&self, folded_name: &str);
216}
217
218impl ReadSink for LiveEdgeCollector {
219 fn record_scalar(&self, sheet_id: SheetId, row: u32, col: u32) {
220 LiveEdgeCollector::record_scalar(self, sheet_id, row, col)
221 }
222 fn record_rect(&self, sheet_id: SheetId, sr: u32, sc: u32, er: u32, ec: u32) {
223 LiveEdgeCollector::record_rect(self, sheet_id, sr, sc, er, ec)
224 }
225 fn record_name(&self, folded_name: &str) {
226 LiveEdgeCollector::record_name(self, folded_name)
227 }
228}
229
230#[derive(Default)]
235pub struct ReadLog {
236 reads: Mutex<Vec<ReadRect>>,
237}
238
239pub type ReadRect = (SheetId, u32, u32, u32, u32);
241
242impl ReadLog {
243 pub fn take(&self) -> Vec<ReadRect> {
244 std::mem::take(&mut self.reads.lock().unwrap())
245 }
246}
247
248impl ReadSink for ReadLog {
249 fn record_scalar(&self, sheet_id: SheetId, row: u32, col: u32) {
250 self.reads
251 .lock()
252 .unwrap()
253 .push((sheet_id, row, col, row, col));
254 }
255 fn record_rect(&self, sheet_id: SheetId, sr: u32, sc: u32, er: u32, ec: u32) {
256 self.reads.lock().unwrap().push((sheet_id, sr, sc, er, ec));
257 }
258 fn record_name(&self, _folded_name: &str) {}
259}
260
261pub struct RecordingContext<'a, R: EvaluationContext, S: ReadSink = LiveEdgeCollector> {
292 engine: &'a Engine<R>,
293 collector: &'a S,
294}
295
296impl<'a, R: EvaluationContext, S: ReadSink> RecordingContext<'a, R, S> {
297 pub fn new(engine: &'a Engine<R>, collector: &'a S) -> Self {
298 Self { engine, collector }
299 }
300
301 fn record_name(&self, raw_name: &str) {
304 let key = self.engine.graph.name_lookup_key(raw_name);
305 self.collector.record_name(&key);
306 }
307
308 fn record_cell_1based(&self, sheet_name: &str, row: u32, col: u32) {
310 if row == 0 || col == 0 {
311 return;
312 }
313 if let Some(sid) = self.engine.sheet_id(sheet_name) {
314 self.collector.record_scalar(sid, row - 1, col - 1);
315 }
316 }
317
318 fn record_view(&self, view: &RangeView<'_>) {
322 if view.is_empty() {
323 return;
324 }
325 if let Some(sid) = self.engine.sheet_id(view.sheet_name()) {
326 self.collector.record_rect(
327 sid,
328 view.start_row() as u32,
329 view.start_col() as u32,
330 view.end_row() as u32,
331 view.end_col() as u32,
332 );
333 }
334 }
335}
336
337impl<'a, R: EvaluationContext, S: ReadSink> ReferenceResolver for RecordingContext<'a, R, S> {
338 fn resolve_cell_reference(
339 &self,
340 sheet: Option<&str>,
341 row: u32,
342 col: u32,
343 ) -> Result<LiteralValue, ExcelError> {
344 if let Some(sheet_name) = sheet {
348 self.record_cell_1based(sheet_name, row, col);
349 }
350 self.engine.resolve_cell_reference(sheet, row, col)
351 }
352}
353
354impl<'a, R: EvaluationContext, S: ReadSink> RangeResolver for RecordingContext<'a, R, S> {
355 fn resolve_range_reference(
356 &self,
357 sheet: Option<&str>,
358 sr: Option<u32>,
359 sc: Option<u32>,
360 er: Option<u32>,
361 ec: Option<u32>,
362 ) -> Result<Box<dyn Range>, ExcelError> {
363 if let Some(sheet_name) = sheet {
366 let reference = ReferenceType::Range {
367 sheet: Some(sheet_name.to_string()),
368 start_row: sr,
369 start_col: sc,
370 end_row: er,
371 end_col: ec,
372 start_row_abs: true,
373 start_col_abs: true,
374 end_row_abs: true,
375 end_col_abs: true,
376 };
377 if let Ok(view) = self.engine.resolve_range_view(&reference, sheet_name) {
378 self.record_view(&view);
379 }
380 }
381 self.engine.resolve_range_reference(sheet, sr, sc, er, ec)
382 }
383}
384
385impl<'a, R: EvaluationContext, S: ReadSink> NamedRangeResolver for RecordingContext<'a, R, S> {
386 fn resolve_named_range_reference(
387 &self,
388 name: &str,
389 ) -> Result<Vec<Vec<LiteralValue>>, ExcelError> {
390 self.record_name(name);
394 self.engine.resolve_named_range_reference(name)
395 }
396}
397
398impl<'a, R: EvaluationContext, S: ReadSink> TableResolver for RecordingContext<'a, R, S> {
399 fn resolve_table_reference(&self, tref: &TableReference) -> Result<Box<dyn Table>, ExcelError> {
400 self.engine.resolve_table_reference(tref)
403 }
404}
405
406impl<'a, R: EvaluationContext, S: ReadSink> SourceResolver for RecordingContext<'a, R, S> {
407 fn source_scalar_version(&self, name: &str) -> Option<u64> {
408 self.engine.source_scalar_version(name)
409 }
410 fn resolve_source_scalar(&self, name: &str) -> Result<LiteralValue, ExcelError> {
411 self.engine.resolve_source_scalar(name)
412 }
413 fn source_table_version(&self, name: &str) -> Option<u64> {
414 self.engine.source_table_version(name)
415 }
416 fn resolve_source_table(&self, name: &str) -> Result<Box<dyn Table>, ExcelError> {
417 self.engine.resolve_source_table(name)
418 }
419}
420
421impl<'a, R: EvaluationContext, S: ReadSink> Resolver for RecordingContext<'a, R, S> {}
422
423impl<'a, R: EvaluationContext, S: ReadSink> FunctionProvider for RecordingContext<'a, R, S> {
424 fn planning_semantic_revision(&self) -> Option<u64> {
425 self.engine.planning_semantic_revision()
426 }
427
428 fn get_function(
429 &self,
430 ns: &str,
431 name: &str,
432 ) -> Option<std::sync::Arc<dyn crate::traits::Function>> {
433 self.engine.get_function(ns, name)
434 }
435
436 fn get_function_for_planning(
437 &self,
438 ns: &str,
439 name: &str,
440 ) -> Option<std::sync::Arc<dyn crate::traits::Function>> {
441 self.engine.get_function_for_planning(ns, name)
442 }
443}
444
445impl<'a, R: EvaluationContext, S: ReadSink> EvaluationContext for RecordingContext<'a, R, S> {
446 fn resolve_range_view<'c>(
449 &'c self,
450 reference: &ReferenceType,
451 current_sheet: &str,
452 ) -> Result<RangeView<'c>, ExcelError> {
453 if let ReferenceType::NamedRange(name) = reference {
458 self.record_name(name);
459 }
460 let view = self.engine.resolve_range_view(reference, current_sheet)?;
461 self.record_view(&view);
462 Ok(view)
463 }
464
465 fn resolve_cell_reference_value(
466 &self,
467 sheet: Option<&str>,
468 row: u32,
469 col: u32,
470 current_sheet: &str,
471 ) -> Result<LiteralValue, ExcelError> {
472 self.record_cell_1based(sheet.unwrap_or(current_sheet), row, col);
473 self.engine
474 .resolve_cell_reference_value(sheet, row, col, current_sheet)
475 }
476
477 fn resolve_cell_format(
478 &self,
479 sheet: Option<&str>,
480 row: u32,
481 col: u32,
482 current_sheet: &str,
483 ) -> Option<crate::format::FormatId> {
484 self.engine
485 .resolve_cell_format(sheet, row, col, current_sheet)
486 }
487
488 fn format_class(
489 &self,
490 format: crate::format::FormatId,
491 ) -> Option<formualizer_common::numfmt::FormatClass> {
492 self.engine.format_class(format)
493 }
494
495 fn record_cell_derived_format(
496 &self,
497 sheet: &str,
498 row: u32,
499 col: u32,
500 format: Option<crate::format::FormatId>,
501 ) {
502 self.engine
503 .record_cell_derived_format(sheet, row, col, format)
504 }
505
506 fn thread_pool(&self) -> Option<&std::sync::Arc<rayon::ThreadPool>> {
509 self.engine.thread_pool()
510 }
511 fn cancellation_token(&self) -> Option<crate::engine::CancelToken> {
512 self.engine.cancellation_token()
513 }
514 fn chunk_hint(&self) -> Option<usize> {
515 self.engine.chunk_hint()
516 }
517 fn locale(&self) -> crate::locale::Locale {
518 self.engine.locale()
519 }
520 fn workbook_sheet_count(&self) -> Option<usize> {
521 self.engine.workbook_sheet_count()
522 }
523 fn sheet_index_by_name(&self, sheet: &str) -> Option<usize> {
524 self.engine.sheet_index_by_name(sheet)
525 }
526 fn current_sheet_index(&self, current_sheet: &str) -> Option<usize> {
527 self.engine.current_sheet_index(current_sheet)
528 }
529 fn inspect_reference(
530 &self,
531 reference: &ReferenceType,
532 current_sheet: &str,
533 ) -> Result<Option<ReferenceInfo>, ExcelError> {
534 self.engine.inspect_reference(reference, current_sheet)
535 }
536 fn formula_text_at_cell(&self, cell: CellRef) -> Result<Option<String>, ExcelError> {
537 self.engine.formula_text_at_cell(cell)
538 }
539 fn resolve_spill_reference(
540 &self,
541 anchor: &ReferenceType,
542 current_sheet: &str,
543 ) -> Result<ReferenceType, ExcelError> {
544 match anchor {
548 ReferenceType::Cell {
549 sheet, row, col, ..
550 } => self.record_cell_1based(sheet.as_deref().unwrap_or(current_sheet), *row, *col),
551 ReferenceType::NamedRange(name) => self.record_name(name),
552 _ => {}
553 }
554 self.engine.resolve_spill_reference(anchor, current_sheet)
555 }
556 fn clock(&self) -> &dyn crate::timezone::ClockProvider {
557 self.engine.clock()
558 }
559 fn timezone(&self) -> &crate::timezone::TimeZoneSpec {
560 self.engine.timezone()
561 }
562 fn volatile_level(&self) -> crate::traits::VolatileLevel {
563 self.engine.volatile_level()
564 }
565 fn workbook_seed(&self) -> u64 {
566 self.engine.workbook_seed()
567 }
568 fn recalc_epoch(&self) -> u64 {
569 self.engine.recalc_epoch()
570 }
571 fn used_rows_for_columns(
572 &self,
573 sheet: &str,
574 start_col: u32,
575 end_col: u32,
576 ) -> Option<(u32, u32)> {
577 self.engine.used_rows_for_columns(sheet, start_col, end_col)
578 }
579 fn used_cols_for_rows(&self, sheet: &str, start_row: u32, end_row: u32) -> Option<(u32, u32)> {
580 self.engine.used_cols_for_rows(sheet, start_row, end_row)
581 }
582 fn sheet_bounds(&self, sheet: &str) -> Option<(u32, u32)> {
583 self.engine.sheet_bounds(sheet)
584 }
585 fn data_snapshot_id(&self) -> u64 {
586 self.engine.data_snapshot_id()
587 }
588 fn backend_caps(&self) -> crate::traits::BackendCaps {
589 self.engine.backend_caps()
590 }
591 fn date_system(&self) -> crate::engine::DateSystem {
592 self.engine.date_system()
593 }
594 fn build_lookup_index(
595 &self,
596 view: &RangeView<'_>,
597 axis: crate::engine::lookup_index_cache::LookupAxis,
598 ) -> Option<std::sync::Arc<crate::engine::lookup_index_cache::LookupIndex>> {
599 self.engine.build_lookup_index(view, axis)
600 }
601 fn build_criteria_mask(
602 &self,
603 view: &RangeView<'_>,
604 col_in_view: usize,
605 pred: &crate::args::CriteriaPredicate,
606 ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
607 self.engine.build_criteria_mask(view, col_in_view, pred)
608 }
609 fn build_row_visibility_mask(
610 &self,
611 view: &RangeView<'_>,
612 mode: crate::engine::row_visibility::VisibilityMaskMode,
613 ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
614 self.engine.build_row_visibility_mask(view, mode)
615 }
616 fn nested_subtotal_cells(
617 &self,
618 view: &RangeView<'_>,
619 include_aggregate: bool,
620 ) -> Option<Vec<(usize, usize, usize)>> {
621 self.engine.nested_subtotal_cells(view, include_aggregate)
622 }
623}