Skip to main content

formualizer_eval/engine/graph/
sources.rs

1use crate::SheetId;
2use crate::engine::graph::DependencyGraph;
3use crate::engine::named_range::NameScope;
4use crate::engine::vertex::{VertexId, VertexKind};
5use formualizer_common::{ExcelError, ExcelErrorKind};
6
7#[derive(Debug, Clone)]
8pub struct SourceScalarEntry {
9    pub name: String,
10    pub vertex: VertexId,
11    pub version: Option<u64>,
12}
13
14#[derive(Debug, Clone)]
15pub struct SourceTableEntry {
16    pub name: String,
17    pub vertex: VertexId,
18    pub version: Option<u64>,
19}
20
21impl DependencyGraph {
22    fn allocate_source_vertex(&mut self) -> VertexId {
23        // External sources are identified by name and have no position; the default sheet
24        // is recorded only as the scope their lookups answer for.
25        let scope_sheet_id: SheetId = self.default_sheet_id;
26        self.allocate_symbol_vertex(VertexKind::External, scope_sheet_id)
27    }
28
29    pub fn resolve_source_scalar_entry(&self, name: &str) -> Option<&SourceScalarEntry> {
30        self.source_scalars.get(name)
31    }
32
33    pub fn resolve_source_table_entry(&self, name: &str) -> Option<&SourceTableEntry> {
34        self.source_tables.get(name)
35    }
36
37    pub fn define_source_scalar(
38        &mut self,
39        name: &str,
40        version: Option<u64>,
41    ) -> Result<(), ExcelError> {
42        if name.is_empty() {
43            return Err(ExcelError::new(ExcelErrorKind::Name)
44                .with_message("Source name cannot be empty".to_string()));
45        }
46        if self.source_scalars.contains_key(name) || self.source_tables.contains_key(name) {
47            return Err(ExcelError::new(ExcelErrorKind::Name)
48                .with_message(format!("Source already defined: {name}")));
49        }
50
51        let vertex = self.allocate_source_vertex();
52        self.source_vertex_lookup.insert(vertex, name.to_string());
53        self.mark_volatile(vertex, version.is_none());
54
55        let entry = SourceScalarEntry {
56            name: name.to_string(),
57            vertex,
58            version,
59        };
60        self.source_scalars.insert(name.to_string(), entry);
61        self.resolve_pending_name_references(NameScope::Workbook, name);
62        self.bump_symbol_revision();
63        Ok(())
64    }
65
66    pub fn define_source_table(
67        &mut self,
68        name: &str,
69        version: Option<u64>,
70    ) -> Result<(), ExcelError> {
71        if name.is_empty() {
72            return Err(ExcelError::new(ExcelErrorKind::Name)
73                .with_message("Source name cannot be empty".to_string()));
74        }
75        if self.source_tables.contains_key(name) || self.source_scalars.contains_key(name) {
76            return Err(ExcelError::new(ExcelErrorKind::Name)
77                .with_message(format!("Source already defined: {name}")));
78        }
79
80        let vertex = self.allocate_source_vertex();
81        self.source_vertex_lookup.insert(vertex, name.to_string());
82        self.mark_volatile(vertex, version.is_none());
83
84        let entry = SourceTableEntry {
85            name: name.to_string(),
86            vertex,
87            version,
88        };
89        self.source_tables.insert(name.to_string(), entry);
90        self.bump_symbol_revision();
91        Ok(())
92    }
93
94    pub fn set_source_scalar_version(
95        &mut self,
96        name: &str,
97        version: Option<u64>,
98    ) -> Result<(), ExcelError> {
99        let vertex = {
100            let entry = self.source_scalars.get_mut(name).ok_or_else(|| {
101                ExcelError::new(ExcelErrorKind::Name)
102                    .with_message(format!("Unknown source: {name}"))
103            })?;
104
105            if entry.version == version {
106                return Ok(());
107            }
108
109            entry.version = version;
110            entry.vertex
111        };
112
113        self.mark_volatile(vertex, version.is_none());
114        self.mark_dirty(vertex);
115        Ok(())
116    }
117
118    pub fn set_source_table_version(
119        &mut self,
120        name: &str,
121        version: Option<u64>,
122    ) -> Result<(), ExcelError> {
123        let vertex = {
124            let entry = self.source_tables.get_mut(name).ok_or_else(|| {
125                ExcelError::new(ExcelErrorKind::Name)
126                    .with_message(format!("Unknown source: {name}"))
127            })?;
128
129            if entry.version == version {
130                return Ok(());
131            }
132
133            entry.version = version;
134            entry.vertex
135        };
136
137        self.mark_volatile(vertex, version.is_none());
138        self.mark_dirty(vertex);
139        Ok(())
140    }
141
142    pub fn invalidate_source(&mut self, name: &str) -> Result<(), ExcelError> {
143        if let Some(s) = self.source_scalars.get(name) {
144            self.mark_dirty(s.vertex);
145            return Ok(());
146        }
147        if let Some(t) = self.source_tables.get(name) {
148            self.mark_dirty(t.vertex);
149            return Ok(());
150        }
151        Err(ExcelError::new(ExcelErrorKind::Name).with_message(format!("Unknown source: {name}")))
152    }
153}