Skip to main content

formualizer_eval/engine/graph/
names.rs

1use super::*;
2use formualizer_common::parse_a1_1based;
3
4/// Validate that a name conforms to Excel naming rules.
5fn is_valid_excel_name(name: &str) -> bool {
6    // Excel name rules:
7    // 1. Must start with a letter, underscore, or backslash
8    // 2. Can contain letters, numbers, periods, and underscores
9    // 3. Cannot be a cell reference (like A1, B2, etc.)
10    // 4. Cannot exceed 255 characters
11    // 5. Cannot contain spaces
12
13    if name.is_empty() || name.len() > 255 {
14        return false;
15    }
16
17    if parse_a1_1based(name).is_ok() {
18        return false;
19    }
20
21    let mut chars = name.chars();
22
23    // First character must be letter, underscore, or backslash
24    if let Some(first) = chars.next()
25        && !first.is_alphabetic()
26        && first != '_'
27        && first != '\\'
28    {
29        return false;
30    }
31
32    // Remaining characters must be letters, digits, periods, or underscores
33    for c in chars {
34        if !c.is_alphanumeric() && c != '.' && c != '_' {
35            return false;
36        }
37    }
38
39    true
40}
41
42/// Helper function to adjust a named definition during structural operations.
43fn adjust_named_definition(
44    definition: &mut NamedDefinition,
45    adjuster: &crate::engine::graph::editor::reference_adjuster::ReferenceAdjuster,
46    operation: &crate::engine::graph::editor::reference_adjuster::ShiftOperation,
47) -> Result<(), ExcelError> {
48    match definition {
49        NamedDefinition::Cell(cell_ref) => {
50            if let Some(adjusted) = adjuster.adjust_cell_ref(cell_ref, operation) {
51                *cell_ref = adjusted;
52            } else {
53                return Err(ExcelError::new(ExcelErrorKind::Ref));
54            }
55        }
56        NamedDefinition::Range(range_ref) => {
57            let adjusted_start = adjuster.adjust_cell_ref(&range_ref.start, operation);
58            let adjusted_end = adjuster.adjust_cell_ref(&range_ref.end, operation);
59
60            if let (Some(start), Some(end)) = (adjusted_start, adjusted_end) {
61                range_ref.start = start;
62                range_ref.end = end;
63            } else {
64                return Err(ExcelError::new(ExcelErrorKind::Ref));
65            }
66        }
67        NamedDefinition::Formula {
68            ast,
69            dependencies,
70            range_deps,
71        } => {
72            let adjusted_ast = adjuster.adjust_ast(ast, operation);
73            *ast = adjusted_ast;
74
75            dependencies.clear();
76            range_deps.clear();
77        }
78    }
79    Ok(())
80}
81
82impl DependencyGraph {
83    fn next_name_coord(&mut self) -> AbsCoord {
84        let seq = self.name_vertex_seq;
85        self.name_vertex_seq = self.name_vertex_seq.wrapping_add(1);
86        let row = (seq / 16_384).min(0x000F_FFFF);
87        let col = seq % 16_384;
88        AbsCoord::new(row, col)
89    }
90
91    pub(super) fn allocate_name_vertex(&mut self, scope: NameScope) -> VertexId {
92        let coord = self.next_name_coord();
93        let sheet_id = match scope {
94            NameScope::Sheet(id) => id,
95            NameScope::Workbook => self.default_sheet_id,
96        };
97        let vertex_id = self.store.allocate(coord, sheet_id, 0x01);
98        self.store.set_kind(vertex_id, VertexKind::NamedScalar);
99        self.store.set_dirty(vertex_id, true);
100        self.edges.add_vertex(coord, vertex_id.0);
101        self.dirty_vertices.insert(vertex_id);
102        vertex_id
103    }
104
105    // Named Range Methods
106
107    /// Define a new named range
108    pub fn define_name(
109        &mut self,
110        name: &str,
111        definition: NamedDefinition,
112        scope: NameScope,
113    ) -> Result<(), ExcelError> {
114        // Validate name
115        if !is_valid_excel_name(name) {
116            return Err(
117                ExcelError::new(ExcelErrorKind::Name).with_message(format!("Invalid name: {name}"))
118            );
119        }
120
121        // Check for duplicates
122        match scope {
123            NameScope::Workbook => {
124                if self.named_ranges.contains_key(name) {
125                    return Err(ExcelError::new(ExcelErrorKind::Name)
126                        .with_message(format!("Name already exists: {name}")));
127                }
128            }
129            NameScope::Sheet(sheet_id) => {
130                if self
131                    .sheet_named_ranges
132                    .contains_key(&(sheet_id, name.to_string()))
133                {
134                    return Err(ExcelError::new(ExcelErrorKind::Name)
135                        .with_message(format!("Name already exists in sheet: {name}")));
136                }
137            }
138        }
139
140        let mut final_definition = definition;
141        // Extract dependencies if formula
142        if let NamedDefinition::Formula { ref ast, .. } = final_definition {
143            let (deps, range_deps, _, _) = self.extract_dependencies(
144                ast,
145                match scope {
146                    NameScope::Sheet(id) => id,
147                    NameScope::Workbook => self.default_sheet_id,
148                },
149            )?;
150            final_definition = NamedDefinition::Formula {
151                ast: ast.clone(),
152                dependencies: deps,
153                range_deps,
154            };
155        }
156
157        // Allocate vertex only after dependency extraction succeeds
158        let vertex_id = self.allocate_name_vertex(scope);
159
160        let named_range = NamedRange {
161            definition: final_definition,
162            scope,
163            dependents: FxHashSet::default(),
164            vertex: vertex_id,
165        };
166
167        if matches!(named_range.definition, NamedDefinition::Range(_)) {
168            self.store.set_kind(vertex_id, VertexKind::NamedArray);
169        } else {
170            self.store.set_kind(vertex_id, VertexKind::NamedScalar);
171        }
172
173        let referenced_names =
174            self.rebuild_name_dependencies(vertex_id, &named_range.definition, scope);
175        if !referenced_names.is_empty() {
176            self.attach_vertex_to_names(vertex_id, &referenced_names);
177        }
178
179        let key = name.to_string();
180
181        match scope {
182            NameScope::Workbook => {
183                self.named_ranges.insert(key.clone(), named_range);
184            }
185            NameScope::Sheet(id) => {
186                self.sheet_named_ranges
187                    .insert((id, key.clone()), named_range);
188            }
189        }
190
191        self.name_vertex_lookup.insert(vertex_id, (scope, key));
192        self.resolve_pending_name_references(scope, name, vertex_id);
193
194        Ok(())
195    }
196
197    /// Iterate workbook-scoped named ranges (for bindings/testing)
198    pub fn named_ranges_iter(&self) -> impl Iterator<Item = (&String, &NamedRange)> {
199        self.named_ranges.iter()
200    }
201
202    /// Iterate sheet-scoped named ranges (for bindings/testing)
203    pub fn sheet_named_ranges_iter(
204        &self,
205    ) -> impl Iterator<Item = (&(SheetId, String), &NamedRange)> {
206        self.sheet_named_ranges.iter()
207    }
208
209    pub fn resolve_name_entry(&self, name: &str, current_sheet: SheetId) -> Option<&NamedRange> {
210        self.sheet_named_ranges
211            .get(&(current_sheet, name.to_string()))
212            .or_else(|| self.named_ranges.get(name))
213    }
214
215    /// Resolve a named range to its definition
216    pub fn resolve_name(&self, name: &str, current_sheet: SheetId) -> Option<&NamedDefinition> {
217        self.resolve_name_entry(name, current_sheet)
218            .map(|nr| &nr.definition)
219    }
220
221    pub fn named_range_by_vertex(&self, vertex: VertexId) -> Option<&NamedRange> {
222        self.name_vertex_lookup
223            .get(&vertex)
224            .and_then(|(scope, name)| match scope {
225                NameScope::Workbook => self.named_ranges.get(name),
226                NameScope::Sheet(sheet_id) => {
227                    self.sheet_named_ranges.get(&(*sheet_id, name.clone()))
228                }
229            })
230    }
231
232    /// Update an existing named range definition
233    pub fn update_name(
234        &mut self,
235        name: &str,
236        new_definition: NamedDefinition,
237        scope: NameScope,
238    ) -> Result<(), ExcelError> {
239        // First collect dependents to avoid borrow checker issues
240        let dependents_to_dirty = match scope {
241            NameScope::Workbook => self
242                .named_ranges
243                .get(name)
244                .map(|nr| nr.dependents.iter().copied().collect::<Vec<_>>()),
245            NameScope::Sheet(id) => self
246                .sheet_named_ranges
247                .get(&(id, name.to_string()))
248                .map(|nr| nr.dependents.iter().copied().collect::<Vec<_>>()),
249        };
250
251        if let Some(dependents) = dependents_to_dirty {
252            // Mark all dependents as dirty
253            for vertex_id in dependents {
254                self.mark_vertex_dirty(vertex_id);
255            }
256
257            // Now update the definition
258            let named_range = match scope {
259                NameScope::Workbook => self.named_ranges.get_mut(name),
260                NameScope::Sheet(id) => self.sheet_named_ranges.get_mut(&(id, name.to_string())),
261            };
262
263            let mut update_data: Option<(VertexId, NameScope, NamedDefinition, bool)> = None;
264            if let Some(named_range) = named_range {
265                named_range.definition = new_definition;
266                named_range.dependents.clear();
267                let is_range = matches!(named_range.definition, NamedDefinition::Range(_));
268                update_data = Some((
269                    named_range.vertex,
270                    named_range.scope,
271                    named_range.definition.clone(),
272                    is_range,
273                ));
274            }
275
276            if let Some((vertex, scope_value, definition_snapshot, is_range)) = update_data {
277                self.detach_vertex_from_names(vertex);
278
279                if is_range {
280                    self.store.set_kind(vertex, VertexKind::NamedArray);
281                } else {
282                    self.store.set_kind(vertex, VertexKind::NamedScalar);
283                }
284                self.store.set_dirty(vertex, true);
285                self.dirty_vertices.insert(vertex);
286
287                let referenced_names =
288                    self.rebuild_name_dependencies(vertex, &definition_snapshot, scope_value);
289                if !referenced_names.is_empty() {
290                    self.attach_vertex_to_names(vertex, &referenced_names);
291                }
292            }
293
294            Ok(())
295        } else {
296            Err(ExcelError::new(ExcelErrorKind::Name)
297                .with_message(format!("Name not found: {name}")))
298        }
299    }
300
301    /// Delete a named range
302    pub fn delete_name(&mut self, name: &str, scope: NameScope) -> Result<(), ExcelError> {
303        let named_range = match scope {
304            NameScope::Workbook => self.named_ranges.remove(name),
305            NameScope::Sheet(id) => self.sheet_named_ranges.remove(&(id, name.to_string())),
306        };
307
308        if let Some(named_range) = named_range {
309            let mut affected: FxHashSet<VertexId> = FxHashSet::default();
310            for &vertex_id in &named_range.dependents {
311                affected.insert(vertex_id);
312            }
313            for (vertex_id, names) in self.vertex_to_names.iter() {
314                if names.iter().any(|vid| *vid == named_range.vertex) {
315                    affected.insert(*vertex_id);
316                }
317            }
318            for vertex_id in affected {
319                self.mark_vertex_dirty(vertex_id);
320                if let Some(names) = self.vertex_to_names.get_mut(&vertex_id) {
321                    names.retain(|vid| *vid != named_range.vertex);
322                    if names.is_empty() {
323                        self.vertex_to_names.remove(&vertex_id);
324                    }
325                }
326            }
327            self.mark_named_vertex_deleted(&named_range);
328            Ok(())
329        } else {
330            Err(ExcelError::new(ExcelErrorKind::Name)
331                .with_message(format!("Name not found: {name}")))
332        }
333    }
334
335    pub(super) fn detach_vertex_from_names(&mut self, vertex: VertexId) {
336        if let Some(prior) = self.vertex_to_names.remove(&vertex) {
337            for name_vertex in prior {
338                if let Some((scope, name)) = self.name_vertex_lookup.get(&name_vertex).cloned() {
339                    match scope {
340                        NameScope::Workbook => {
341                            if let Some(entry) = self.named_ranges.get_mut(&name) {
342                                entry.dependents.remove(&vertex);
343                            }
344                        }
345                        NameScope::Sheet(sheet_id) => {
346                            if let Some(entry) =
347                                self.sheet_named_ranges.get_mut(&(sheet_id, name.clone()))
348                            {
349                                entry.dependents.remove(&vertex);
350                            }
351                        }
352                    }
353                }
354            }
355        }
356    }
357
358    pub(crate) fn attach_vertex_to_names(&mut self, vertex: VertexId, names: &[VertexId]) {
359        if names.is_empty() {
360            return;
361        }
362        let mut unique = FxHashSet::default();
363        let mut recorded = Vec::new();
364        for &name_vertex in names {
365            if !unique.insert(name_vertex) {
366                continue;
367            }
368            if let Some((scope, name)) = self.name_vertex_lookup.get(&name_vertex).cloned() {
369                match scope {
370                    NameScope::Workbook => {
371                        if let Some(entry) = self.named_ranges.get_mut(&name) {
372                            entry.dependents.insert(vertex);
373                        }
374                    }
375                    NameScope::Sheet(sheet_id) => {
376                        if let Some(entry) =
377                            self.sheet_named_ranges.get_mut(&(sheet_id, name.clone()))
378                        {
379                            entry.dependents.insert(vertex);
380                        }
381                    }
382                }
383                recorded.push(name_vertex);
384            }
385        }
386        if !recorded.is_empty() {
387            self.vertex_to_names.insert(vertex, recorded);
388        }
389    }
390
391    pub(super) fn unregister_name_cell_dependencies(&mut self, name_vertex: VertexId) {
392        if let Some(prev) = self.name_to_cell_dependencies.remove(&name_vertex) {
393            for dep in prev {
394                if let Some(set) = self.cell_to_name_dependents.get_mut(&dep) {
395                    set.remove(&name_vertex);
396                    if set.is_empty() {
397                        self.cell_to_name_dependents.remove(&dep);
398                    }
399                }
400            }
401        }
402    }
403
404    pub(super) fn register_name_cell_dependencies(
405        &mut self,
406        name_vertex: VertexId,
407        dependencies: &[VertexId],
408    ) {
409        self.unregister_name_cell_dependencies(name_vertex);
410        if dependencies.is_empty() {
411            return;
412        }
413        for dep in dependencies {
414            self.cell_to_name_dependents
415                .entry(*dep)
416                .or_default()
417                .insert(name_vertex);
418        }
419        self.name_to_cell_dependencies
420            .insert(name_vertex, dependencies.to_vec());
421    }
422
423    pub(crate) fn record_pending_name_reference(
424        &mut self,
425        sheet_id: SheetId,
426        name: &str,
427        formula_vertex: VertexId,
428    ) {
429        self.pending_name_links
430            .entry(name.to_string())
431            .or_default()
432            .push((sheet_id, formula_vertex));
433    }
434
435    fn resolve_pending_name_references(
436        &mut self,
437        scope: NameScope,
438        name: &str,
439        named_vertex: VertexId,
440    ) {
441        if let Some(mut entries) = self.pending_name_links.remove(name) {
442            let mut remaining: Vec<(SheetId, VertexId)> = Vec::new();
443            for (sheet_id, formula_vertex) in entries.drain(..) {
444                let attach = match scope {
445                    NameScope::Workbook => true,
446                    NameScope::Sheet(expected) => expected == sheet_id,
447                };
448                if attach {
449                    self.add_dependent_edges(formula_vertex, &[named_vertex]);
450                    self.attach_vertex_to_names(formula_vertex, &[named_vertex]);
451                } else {
452                    remaining.push((sheet_id, formula_vertex));
453                }
454            }
455            if !remaining.is_empty() {
456                self.pending_name_links.insert(name.to_string(), remaining);
457            }
458        }
459    }
460
461    pub(super) fn name_depends_on_vertex(
462        &self,
463        name_vertex: VertexId,
464        target: VertexId,
465        visited: &mut FxHashSet<VertexId>,
466    ) -> bool {
467        if !visited.insert(name_vertex) {
468            return false;
469        }
470
471        for dependency in self.edges.out_edges(name_vertex).iter().copied() {
472            if dependency == target {
473                return true;
474            }
475
476            if matches!(
477                self.store.kind(dependency),
478                VertexKind::NamedScalar | VertexKind::NamedArray
479            ) && self.name_depends_on_vertex(dependency, target, visited)
480            {
481                return true;
482            }
483        }
484
485        false
486    }
487
488    pub(super) fn rebuild_name_dependencies(
489        &mut self,
490        vertex: VertexId,
491        definition: &NamedDefinition,
492        scope: NameScope,
493    ) -> Vec<VertexId> {
494        self.remove_dependent_edges(vertex);
495        self.unregister_name_cell_dependencies(vertex);
496
497        let mut dependencies: Vec<VertexId> = Vec::new();
498        let mut range_dependencies: Vec<SharedRangeRef<'static>> = Vec::new();
499        let mut placeholders = Vec::new();
500
501        match definition {
502            NamedDefinition::Cell(cell_ref) => {
503                let vertex_id = self.get_or_create_vertex(cell_ref, &mut placeholders);
504                dependencies.push(vertex_id);
505            }
506            NamedDefinition::Range(range_ref) => {
507                let height = range_ref
508                    .end
509                    .coord
510                    .row()
511                    .saturating_sub(range_ref.start.coord.row())
512                    + 1;
513                let width = range_ref
514                    .end
515                    .coord
516                    .col()
517                    .saturating_sub(range_ref.start.coord.col())
518                    + 1;
519                let size = (width * height) as usize;
520
521                if size <= self.config.range_expansion_limit {
522                    for row in range_ref.start.coord.row()..=range_ref.end.coord.row() {
523                        for col in range_ref.start.coord.col()..=range_ref.end.coord.col() {
524                            let coord = Coord::new(row, col, true, true);
525                            let addr = CellRef::new(range_ref.start.sheet_id, coord);
526                            let vertex_id = self.get_or_create_vertex(&addr, &mut placeholders);
527                            dependencies.push(vertex_id);
528                        }
529                    }
530                } else {
531                    let sheet_loc = SharedSheetLocator::Id(range_ref.start.sheet_id);
532                    let sr = formualizer_common::AxisBound::new(
533                        range_ref.start.coord.row(),
534                        range_ref.start.coord.row_abs(),
535                    );
536                    let sc = formualizer_common::AxisBound::new(
537                        range_ref.start.coord.col(),
538                        range_ref.start.coord.col_abs(),
539                    );
540                    let er = formualizer_common::AxisBound::new(
541                        range_ref.end.coord.row(),
542                        range_ref.end.coord.row_abs(),
543                    );
544                    let ec = formualizer_common::AxisBound::new(
545                        range_ref.end.coord.col(),
546                        range_ref.end.coord.col_abs(),
547                    );
548                    if let Ok(r) = SharedRangeRef::from_parts(
549                        sheet_loc,
550                        Some(sr),
551                        Some(sc),
552                        Some(er),
553                        Some(ec),
554                    ) {
555                        range_dependencies.push(r.into_owned());
556                    }
557                }
558            }
559            NamedDefinition::Formula {
560                dependencies: formula_deps,
561                range_deps,
562                ..
563            } => {
564                dependencies.extend(formula_deps.iter().copied());
565                range_dependencies.extend(range_deps.iter().cloned());
566            }
567        }
568
569        if !dependencies.is_empty() {
570            self.add_dependent_edges(vertex, &dependencies);
571        }
572        self.register_name_cell_dependencies(vertex, &dependencies);
573
574        if !range_dependencies.is_empty() {
575            let sheet_id = match scope {
576                NameScope::Sheet(id) => id,
577                NameScope::Workbook => self.default_sheet_id,
578            };
579            self.add_range_dependent_edges(vertex, &range_dependencies, sheet_id);
580        }
581
582        dependencies
583            .iter()
584            .filter(|vid| {
585                matches!(
586                    self.store.kind(**vid),
587                    VertexKind::NamedScalar | VertexKind::NamedArray
588                )
589            })
590            .copied()
591            .collect()
592    }
593
594    pub fn adjust_named_ranges(
595        &mut self,
596        operation: &crate::engine::graph::editor::reference_adjuster::ShiftOperation,
597    ) -> Result<(), ExcelError> {
598        let adjuster = crate::engine::graph::editor::reference_adjuster::ReferenceAdjuster::new();
599
600        // Adjust workbook-scoped names
601        for named_range in self.named_ranges.values_mut() {
602            adjust_named_definition(&mut named_range.definition, &adjuster, operation)?;
603        }
604
605        // Adjust sheet-scoped names
606        for named_range in self.sheet_named_ranges.values_mut() {
607            adjust_named_definition(&mut named_range.definition, &adjuster, operation)?;
608        }
609
610        Ok(())
611    }
612
613    /// Mark a vertex as having a #NAME! error
614    pub fn mark_as_name_error(&mut self, vertex_id: VertexId) {
615        // Mark the vertex as dirty
616        self.mark_vertex_dirty(vertex_id);
617    }
618
619    pub(super) fn mark_named_vertex_deleted(&mut self, named_range: &NamedRange) {
620        self.detach_vertex_from_names(named_range.vertex);
621        self.remove_dependent_edges(named_range.vertex);
622        self.unregister_name_cell_dependencies(named_range.vertex);
623        self.store.mark_deleted(named_range.vertex, true);
624        self.vertex_values.remove(&named_range.vertex);
625        self.vertex_formulas.remove(&named_range.vertex);
626        self.dirty_vertices.remove(&named_range.vertex);
627        self.volatile_vertices.remove(&named_range.vertex);
628        self.vertex_to_names.remove(&named_range.vertex);
629        self.name_vertex_lookup.remove(&named_range.vertex);
630    }
631}