1use super::*;
2use formualizer_parse::parser::ASTNode;
3
4type ExtractDependenciesResult = Result<
6 (
7 Vec<VertexId>,
8 Vec<SharedRangeRef<'static>>,
9 Vec<CellRef>,
10 Vec<VertexId>,
11 ),
12 ExcelError,
13>;
14
15type ExtractDependenciesWithPendingNamesResult = Result<
16 (
17 Vec<VertexId>,
18 Vec<SharedRangeRef<'static>>,
19 Vec<CellRef>,
20 Vec<VertexId>,
21 Vec<String>,
22 ),
23 ExcelError,
24>;
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27enum UnresolvedNamePolicy {
28 Error,
29 Collect,
30}
31
32struct GraphReferenceContext<'a> {
33 graph: &'a mut DependencyGraph,
34 current_sheet_id: SheetId,
35 dependencies: &'a mut FxHashSet<VertexId>,
36 range_dependencies: &'a mut Vec<SharedRangeRef<'static>>,
37 created_placeholders: &'a mut Vec<CellRef>,
38 named_dependencies: &'a mut Vec<VertexId>,
39 unresolved_names: &'a mut FxHashSet<String>,
40 unresolved_name_policy: UnresolvedNamePolicy,
41}
42
43fn graph_no_local_bindings(
44 _: &GraphReferenceContext<'_>,
45 _: &str,
46 _: usize,
47) -> crate::engine::refs::LocalBindingStyle {
48 crate::engine::refs::LocalBindingStyle::None
49}
50
51fn graph_data_store<'context>(
52 context: &'context GraphReferenceContext<'_>,
53) -> &'context super::super::arena::DataStore {
54 &context.graph.data_store
55}
56
57fn graph_sheet_registry<'context>(
58 context: &'context GraphReferenceContext<'_>,
59) -> &'context super::super::sheet_registry::SheetRegistry {
60 &context.graph.sheet_reg
61}
62
63fn defer_unbound(
67 context: &mut GraphReferenceContext<'_>,
68 kind: &str,
69 name: &str,
70 error: ExcelError,
71) -> Result<(), ExcelError> {
72 let tombstone = kind == "sheet" && DependencyGraph::is_tombstone_sheet(name);
73 if !name.is_empty()
74 && !tombstone
75 && context.unresolved_name_policy == UnresolvedNamePolicy::Collect
76 && context.graph.config.preparation_policy == crate::engine::PreparationPolicy::BestEffort
77 {
78 context
79 .unresolved_names
80 .insert(DependencyGraph::unbound_symbol_key(kind, name));
81 Ok(())
82 } else {
83 Err(error)
84 }
85}
86
87fn push_cell_dependency(context: &mut GraphReferenceContext<'_>, address: CellRef) {
91 match context.graph.dep_vertex(&address) {
92 Some(vertex) => {
93 context.dependencies.insert(vertex);
94 }
95 None => {
96 if !context
97 .created_placeholders
98 .iter()
99 .any(|c| super::same_cell(c, &address))
100 {
101 context.created_placeholders.push(address);
102 }
103 }
104 }
105}
106
107fn collect_graph_reference(
108 context: &mut GraphReferenceContext<'_>,
109 reference: crate::engine::refs::SemanticReference<'_>,
110) -> Result<(), ExcelError> {
111 use crate::engine::refs::SemanticReference;
112
113 match reference {
114 SemanticReference::ExternalSource(external) => match external.kind {
115 formualizer_parse::parser::ExternalRefKind::Cell { .. } => {
116 let name = external.raw.as_str();
117 if let Some(source) = context.graph.resolve_source_scalar_entry(name) {
118 context.dependencies.insert(source.vertex);
119 Ok(())
120 } else {
121 Err(ExcelError::new(ExcelErrorKind::Name)
122 .with_message(format!("Undefined name: {name}")))
123 }
124 }
125 formualizer_parse::parser::ExternalRefKind::Range { .. } => {
126 let name = external.raw.as_str();
127 if let Some(source) = context.graph.resolve_source_table_entry(name) {
128 context.dependencies.insert(source.vertex);
129 Ok(())
130 } else if crate::engine::refs::unbound_external_range_defers(&external.kind)
131 && context.unresolved_name_policy == UnresolvedNamePolicy::Collect
132 {
133 context.unresolved_names.insert(name.to_string());
134 Ok(())
135 } else {
136 Err(ExcelError::new(ExcelErrorKind::Name)
137 .with_message(format!("Undefined table: {name}")))
138 }
139 }
140 },
141 SemanticReference::Cell(cell) => {
142 let sheet_id = match cell.sheet.name() {
143 Some(name) => match context.graph.resolve_existing_sheet_id(name) {
144 Ok(id) => id,
145 Err(e) => return defer_unbound(context, "sheet", name, e),
146 },
147 None => context.current_sheet_id,
148 };
149 let address = CellRef::new(sheet_id, Coord::from_excel(cell.row, cell.col, true, true));
150 push_cell_dependency(context, address);
151 Ok(())
152 }
153 SemanticReference::OpenRange(range) => {
154 let sheet_name = range.sheet.name();
155 if let Some(SharedRef::Range(range)) = range.original.to_sheet_ref_lossy() {
156 let owned = range.into_owned();
157 let sheet_id = match context
159 .graph
160 .sheet_reg()
161 .resolve_locator(&owned.sheet, context.current_sheet_id)
162 {
163 Ok(id) => id,
164 Err(e) => return defer_unbound(context, "sheet", sheet_name.unwrap_or(""), e),
165 };
166 context.range_dependencies.push(SharedRangeRef {
167 sheet: SharedSheetLocator::Id(sheet_id),
168 start_row: owned.start_row,
169 start_col: owned.start_col,
170 end_row: owned.end_row,
171 end_col: owned.end_col,
172 });
173 }
174 Ok(())
175 }
176 SemanticReference::FiniteRange(range) => {
177 let (sr, sc, er, ec) = range
178 .finite_bounds()
179 .expect("finite reference must have all bounds");
180 if range.is_reversed() {
181 return Err(ExcelError::new(ExcelErrorKind::Ref));
182 }
183 let area = range.saturating_area().expect("finite area");
184
185 if area <= context.graph.config.range_expansion_limit as u64 {
188 let sheet_id = match range.sheet.name() {
189 Some(name) => match context.graph.resolve_existing_sheet_id(name) {
190 Ok(id) => id,
191 Err(e) => return defer_unbound(context, "sheet", name, e),
192 },
193 None => context.current_sheet_id,
194 };
195 for row in sr..=er {
196 for col in sc..=ec {
197 let address =
198 CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
199 push_cell_dependency(context, address);
200 }
201 }
202 } else if let Some(SharedRef::Range(shared)) = range.original.to_sheet_ref_lossy() {
203 let owned = shared.into_owned();
204 let sheet_id = match context
206 .graph
207 .sheet_reg()
208 .resolve_locator(&owned.sheet, context.current_sheet_id)
209 {
210 Ok(id) => id,
211 Err(e) => {
212 return defer_unbound(
213 context,
214 "sheet",
215 range.sheet.name().unwrap_or(""),
216 e,
217 );
218 }
219 };
220 context.range_dependencies.push(SharedRangeRef {
221 sheet: SharedSheetLocator::Id(sheet_id),
222 start_row: owned.start_row,
223 start_col: owned.start_col,
224 end_row: owned.end_row,
225 end_col: owned.end_col,
226 });
227 }
228 Ok(())
229 }
230 SemanticReference::Name(name) => {
231 if let Some(named_range) = context
232 .graph
233 .resolve_name_entry(name, context.current_sheet_id)
234 {
235 context.dependencies.insert(named_range.vertex);
236 context.named_dependencies.push(named_range.vertex);
237 } else if let Some(source) = context.graph.resolve_source_scalar_entry(name) {
238 context.dependencies.insert(source.vertex);
239 } else {
240 match context.unresolved_name_policy {
241 UnresolvedNamePolicy::Error => {
242 return Err(ExcelError::new(ExcelErrorKind::Name)
243 .with_message(format!("Undefined name: {name}")));
244 }
245 UnresolvedNamePolicy::Collect => {
246 context.unresolved_names.insert(name.to_string());
247 }
248 }
249 }
250 Ok(())
251 }
252 SemanticReference::Table(table_reference) => {
253 if let Some(table) = context.graph.resolve_table_entry(&table_reference.name) {
254 context.dependencies.insert(table.vertex);
255 } else if let Some(source) = context
256 .graph
257 .resolve_source_table_entry(&table_reference.name)
258 {
259 context.dependencies.insert(source.vertex);
260 } else {
261 return defer_unbound(
262 context,
263 "table",
264 &table_reference.name,
265 ExcelError::new(ExcelErrorKind::Name)
266 .with_message(format!("Undefined table: {}", table_reference.name)),
267 );
268 }
269 Ok(())
270 }
271 SemanticReference::ThreeDimensional(_) | SemanticReference::Unsupported(_) => Ok(()),
272 }
273}
274impl DependencyGraph {
275 pub(super) fn extract_dependencies(
278 &mut self,
279 ast: &ASTNode,
280 current_sheet_id: SheetId,
281 ) -> ExtractDependenciesResult {
282 let (dependencies, ranges, placeholders, named_dependencies, _pending_names) =
283 self.extract_dependencies_inner(ast, current_sheet_id, UnresolvedNamePolicy::Error)?;
284 Ok((dependencies, ranges, placeholders, named_dependencies))
285 }
286
287 pub(super) fn extract_dependencies_with_pending_names(
288 &mut self,
289 ast: &ASTNode,
290 current_sheet_id: SheetId,
291 ) -> ExtractDependenciesWithPendingNamesResult {
292 self.extract_dependencies_inner(ast, current_sheet_id, UnresolvedNamePolicy::Collect)
293 }
294
295 pub(super) fn extract_dependencies_arena(
296 &mut self,
297 ast_id: AstNodeId,
298 current_sheet_id: SheetId,
299 ) -> ExtractDependenciesResult {
300 let (dependencies, ranges, placeholders, named_dependencies, _pending_names) = self
301 .extract_dependencies_inner_arena(
302 ast_id,
303 current_sheet_id,
304 UnresolvedNamePolicy::Error,
305 )?;
306 Ok((dependencies, ranges, placeholders, named_dependencies))
307 }
308
309 pub(super) fn extract_dependencies_with_pending_names_arena(
310 &mut self,
311 ast_id: AstNodeId,
312 current_sheet_id: SheetId,
313 ) -> ExtractDependenciesWithPendingNamesResult {
314 self.extract_dependencies_inner_arena(
315 ast_id,
316 current_sheet_id,
317 UnresolvedNamePolicy::Collect,
318 )
319 }
320
321 fn extract_dependencies_inner_arena(
322 &mut self,
323 ast_id: AstNodeId,
324 current_sheet_id: SheetId,
325 unresolved_name_policy: UnresolvedNamePolicy,
326 ) -> ExtractDependenciesWithPendingNamesResult {
327 let mut dependencies = FxHashSet::default();
328 let mut range_dependencies: Vec<SharedRangeRef<'static>> = Vec::new();
329 let mut created_placeholders = Vec::new();
330 let mut named_dependencies = Vec::new();
331 let mut unresolved_names = FxHashSet::default();
332 let mut context = GraphReferenceContext {
333 graph: self,
334 current_sheet_id,
335 dependencies: &mut dependencies,
336 range_dependencies: &mut range_dependencies,
337 created_placeholders: &mut created_placeholders,
338 named_dependencies: &mut named_dependencies,
339 unresolved_names: &mut unresolved_names,
340 unresolved_name_policy,
341 };
342 crate::engine::refs::visit_arena_references(
343 ast_id,
344 &mut context,
345 graph_data_store,
346 graph_sheet_registry,
347 collect_graph_reference,
348 )?;
349
350 let mut deduped_ranges = Vec::new();
352 for range_ref in range_dependencies {
353 if !deduped_ranges.contains(&range_ref) {
354 deduped_ranges.push(range_ref);
355 }
356 }
357
358 named_dependencies.sort_unstable_by_key(|v| v.0);
359 named_dependencies.dedup_by_key(|v| v.0);
360
361 let mut unresolved_names: Vec<String> = unresolved_names.into_iter().collect();
362 unresolved_names.sort();
363
364 Ok((
365 dependencies.into_iter().collect(),
366 deduped_ranges,
367 created_placeholders,
368 named_dependencies,
369 unresolved_names,
370 ))
371 }
372
373 fn extract_dependencies_inner(
374 &mut self,
375 ast: &ASTNode,
376 current_sheet_id: SheetId,
377 unresolved_name_policy: UnresolvedNamePolicy,
378 ) -> ExtractDependenciesWithPendingNamesResult {
379 let mut dependencies = FxHashSet::default();
380 let mut range_dependencies: Vec<SharedRangeRef<'static>> = Vec::new();
381 let mut created_placeholders = Vec::new();
382 let mut named_dependencies = Vec::new();
383 let mut unresolved_names = FxHashSet::default();
384 let mut context = GraphReferenceContext {
385 graph: self,
386 current_sheet_id,
387 dependencies: &mut dependencies,
388 range_dependencies: &mut range_dependencies,
389 created_placeholders: &mut created_placeholders,
390 named_dependencies: &mut named_dependencies,
391 unresolved_names: &mut unresolved_names,
392 unresolved_name_policy,
393 };
394 crate::engine::refs::visit_tree_references(
395 ast,
396 &mut context,
397 graph_no_local_bindings,
398 collect_graph_reference,
399 )?;
400
401 let mut deduped_ranges = Vec::new();
403 for range_ref in range_dependencies {
404 if !deduped_ranges.contains(&range_ref) {
405 deduped_ranges.push(range_ref);
406 }
407 }
408
409 named_dependencies.sort_unstable_by_key(|v| v.0);
410 named_dependencies.dedup_by_key(|v| v.0);
411
412 let mut unresolved_names: Vec<String> = unresolved_names.into_iter().collect();
413 unresolved_names.sort();
414
415 Ok((
416 dependencies.into_iter().collect(),
417 deduped_ranges,
418 created_placeholders,
419 named_dependencies,
420 unresolved_names,
421 ))
422 }
423
424 pub(super) fn is_ast_volatile(&self, ast: &ASTNode) -> bool {
425 if ast.contains_volatile() {
426 return true;
427 }
428
429 use formualizer_parse::parser::ASTNodeType;
430
431 match &ast.node_type {
432 ASTNodeType::Function { name, args } => {
433 if let Some(func) = crate::function_registry::get("", name)
434 && func.caps().contains(crate::function::FnCaps::VOLATILE)
435 {
436 return true;
437 }
438 args.iter().any(|arg| self.is_ast_volatile(arg))
439 }
440 ASTNodeType::BinaryOp { left, right, .. } => {
441 self.is_ast_volatile(left) || self.is_ast_volatile(right)
442 }
443 ASTNodeType::UnaryOp { expr, .. } => self.is_ast_volatile(expr),
444 ASTNodeType::Array(rows) => rows
445 .iter()
446 .any(|row| row.iter().any(|cell| self.is_ast_volatile(cell))),
447 ASTNodeType::Call { callee, args } => {
448 self.is_ast_volatile(callee) || args.iter().any(|a| self.is_ast_volatile(a))
449 }
450 _ => false,
451 }
452 }
453
454 pub fn is_ast_dynamic(&self, ast: &ASTNode) -> bool {
455 use formualizer_parse::parser::ASTNodeType;
456
457 match &ast.node_type {
458 ASTNodeType::Function { name, args } => {
459 if let Some(func) = crate::function_registry::get("", name)
460 && func
461 .caps()
462 .contains(crate::function::FnCaps::DYNAMIC_DEPENDENCY)
463 {
464 return true;
465 }
466 args.iter().any(|arg| self.is_ast_dynamic(arg))
467 }
468 ASTNodeType::BinaryOp { left, right, .. } => {
469 self.is_ast_dynamic(left) || self.is_ast_dynamic(right)
470 }
471 ASTNodeType::UnaryOp { expr, .. } => self.is_ast_dynamic(expr),
472 ASTNodeType::Array(rows) => rows
473 .iter()
474 .any(|row| row.iter().any(|cell| self.is_ast_dynamic(cell))),
475 ASTNodeType::Call { callee, args } => {
476 self.is_ast_dynamic(callee) || args.iter().any(|a| self.is_ast_dynamic(a))
477 }
478 _ => false,
479 }
480 }
481}