Skip to main content

formualizer_eval/engine/
plan.rs

1use crate::SheetId;
2use crate::engine::sheet_registry::SheetRegistry;
3use formualizer_common::Coord as AbsCoord;
4use formualizer_common::ExcelError;
5use formualizer_parse::parser::{CollectPolicy, ReferenceType};
6use rustc_hash::FxHashMap;
7
8/// Compact range descriptor used during planning (engine-only)
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
10pub enum RangeKey {
11    Rect {
12        sheet: SheetId,
13        start: AbsCoord,
14        end: AbsCoord, // inclusive
15    },
16    WholeRow {
17        sheet: SheetId,
18        row: u32,
19    },
20    WholeCol {
21        sheet: SheetId,
22        col: u32,
23    },
24    /// Partially bounded rectangle; None means unbounded in that direction
25    OpenRect {
26        sheet: SheetId,
27        start: Option<AbsCoord>,
28        end: Option<AbsCoord>,
29    },
30}
31
32/// Bitflags conveying per-formula traits
33pub type FormulaFlags = u8;
34pub const F_VOLATILE: FormulaFlags = 0b0000_0001;
35pub const F_HAS_RANGES: FormulaFlags = 0b0000_0010;
36pub const F_HAS_NAMES: FormulaFlags = 0b0000_0100;
37pub const F_HAS_TABLES: FormulaFlags = 0b0001_0000;
38pub const F_LIKELY_ARRAY: FormulaFlags = 0b0000_1000;
39
40#[derive(Debug, Default, Clone)]
41pub struct DependencyPlan {
42    pub formula_targets: Vec<(SheetId, AbsCoord)>,
43    pub global_cells: Vec<(SheetId, AbsCoord)>,
44    pub per_formula_cells: Vec<Vec<u32>>, // indices into global_cells
45    pub per_formula_ranges: Vec<Vec<RangeKey>>,
46    pub per_formula_names: Vec<Vec<String>>,
47    pub per_formula_tables: Vec<Vec<String>>,
48    pub per_formula_flags: Vec<FormulaFlags>,
49    pub edges_flat: Option<Vec<u32>>, // optional flat adjacency (indices into global_cells)
50    pub offsets: Option<Vec<u32>>,    // len = num_formulas + 1 when edges_flat is Some
51}
52
53/// Build a compact dependency plan from ASTs without mutating the graph.
54/// Sheets referenced by name are resolved/created through SheetRegistry at plan time.
55pub fn build_dependency_plan<'a, I>(
56    sheet_reg: &mut SheetRegistry,
57    formulas: I,
58    policy: &CollectPolicy,
59    volatile_flags: Option<&[bool]>,
60) -> Result<DependencyPlan, ExcelError>
61where
62    I: Iterator<Item = (&'a str, u32, u32, &'a formualizer_parse::parser::ASTNode)>,
63{
64    let mut plan = DependencyPlan::default();
65
66    // Global cell pool: (sheet, coord) -> index
67    let mut cell_index: FxHashMap<(SheetId, AbsCoord), u32> = FxHashMap::default();
68
69    for (i, (sheet_name, row, col, ast)) in formulas.enumerate() {
70        let sheet_id = sheet_reg.id_for(sheet_name);
71        let target = (sheet_id, AbsCoord::from_excel(row, col));
72        plan.formula_targets.push(target);
73
74        let mut flags: FormulaFlags = 0;
75        if let Some(v) = volatile_flags.and_then(|v| v.get(i)).copied()
76            && v
77        {
78            flags |= F_VOLATILE;
79        }
80
81        let mut per_cells: Vec<u32> = Vec::new();
82        let mut per_ranges: Vec<RangeKey> = Vec::new();
83        let mut per_names: Vec<String> = Vec::new();
84        let mut per_tables: Vec<String> = Vec::new();
85
86        // Collect references using core collector (may expand small ranges per policy)
87        let refs = ast.collect_references(policy);
88        for r in refs {
89            match r {
90                ReferenceType::Cell {
91                    sheet, row, col, ..
92                } => {
93                    let dep_sheet = sheet
94                        .as_deref()
95                        .map(|name| sheet_reg.id_for(name))
96                        .unwrap_or(sheet_id);
97                    let key = (dep_sheet, AbsCoord::from_excel(row, col));
98                    let idx = match cell_index.get(&key) {
99                        Some(&idx) => idx,
100                        None => {
101                            let new_idx = plan.global_cells.len() as u32;
102                            plan.global_cells.push(key);
103                            cell_index.insert(key, new_idx);
104                            new_idx
105                        }
106                    };
107                    per_cells.push(idx);
108                }
109                ReferenceType::Range {
110                    sheet,
111                    start_row,
112                    start_col,
113                    end_row,
114                    end_col,
115                    ..
116                } => {
117                    let dep_sheet = sheet
118                        .as_deref()
119                        .map(|name| sheet_reg.id_for(name))
120                        .unwrap_or(sheet_id);
121                    match (start_row, start_col, end_row, end_col) {
122                        (Some(sr), Some(sc), Some(er), Some(ec)) => {
123                            per_ranges.push(RangeKey::Rect {
124                                sheet: dep_sheet,
125                                start: AbsCoord::from_excel(sr, sc),
126                                end: AbsCoord::from_excel(er, ec),
127                            })
128                        }
129                        (None, Some(c), None, Some(ec)) if c == ec => {
130                            per_ranges.push(RangeKey::WholeCol {
131                                sheet: dep_sheet,
132                                col: c,
133                            })
134                        }
135                        (Some(r), None, Some(er), None) if r == er => {
136                            per_ranges.push(RangeKey::WholeRow {
137                                sheet: dep_sheet,
138                                row: r,
139                            })
140                        }
141                        _ => per_ranges.push(RangeKey::OpenRect {
142                            sheet: dep_sheet,
143                            start: start_row
144                                .zip(start_col)
145                                .map(|(r, c)| AbsCoord::from_excel(r, c)),
146                            end: end_row
147                                .zip(end_col)
148                                .map(|(r, c)| AbsCoord::from_excel(r, c)),
149                        }),
150                    }
151                }
152                ReferenceType::External(ext) => match ext.kind {
153                    formualizer_parse::parser::ExternalRefKind::Cell { .. } => {
154                        flags |= F_HAS_NAMES;
155                        per_names.push(ext.raw.clone());
156                    }
157                    formualizer_parse::parser::ExternalRefKind::Range { .. } => {
158                        flags |= F_HAS_TABLES;
159                        per_tables.push(ext.raw.clone());
160                    }
161                },
162                ReferenceType::NamedRange(name) => {
163                    // Resolution handled later; mark via flags if caller cares
164                    flags |= F_HAS_NAMES;
165                    per_names.push(name);
166                }
167                ReferenceType::Table(tref) => {
168                    flags |= F_HAS_TABLES;
169                    per_tables.push(tref.name);
170                }
171            }
172        }
173
174        plan.per_formula_cells.push(per_cells);
175        plan.per_formula_ranges.push(per_ranges);
176        plan.per_formula_names.push(per_names);
177        plan.per_formula_tables.push(per_tables);
178        plan.per_formula_flags.push(flags);
179    }
180
181    Ok(plan)
182}