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 struct RecordingContext<'a, R: EvaluationContext> {
240 engine: &'a Engine<R>,
241 collector: &'a LiveEdgeCollector,
242}
243
244impl<'a, R: EvaluationContext> RecordingContext<'a, R> {
245 pub fn new(engine: &'a Engine<R>, collector: &'a LiveEdgeCollector) -> Self {
246 Self { engine, collector }
247 }
248
249 fn record_name(&self, raw_name: &str) {
252 let key = self.engine.graph.name_lookup_key(raw_name);
253 self.collector.record_name(&key);
254 }
255
256 fn record_cell_1based(&self, sheet_name: &str, row: u32, col: u32) {
258 if row == 0 || col == 0 {
259 return;
260 }
261 if let Some(sid) = self.engine.sheet_id(sheet_name) {
262 self.collector.record_scalar(sid, row - 1, col - 1);
263 }
264 }
265
266 fn record_view(&self, view: &RangeView<'_>) {
270 if view.is_empty() {
271 return;
272 }
273 if let Some(sid) = self.engine.sheet_id(view.sheet_name()) {
274 self.collector.record_rect(
275 sid,
276 view.start_row() as u32,
277 view.start_col() as u32,
278 view.end_row() as u32,
279 view.end_col() as u32,
280 );
281 }
282 }
283}
284
285impl<'a, R: EvaluationContext> ReferenceResolver for RecordingContext<'a, R> {
286 fn resolve_cell_reference(
287 &self,
288 sheet: Option<&str>,
289 row: u32,
290 col: u32,
291 ) -> Result<LiteralValue, ExcelError> {
292 if let Some(sheet_name) = sheet {
296 self.record_cell_1based(sheet_name, row, col);
297 }
298 self.engine.resolve_cell_reference(sheet, row, col)
299 }
300}
301
302impl<'a, R: EvaluationContext> RangeResolver for RecordingContext<'a, R> {
303 fn resolve_range_reference(
304 &self,
305 sheet: Option<&str>,
306 sr: Option<u32>,
307 sc: Option<u32>,
308 er: Option<u32>,
309 ec: Option<u32>,
310 ) -> Result<Box<dyn Range>, ExcelError> {
311 if let Some(sheet_name) = sheet {
314 let reference = ReferenceType::Range {
315 sheet: Some(sheet_name.to_string()),
316 start_row: sr,
317 start_col: sc,
318 end_row: er,
319 end_col: ec,
320 start_row_abs: true,
321 start_col_abs: true,
322 end_row_abs: true,
323 end_col_abs: true,
324 };
325 if let Ok(view) = self.engine.resolve_range_view(&reference, sheet_name) {
326 self.record_view(&view);
327 }
328 }
329 self.engine.resolve_range_reference(sheet, sr, sc, er, ec)
330 }
331}
332
333impl<'a, R: EvaluationContext> NamedRangeResolver for RecordingContext<'a, R> {
334 fn resolve_named_range_reference(
335 &self,
336 name: &str,
337 ) -> Result<Vec<Vec<LiteralValue>>, ExcelError> {
338 self.record_name(name);
342 self.engine.resolve_named_range_reference(name)
343 }
344}
345
346impl<'a, R: EvaluationContext> TableResolver for RecordingContext<'a, R> {
347 fn resolve_table_reference(&self, tref: &TableReference) -> Result<Box<dyn Table>, ExcelError> {
348 self.engine.resolve_table_reference(tref)
351 }
352}
353
354impl<'a, R: EvaluationContext> SourceResolver for RecordingContext<'a, R> {
355 fn source_scalar_version(&self, name: &str) -> Option<u64> {
356 self.engine.source_scalar_version(name)
357 }
358 fn resolve_source_scalar(&self, name: &str) -> Result<LiteralValue, ExcelError> {
359 self.engine.resolve_source_scalar(name)
360 }
361 fn source_table_version(&self, name: &str) -> Option<u64> {
362 self.engine.source_table_version(name)
363 }
364 fn resolve_source_table(&self, name: &str) -> Result<Box<dyn Table>, ExcelError> {
365 self.engine.resolve_source_table(name)
366 }
367}
368
369impl<'a, R: EvaluationContext> Resolver for RecordingContext<'a, R> {}
370
371impl<'a, R: EvaluationContext> FunctionProvider for RecordingContext<'a, R> {
372 fn get_function(
373 &self,
374 ns: &str,
375 name: &str,
376 ) -> Option<std::sync::Arc<dyn crate::traits::Function>> {
377 self.engine.get_function(ns, name)
378 }
379}
380
381impl<'a, R: EvaluationContext> EvaluationContext for RecordingContext<'a, R> {
382 fn resolve_range_view<'c>(
385 &'c self,
386 reference: &ReferenceType,
387 current_sheet: &str,
388 ) -> Result<RangeView<'c>, ExcelError> {
389 if let ReferenceType::NamedRange(name) = reference {
394 self.record_name(name);
395 }
396 let view = self.engine.resolve_range_view(reference, current_sheet)?;
397 self.record_view(&view);
398 Ok(view)
399 }
400
401 fn resolve_cell_reference_value(
402 &self,
403 sheet: Option<&str>,
404 row: u32,
405 col: u32,
406 current_sheet: &str,
407 ) -> Result<LiteralValue, ExcelError> {
408 self.record_cell_1based(sheet.unwrap_or(current_sheet), row, col);
409 self.engine
410 .resolve_cell_reference_value(sheet, row, col, current_sheet)
411 }
412
413 fn thread_pool(&self) -> Option<&std::sync::Arc<rayon::ThreadPool>> {
416 self.engine.thread_pool()
417 }
418 fn cancellation_token(&self) -> Option<std::sync::Arc<std::sync::atomic::AtomicBool>> {
419 self.engine.cancellation_token()
420 }
421 fn chunk_hint(&self) -> Option<usize> {
422 self.engine.chunk_hint()
423 }
424 fn locale(&self) -> crate::locale::Locale {
425 self.engine.locale()
426 }
427 fn workbook_sheet_count(&self) -> Option<usize> {
428 self.engine.workbook_sheet_count()
429 }
430 fn sheet_index_by_name(&self, sheet: &str) -> Option<usize> {
431 self.engine.sheet_index_by_name(sheet)
432 }
433 fn current_sheet_index(&self, current_sheet: &str) -> Option<usize> {
434 self.engine.current_sheet_index(current_sheet)
435 }
436 fn inspect_reference(
437 &self,
438 reference: &ReferenceType,
439 current_sheet: &str,
440 ) -> Result<Option<ReferenceInfo>, ExcelError> {
441 self.engine.inspect_reference(reference, current_sheet)
442 }
443 fn formula_text_at_cell(&self, cell: CellRef) -> Result<Option<String>, ExcelError> {
444 self.engine.formula_text_at_cell(cell)
445 }
446 fn clock(&self) -> &dyn crate::timezone::ClockProvider {
447 self.engine.clock()
448 }
449 fn timezone(&self) -> &crate::timezone::TimeZoneSpec {
450 self.engine.timezone()
451 }
452 fn volatile_level(&self) -> crate::traits::VolatileLevel {
453 self.engine.volatile_level()
454 }
455 fn workbook_seed(&self) -> u64 {
456 self.engine.workbook_seed()
457 }
458 fn recalc_epoch(&self) -> u64 {
459 self.engine.recalc_epoch()
460 }
461 fn used_rows_for_columns(
462 &self,
463 sheet: &str,
464 start_col: u32,
465 end_col: u32,
466 ) -> Option<(u32, u32)> {
467 self.engine.used_rows_for_columns(sheet, start_col, end_col)
468 }
469 fn used_cols_for_rows(&self, sheet: &str, start_row: u32, end_row: u32) -> Option<(u32, u32)> {
470 self.engine.used_cols_for_rows(sheet, start_row, end_row)
471 }
472 fn sheet_bounds(&self, sheet: &str) -> Option<(u32, u32)> {
473 self.engine.sheet_bounds(sheet)
474 }
475 fn data_snapshot_id(&self) -> u64 {
476 self.engine.data_snapshot_id()
477 }
478 fn backend_caps(&self) -> crate::traits::BackendCaps {
479 self.engine.backend_caps()
480 }
481 fn date_system(&self) -> crate::engine::DateSystem {
482 self.engine.date_system()
483 }
484 fn build_lookup_index(
485 &self,
486 view: &RangeView<'_>,
487 axis: crate::engine::lookup_index_cache::LookupAxis,
488 ) -> Option<std::sync::Arc<crate::engine::lookup_index_cache::LookupIndex>> {
489 self.engine.build_lookup_index(view, axis)
490 }
491 fn build_criteria_mask(
492 &self,
493 view: &RangeView<'_>,
494 col_in_view: usize,
495 pred: &crate::args::CriteriaPredicate,
496 ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
497 self.engine.build_criteria_mask(view, col_in_view, pred)
498 }
499 fn build_row_visibility_mask(
500 &self,
501 view: &RangeView<'_>,
502 mode: crate::engine::row_visibility::VisibilityMaskMode,
503 ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
504 self.engine.build_row_visibility_mask(view, mode)
505 }
506}