Skip to main content

pdforg_sheets/
dep_graph.rs

1//! Dependency graph for incremental formula recalculation.
2
3use pdf_core::CellAddress;
4use std::collections::{HashMap, HashSet, VecDeque};
5
6/// Bidirectional dependency graph between cells
7#[derive(Debug, Default, Clone)]
8pub struct DepGraph {
9    /// cell → set of cells it directly depends on (reads from)
10    pub deps: HashMap<CellAddress, HashSet<CellAddress>>,
11    /// cell → set of cells that depend on it (readers)
12    pub rdeps: HashMap<CellAddress, HashSet<CellAddress>>,
13}
14
15impl DepGraph {
16    pub fn new() -> Self { DepGraph::default() }
17
18    /// Register that `cell` depends on all cells in `dependencies`
19    pub fn set_deps(&mut self, cell: CellAddress, dependencies: HashSet<CellAddress>) {
20        // Remove old reverse deps for this cell
21        if let Some(old_deps) = self.deps.get(&cell) {
22            for dep in old_deps.clone() {
23                if let Some(rdeps) = self.rdeps.get_mut(&dep) {
24                    rdeps.remove(&cell);
25                }
26            }
27        }
28
29        // Add new reverse deps
30        for dep in &dependencies {
31            self.rdeps.entry(dep.clone()).or_default().insert(cell.clone());
32        }
33
34        self.deps.insert(cell, dependencies);
35    }
36
37    /// Remove all dependency information for a cell (when cell content changes to non-formula)
38    pub fn remove_cell(&mut self, cell: &CellAddress) {
39        if let Some(old_deps) = self.deps.remove(cell) {
40            for dep in old_deps {
41                if let Some(rdeps) = self.rdeps.get_mut(&dep) {
42                    rdeps.remove(cell);
43                }
44            }
45        }
46        self.rdeps.remove(cell);
47    }
48
49    /// Compute the minimal ordered set of cells that need recalculation
50    /// when `changed` is modified. Returns cells in topological order.
51    pub fn dirty_set(&self, changed: &CellAddress) -> Vec<CellAddress> {
52        let mut result = vec![];
53        let mut visited = HashSet::new();
54        let mut queue = VecDeque::new();
55
56        // BFS over reverse dependency graph
57        if let Some(first_rdeps) = self.rdeps.get(changed) {
58            for dep in first_rdeps {
59                if visited.insert(dep.clone()) {
60                    queue.push_back(dep.clone());
61                }
62            }
63        }
64
65        while let Some(cell) = queue.pop_front() {
66            result.push(cell.clone());
67            if let Some(rdeps) = self.rdeps.get(&cell) {
68                for dep in rdeps {
69                    if visited.insert(dep.clone()) {
70                        queue.push_back(dep.clone());
71                    }
72                }
73            }
74        }
75
76        // Topological sort
77        self.topological_sort(&result)
78    }
79
80    /// Topological sort of the given cells based on their dependencies
81    fn topological_sort(&self, cells: &[CellAddress]) -> Vec<CellAddress> {
82        let cell_set: HashSet<&CellAddress> = cells.iter().collect();
83        let mut in_degree: HashMap<&CellAddress, usize> = HashMap::new();
84        let mut adj: HashMap<&CellAddress, Vec<&CellAddress>> = HashMap::new();
85
86        for cell in cells {
87            in_degree.entry(cell).or_insert(0);
88            if let Some(rdeps) = self.rdeps.get(cell) {
89                for rdep in rdeps {
90                    if cell_set.contains(rdep) {
91                        adj.entry(cell).or_default().push(rdep);
92                        *in_degree.entry(rdep).or_insert(0) += 1;
93                    }
94                }
95            }
96        }
97
98        let mut queue: VecDeque<&CellAddress> = in_degree.iter()
99            .filter(|(_, &deg)| deg == 0)
100            .map(|(cell, _)| *cell)
101            .collect();
102
103        let mut result = vec![];
104        while let Some(cell) = queue.pop_front() {
105            result.push(cell.clone());
106            if let Some(neighbors) = adj.get(cell) {
107                for &neighbor in neighbors {
108                    let deg = in_degree.entry(neighbor).or_insert(0);
109                    *deg -= 1;
110                    if *deg == 0 {
111                        queue.push_back(neighbor);
112                    }
113                }
114            }
115        }
116
117        result
118    }
119
120    /// Check if adding `new_dep` for `cell` would create a circular reference
121    pub fn would_create_cycle(&self, cell: &CellAddress, new_dep: &CellAddress) -> bool {
122        // DFS from new_dep to see if we can reach cell
123        let mut visited = HashSet::new();
124        let mut stack = vec![new_dep.clone()];
125        while let Some(cur) = stack.pop() {
126            if &cur == cell { return true; }
127            if visited.insert(cur.clone()) {
128                if let Some(deps) = self.deps.get(&cur) {
129                    for d in deps { stack.push(d.clone()); }
130                }
131            }
132        }
133        false
134    }
135
136    pub fn dep_count(&self) -> usize { self.deps.len() }
137    pub fn rdep_count(&self) -> usize { self.rdeps.len() }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    fn addr(row: u32, col: u32) -> CellAddress { CellAddress::new(row, col) }
145
146    #[test]
147    fn test_simple_dirty_set() {
148        let mut graph = DepGraph::new();
149        // A1 depends on B1
150        graph.set_deps(addr(0, 0), [addr(0, 1)].into_iter().collect());
151        // C1 depends on A1
152        graph.set_deps(addr(0, 2), [addr(0, 0)].into_iter().collect());
153
154        // When B1 changes, A1 and C1 should be dirty
155        let dirty = graph.dirty_set(&addr(0, 1));
156        assert!(dirty.contains(&addr(0, 0)));
157        assert!(dirty.contains(&addr(0, 2)));
158        // A1 should come before C1
159        let a1_pos = dirty.iter().position(|a| a == &addr(0, 0)).unwrap();
160        let c1_pos = dirty.iter().position(|a| a == &addr(0, 2)).unwrap();
161        assert!(a1_pos < c1_pos);
162    }
163
164    #[test]
165    fn test_no_cycle() {
166        let mut graph = DepGraph::new();
167        graph.set_deps(addr(0, 0), [addr(0, 1)].into_iter().collect());
168        assert!(!graph.would_create_cycle(&addr(0, 1), &addr(0, 2)));
169        assert!(graph.would_create_cycle(&addr(0, 1), &addr(0, 0)));
170    }
171}