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 collect_graph_reference(
64 context: &mut GraphReferenceContext<'_>,
65 reference: crate::engine::refs::SemanticReference<'_>,
66) -> Result<(), ExcelError> {
67 use crate::engine::refs::SemanticReference;
68
69 match reference {
70 SemanticReference::ExternalSource(external) => match external.kind {
71 formualizer_parse::parser::ExternalRefKind::Cell { .. } => {
72 let name = external.raw.as_str();
73 if let Some(source) = context.graph.resolve_source_scalar_entry(name) {
74 context.dependencies.insert(source.vertex);
75 Ok(())
76 } else {
77 Err(ExcelError::new(ExcelErrorKind::Name)
78 .with_message(format!("Undefined name: {name}")))
79 }
80 }
81 formualizer_parse::parser::ExternalRefKind::Range { .. } => {
82 let name = external.raw.as_str();
83 if let Some(source) = context.graph.resolve_source_table_entry(name) {
84 context.dependencies.insert(source.vertex);
85 Ok(())
86 } else {
87 Err(ExcelError::new(ExcelErrorKind::Name)
88 .with_message(format!("Undefined table: {name}")))
89 }
90 }
91 },
92 SemanticReference::Cell(cell) => {
93 let sheet_id = match cell.sheet.name() {
94 Some(name) => context.graph.resolve_existing_sheet_id(name)?,
95 None => context.current_sheet_id,
96 };
97 let address = CellRef::new(sheet_id, Coord::from_excel(cell.row, cell.col, true, true));
98 let vertex = context
99 .graph
100 .get_or_create_vertex(&address, context.created_placeholders);
101 context.dependencies.insert(vertex);
102 Ok(())
103 }
104 SemanticReference::OpenRange(range) => {
105 if let Some(SharedRef::Range(range)) = range.original.to_sheet_ref_lossy() {
106 let owned = range.into_owned();
107 let sheet_id = context
109 .graph
110 .sheet_reg()
111 .resolve_locator(&owned.sheet, context.current_sheet_id)?;
112 context.range_dependencies.push(SharedRangeRef {
113 sheet: SharedSheetLocator::Id(sheet_id),
114 start_row: owned.start_row,
115 start_col: owned.start_col,
116 end_row: owned.end_row,
117 end_col: owned.end_col,
118 });
119 }
120 Ok(())
121 }
122 SemanticReference::FiniteRange(range) => {
123 let (sr, sc, er, ec) = range
124 .finite_bounds()
125 .expect("finite reference must have all bounds");
126 if range.is_reversed() {
127 return Err(ExcelError::new(ExcelErrorKind::Ref));
128 }
129 let area = range.saturating_area().expect("finite area");
130
131 if area <= context.graph.config.range_expansion_limit as u64 {
134 let sheet_id = match range.sheet.name() {
135 Some(name) => context.graph.resolve_existing_sheet_id(name)?,
136 None => context.current_sheet_id,
137 };
138 for row in sr..=er {
139 for col in sc..=ec {
140 let address =
141 CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
142 let vertex = context
143 .graph
144 .get_or_create_vertex(&address, context.created_placeholders);
145 context.dependencies.insert(vertex);
146 }
147 }
148 } else if let Some(SharedRef::Range(range)) = range.original.to_sheet_ref_lossy() {
149 let owned = range.into_owned();
150 let sheet_id = context
152 .graph
153 .sheet_reg()
154 .resolve_locator(&owned.sheet, context.current_sheet_id)?;
155 context.range_dependencies.push(SharedRangeRef {
156 sheet: SharedSheetLocator::Id(sheet_id),
157 start_row: owned.start_row,
158 start_col: owned.start_col,
159 end_row: owned.end_row,
160 end_col: owned.end_col,
161 });
162 }
163 Ok(())
164 }
165 SemanticReference::Name(name) => {
166 if let Some(named_range) = context
167 .graph
168 .resolve_name_entry(name, context.current_sheet_id)
169 {
170 context.dependencies.insert(named_range.vertex);
171 context.named_dependencies.push(named_range.vertex);
172 } else if let Some(source) = context.graph.resolve_source_scalar_entry(name) {
173 context.dependencies.insert(source.vertex);
174 } else {
175 match context.unresolved_name_policy {
176 UnresolvedNamePolicy::Error => {
177 return Err(ExcelError::new(ExcelErrorKind::Name)
178 .with_message(format!("Undefined name: {name}")));
179 }
180 UnresolvedNamePolicy::Collect => {
181 context.unresolved_names.insert(name.to_string());
182 }
183 }
184 }
185 Ok(())
186 }
187 SemanticReference::Table(table_reference) => {
188 if let Some(table) = context.graph.resolve_table_entry(&table_reference.name) {
189 context.dependencies.insert(table.vertex);
190 } else if let Some(source) = context
191 .graph
192 .resolve_source_table_entry(&table_reference.name)
193 {
194 context.dependencies.insert(source.vertex);
195 } else {
196 return Err(ExcelError::new(ExcelErrorKind::Name)
197 .with_message(format!("Undefined table: {}", table_reference.name)));
198 }
199 Ok(())
200 }
201 SemanticReference::ThreeDimensional(_) | SemanticReference::Unsupported(_) => Ok(()),
202 }
203}
204impl DependencyGraph {
205 pub(super) fn extract_dependencies(
208 &mut self,
209 ast: &ASTNode,
210 current_sheet_id: SheetId,
211 ) -> ExtractDependenciesResult {
212 let (dependencies, ranges, placeholders, named_dependencies, _pending_names) =
213 self.extract_dependencies_inner(ast, current_sheet_id, UnresolvedNamePolicy::Error)?;
214 Ok((dependencies, ranges, placeholders, named_dependencies))
215 }
216
217 pub(super) fn extract_dependencies_with_pending_names(
218 &mut self,
219 ast: &ASTNode,
220 current_sheet_id: SheetId,
221 ) -> ExtractDependenciesWithPendingNamesResult {
222 self.extract_dependencies_inner(ast, current_sheet_id, UnresolvedNamePolicy::Collect)
223 }
224
225 pub(super) fn extract_dependencies_arena(
226 &mut self,
227 ast_id: AstNodeId,
228 current_sheet_id: SheetId,
229 ) -> ExtractDependenciesResult {
230 let (dependencies, ranges, placeholders, named_dependencies, _pending_names) = self
231 .extract_dependencies_inner_arena(
232 ast_id,
233 current_sheet_id,
234 UnresolvedNamePolicy::Error,
235 )?;
236 Ok((dependencies, ranges, placeholders, named_dependencies))
237 }
238
239 pub(super) fn extract_dependencies_with_pending_names_arena(
240 &mut self,
241 ast_id: AstNodeId,
242 current_sheet_id: SheetId,
243 ) -> ExtractDependenciesWithPendingNamesResult {
244 self.extract_dependencies_inner_arena(
245 ast_id,
246 current_sheet_id,
247 UnresolvedNamePolicy::Collect,
248 )
249 }
250
251 fn extract_dependencies_inner_arena(
252 &mut self,
253 ast_id: AstNodeId,
254 current_sheet_id: SheetId,
255 unresolved_name_policy: UnresolvedNamePolicy,
256 ) -> ExtractDependenciesWithPendingNamesResult {
257 let mut dependencies = FxHashSet::default();
258 let mut range_dependencies: Vec<SharedRangeRef<'static>> = Vec::new();
259 let mut created_placeholders = Vec::new();
260 let mut named_dependencies = Vec::new();
261 let mut unresolved_names = FxHashSet::default();
262 let mut context = GraphReferenceContext {
263 graph: self,
264 current_sheet_id,
265 dependencies: &mut dependencies,
266 range_dependencies: &mut range_dependencies,
267 created_placeholders: &mut created_placeholders,
268 named_dependencies: &mut named_dependencies,
269 unresolved_names: &mut unresolved_names,
270 unresolved_name_policy,
271 };
272 crate::engine::refs::visit_arena_references(
273 ast_id,
274 &mut context,
275 graph_data_store,
276 graph_sheet_registry,
277 collect_graph_reference,
278 )?;
279
280 let mut deduped_ranges = Vec::new();
282 for range_ref in range_dependencies {
283 if !deduped_ranges.contains(&range_ref) {
284 deduped_ranges.push(range_ref);
285 }
286 }
287
288 named_dependencies.sort_unstable_by_key(|v| v.0);
289 named_dependencies.dedup_by_key(|v| v.0);
290
291 let mut unresolved_names: Vec<String> = unresolved_names.into_iter().collect();
292 unresolved_names.sort();
293
294 Ok((
295 dependencies.into_iter().collect(),
296 deduped_ranges,
297 created_placeholders,
298 named_dependencies,
299 unresolved_names,
300 ))
301 }
302
303 fn extract_dependencies_inner(
304 &mut self,
305 ast: &ASTNode,
306 current_sheet_id: SheetId,
307 unresolved_name_policy: UnresolvedNamePolicy,
308 ) -> ExtractDependenciesWithPendingNamesResult {
309 let mut dependencies = FxHashSet::default();
310 let mut range_dependencies: Vec<SharedRangeRef<'static>> = Vec::new();
311 let mut created_placeholders = Vec::new();
312 let mut named_dependencies = Vec::new();
313 let mut unresolved_names = FxHashSet::default();
314 let mut context = GraphReferenceContext {
315 graph: self,
316 current_sheet_id,
317 dependencies: &mut dependencies,
318 range_dependencies: &mut range_dependencies,
319 created_placeholders: &mut created_placeholders,
320 named_dependencies: &mut named_dependencies,
321 unresolved_names: &mut unresolved_names,
322 unresolved_name_policy,
323 };
324 crate::engine::refs::visit_tree_references(
325 ast,
326 &mut context,
327 graph_no_local_bindings,
328 collect_graph_reference,
329 )?;
330
331 let mut deduped_ranges = Vec::new();
333 for range_ref in range_dependencies {
334 if !deduped_ranges.contains(&range_ref) {
335 deduped_ranges.push(range_ref);
336 }
337 }
338
339 named_dependencies.sort_unstable_by_key(|v| v.0);
340 named_dependencies.dedup_by_key(|v| v.0);
341
342 let mut unresolved_names: Vec<String> = unresolved_names.into_iter().collect();
343 unresolved_names.sort();
344
345 Ok((
346 dependencies.into_iter().collect(),
347 deduped_ranges,
348 created_placeholders,
349 named_dependencies,
350 unresolved_names,
351 ))
352 }
353
354 pub(super) fn is_ast_volatile(&self, ast: &ASTNode) -> bool {
355 if ast.contains_volatile() {
356 return true;
357 }
358
359 use formualizer_parse::parser::ASTNodeType;
360
361 match &ast.node_type {
362 ASTNodeType::Function { name, args } => {
363 if let Some(func) = crate::function_registry::get("", name)
364 && func.caps().contains(crate::function::FnCaps::VOLATILE)
365 {
366 return true;
367 }
368 args.iter().any(|arg| self.is_ast_volatile(arg))
369 }
370 ASTNodeType::BinaryOp { left, right, .. } => {
371 self.is_ast_volatile(left) || self.is_ast_volatile(right)
372 }
373 ASTNodeType::UnaryOp { expr, .. } => self.is_ast_volatile(expr),
374 ASTNodeType::Array(rows) => rows
375 .iter()
376 .any(|row| row.iter().any(|cell| self.is_ast_volatile(cell))),
377 ASTNodeType::Call { callee, args } => {
378 self.is_ast_volatile(callee) || args.iter().any(|a| self.is_ast_volatile(a))
379 }
380 _ => false,
381 }
382 }
383
384 pub fn is_ast_dynamic(&self, ast: &ASTNode) -> bool {
385 use formualizer_parse::parser::ASTNodeType;
386
387 match &ast.node_type {
388 ASTNodeType::Function { name, args } => {
389 if let Some(func) = crate::function_registry::get("", name)
390 && func
391 .caps()
392 .contains(crate::function::FnCaps::DYNAMIC_DEPENDENCY)
393 {
394 return true;
395 }
396 args.iter().any(|arg| self.is_ast_dynamic(arg))
397 }
398 ASTNodeType::BinaryOp { left, right, .. } => {
399 self.is_ast_dynamic(left) || self.is_ast_dynamic(right)
400 }
401 ASTNodeType::UnaryOp { expr, .. } => self.is_ast_dynamic(expr),
402 ASTNodeType::Array(rows) => rows
403 .iter()
404 .any(|row| row.iter().any(|cell| self.is_ast_dynamic(cell))),
405 ASTNodeType::Call { callee, args } => {
406 self.is_ast_dynamic(callee) || args.iter().any(|a| self.is_ast_dynamic(a))
407 }
408 _ => false,
409 }
410 }
411}