Skip to main content

visi_core/core/engine/
column.rs

1use crate::core::CompiledFormula;
2use crate::core::SharedVec;
3use serde::{Deserialize, Serialize};
4
5use super::bitmask::Bitmask;
6use super::cell::{CellType, generate_unique_id};
7use super::result_data::ResultData;
8
9/// A column of computed values, stored in whichever representation fits what
10/// it currently holds.
11///
12/// A column starts out as `Integer` and widens as needed: writing a float
13/// promotes it to `Float`, and writing anything that is neither demotes it to
14/// `Any`. It never narrows back. The two numeric representations keep a
15/// separate validity [`Bitmask`] so a blank cell is distinct from a zero.
16///
17/// This is a storage detail of [`DataColumn`], exposed for reading. The
18/// operations that change a column's length are crate-private, since they
19/// would desync it from the sibling vectors it must stay aligned with -- go
20/// through `Sheet` to edit cells.
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub enum ColumnData {
23    /// All-integer, or integer-and-blank.
24    Integer {
25        /// Which positions hold a value rather than a blank.
26        validity: Bitmask,
27        /// The values. Positions marked invalid hold a placeholder.
28        values: SharedVec<i64>,
29    },
30    /// Numeric with at least one non-integer, or integer-and-blank promoted.
31    Float {
32        /// Which positions hold a value rather than a blank.
33        validity: Bitmask,
34        /// The values. Positions marked invalid hold a placeholder.
35        values: SharedVec<f64>,
36    },
37    /// Mixed: anything the numeric representations cannot hold -- text,
38    /// booleans, errors.
39    Any(SharedVec<ResultData>),
40}
41
42impl ColumnData {
43    pub(crate) fn new(size: usize) -> Self {
44        Self::Integer {
45            validity: Bitmask::with_size(size),
46            values: vec![0; size].into(),
47        }
48    }
49
50    /// How many rows the column holds.
51    pub fn len(&self) -> usize {
52        match self {
53            Self::Integer { validity, .. } => validity.len,
54            Self::Float { validity, .. } => validity.len,
55            Self::Any(v) => v.len(),
56        }
57    }
58
59    /// Whether the column holds no rows at all.
60    pub fn is_empty(&self) -> bool {
61        self.len() == 0
62    }
63
64    pub(crate) fn push(&mut self, value: ResultData) {
65        let index = self.len();
66        self.insert(index, value);
67    }
68
69    /// The value at `index`, or `None` if that is past the end.
70    ///
71    /// A blank within the column's range reads as
72    /// `Some(ResultData::None)`, which is what distinguishes it from an
73    /// out-of-range index.
74    pub fn get(&self, index: usize) -> Option<ResultData> {
75        if index >= self.len() {
76            return None;
77        }
78        match self {
79            Self::Integer { validity, values } => {
80                if validity.get(index) {
81                    Some(ResultData::Integer(values[index]))
82                } else {
83                    Some(ResultData::None)
84                }
85            }
86            Self::Float { validity, values } => {
87                if validity.get(index) {
88                    Some(ResultData::Float(values[index]))
89                } else {
90                    Some(ResultData::None)
91                }
92            }
93            Self::Any(v) => Some(v[index].clone()),
94        }
95    }
96
97    pub(crate) fn demote_to_any(&mut self) {
98        let len = self.len();
99        let mut any = Vec::with_capacity(len);
100        for i in 0..len {
101            any.push(self.get(i).unwrap());
102        }
103        *self = Self::Any(any.into());
104    }
105
106    pub(crate) fn promote_to_float(&mut self) {
107        if let Self::Integer { validity, values } = self {
108            let float_values = values.iter().map(|&i| i as f64).collect();
109            *self = Self::Float {
110                validity: validity.clone(),
111                values: float_values,
112            };
113        }
114    }
115
116    pub(crate) fn resize(&mut self, size: usize) {
117        match self {
118            Self::Integer { validity, values } => {
119                values.resize(size, 0);
120                *validity = Bitmask::with_size(size);
121            }
122            Self::Float { validity, values } => {
123                values.resize(size, 0.0);
124                *validity = Bitmask::with_size(size);
125            }
126            Self::Any(v) => {
127                v.resize(size, ResultData::None);
128            }
129        }
130    }
131
132    pub(crate) fn set(&mut self, index: usize, value: ResultData) {
133        if index >= self.len() {
134            return;
135        }
136        match self {
137            Self::Integer { validity, values } => match value {
138                ResultData::Integer(i) => {
139                    validity.set(index, true);
140                    values[index] = i;
141                }
142                ResultData::Float(f) => {
143                    self.promote_to_float();
144                    self.set(index, ResultData::Float(f));
145                }
146                ResultData::None => {
147                    validity.set(index, false);
148                    values[index] = 0;
149                }
150                _ => {
151                    self.demote_to_any();
152                    if let Self::Any(v) = self {
153                        v[index] = value;
154                    }
155                }
156            },
157            Self::Float { validity, values } => match value {
158                ResultData::Float(f) => {
159                    validity.set(index, true);
160                    values[index] = f;
161                }
162                ResultData::Integer(i) => {
163                    validity.set(index, true);
164                    values[index] = i as f64;
165                }
166                ResultData::None => {
167                    validity.set(index, false);
168                    values[index] = 0.0;
169                }
170                _ => {
171                    self.demote_to_any();
172                    if let Self::Any(v) = self {
173                        v[index] = value;
174                    }
175                }
176            },
177            Self::Any(v) => {
178                v[index] = value;
179            }
180        }
181    }
182
183    pub(crate) fn insert(&mut self, index: usize, value: ResultData) {
184        match self {
185            Self::Integer { validity, values } => match value {
186                ResultData::Integer(i) => {
187                    validity.insert(index, true);
188                    values.insert(index, i);
189                }
190                ResultData::Float(f) => {
191                    self.promote_to_float();
192                    self.insert(index, ResultData::Float(f));
193                }
194                ResultData::None => {
195                    validity.insert(index, false);
196                    values.insert(index, 0);
197                }
198                _ => {
199                    self.demote_to_any();
200                    if let Self::Any(v) = self {
201                        v.insert(index, value);
202                    }
203                }
204            },
205            Self::Float { validity, values } => match value {
206                ResultData::Float(f) => {
207                    validity.insert(index, true);
208                    values.insert(index, f);
209                }
210                ResultData::Integer(i) => {
211                    validity.insert(index, true);
212                    values.insert(index, i as f64);
213                }
214                ResultData::None => {
215                    validity.insert(index, false);
216                    values.insert(index, 0.0);
217                }
218                _ => {
219                    self.demote_to_any();
220                    if let Self::Any(v) = self {
221                        v.insert(index, value);
222                    }
223                }
224            },
225            Self::Any(v) => {
226                v.insert(index, value);
227            }
228        }
229    }
230
231    pub(crate) fn remove(&mut self, index: usize) {
232        match self {
233            Self::Integer { validity, values } => {
234                validity.remove(index);
235                values.remove(index);
236            }
237            Self::Float { validity, values } => {
238                validity.remove(index);
239                values.remove(index);
240            }
241            Self::Any(v) => {
242                v.remove(index);
243            }
244        }
245    }
246
247    pub(crate) fn drain<R: std::ops::RangeBounds<usize> + Clone>(&mut self, range: R) {
248        match self {
249            Self::Integer { validity, values } => {
250                validity.drain(range.clone());
251                values.drain(range);
252            }
253            Self::Float { validity, values } => {
254                validity.drain(range.clone());
255                values.drain(range);
256            }
257            Self::Any(v) => {
258                v.drain(range);
259            }
260        }
261    }
262}
263
264impl Default for ColumnData {
265    fn default() -> Self {
266        Self::Integer {
267            validity: Bitmask::with_size(0),
268            values: SharedVec::new(),
269        }
270    }
271}
272
273/// One column of a sheet: the raw text, the computed values, the cell types,
274/// the compiled formulas and the styles, as parallel per-row vectors.
275///
276/// # Invariant
277///
278/// `src`, `data`, `cell_types`, `compiled_src` and `styles` must all stay the
279/// same length -- row `r` of the column is entry `r` of each. Nothing
280/// enforces this; the row and column insert/delete paths in `Sheet` maintain it
281/// by hand, and `Sheet::setup_after_deserialization` restores it after a load,
282/// since only `src` and `styles` are persisted. Mutating one of these vectors
283/// directly will break it.
284///
285/// `dirty_indices` is not part of that invariant -- it is a queue of rows
286/// awaiting recomputation, and is emptied by `Sheet::commit`.
287#[derive(Debug, Clone, Serialize, Deserialize)]
288pub struct DataColumn {
289    /// Identifier, stable across renames and repositioning. Compiled formulas
290    /// refer to a column by this rather than by name or position.
291    #[serde(default = "generate_unique_id")]
292    pub id: u64,
293    /// Display name, empty unless one was set.
294    #[serde(default)]
295    pub name: String,
296    /// [AI-Agent] Excel/OpenXML column width in character units, when one was explicitly stored.
297    #[serde(default)]
298    pub width: Option<f64>,
299    /// The computed values. Rebuilt on load, so not persisted.
300    #[serde(skip, default)]
301    pub(crate) data: ColumnData,
302    /// The raw text of each cell, exactly as typed. The only representation
303    /// that is persisted, and the one everything else is rebuilt from.
304    pub(crate) src: SharedVec<String>,
305    /// Intrinsic cell data types, matching Excel / OpenXML representations.
306    #[serde(default)]
307    pub(crate) cell_types: SharedVec<CellType>,
308    /// Cached compile output for each cell. Rebuilt on load.
309    #[serde(skip, default)]
310    pub(crate) compiled_src: SharedVec<CompiledFormula>,
311    /// Rows awaiting recomputation. Drained by `Sheet::commit`.
312    #[serde(skip, default)]
313    pub(crate) dirty_indices: SharedVec<usize>,
314    /// Per-cell styling, `None` where a cell has none. Carries a date cell's
315    /// number format.
316    #[serde(default)]
317    pub(crate) styles: SharedVec<Option<crate::core::CellStyle>>,
318}
319
320pub(crate) struct ColumnPosition {
321    pub row: usize,
322    pub char_offset: usize,
323}
324
325impl DataColumn {
326    /// A column of `size` empty rows, with every parallel vector sized to
327    /// match and a freshly generated id.
328    pub fn new(size: usize) -> Self {
329        Self {
330            id: generate_unique_id(),
331            name: String::new(),
332            width: None,
333            data: ColumnData::new(size),
334            src: vec![String::new(); size].into(),
335            cell_types: vec![CellType::Empty; size].into(),
336            compiled_src: vec![CompiledFormula::default(); size].into(),
337            dirty_indices: SharedVec::new(),
338            styles: vec![None; size].into(),
339        }
340    }
341
342    /// Rows in the column. Every parallel vector has this length.
343    pub fn len(&self) -> usize {
344        self.src.len()
345    }
346
347    /// Whether the column has no rows.
348    pub fn is_empty(&self) -> bool {
349        self.src.is_empty()
350    }
351
352    /// The raw text of a cell, exactly as typed, or `None` past the end.
353    pub fn src(&self, row: usize) -> Option<&str> {
354        self.src.get(row).map(String::as_str)
355    }
356
357    /// The computed value of a cell, or `None` past the end.
358    ///
359    /// Reflects the last `Sheet::commit`; a cell edited since then still
360    /// reads as its old value.
361    pub fn value(&self, row: usize) -> Option<ResultData> {
362        self.data.get(row)
363    }
364
365    /// The whole value column, for callers that want to work with the typed
366    /// representation rather than row by row.
367    pub fn values(&self) -> &ColumnData {
368        &self.data
369    }
370
371    /// The intrinsic data type of a cell, or `None` past the end.
372    pub fn cell_type(&self, row: usize) -> Option<CellType> {
373        self.cell_types.get(row).copied()
374    }
375
376    /// Sets the intrinsic data type of a cell at `row`.
377    pub fn set_cell_type(&mut self, row: usize, cell_type: CellType) {
378        if row < self.cell_types.len() {
379            self.cell_types[row] = cell_type;
380        }
381    }
382
383    /// A cell's compiled formula, or `None` past the end. A cell holding a
384    /// literal has an empty one rather than no entry.
385    pub fn compiled(&self, row: usize) -> Option<&CompiledFormula> {
386        self.compiled_src.get(row)
387    }
388
389    /// A cell's style, or `None` if it has none or is past the end.
390    pub fn style(&self, row: usize) -> Option<&crate::core::CellStyle> {
391        self.styles.get(row).and_then(Option::as_ref)
392    }
393
394    pub(crate) fn mark_dirty(&mut self, row: usize) {
395        if !self.dirty_indices.contains(&row) {
396            self.dirty_indices.push(row);
397        }
398    }
399
400    #[cfg(test)]
401    pub(crate) fn from_src(name: impl Into<String>, src: Vec<String>) -> Self {
402        let mut col = Self::new(src.len());
403        col.name = name.into();
404        col.src = src.into();
405        col
406    }
407
408    pub(crate) fn rebuild_after_load(&mut self) {
409        let size = self.src.len();
410        self.data.resize(size);
411        self.cell_types.resize(size, CellType::Empty);
412        self.compiled_src = vec![CompiledFormula::default(); size].into();
413        self.styles.resize(size, None);
414    }
415
416    pub(crate) fn push_row(&mut self) {
417        self.src.push(String::new());
418        self.cell_types.push(CellType::Empty);
419        self.compiled_src.push(CompiledFormula::default());
420        self.data.push(ResultData::None);
421        self.styles.push(None);
422    }
423
424    pub(crate) fn insert_row(&mut self, index: usize) {
425        if index >= self.len() {
426            self.push_row();
427            return;
428        }
429        self.src.insert(index, String::new());
430        self.cell_types.insert(index, CellType::Empty);
431        self.compiled_src.insert(index, CompiledFormula::default());
432        self.data.insert(index, ResultData::None);
433        self.styles.insert(index, None);
434        self.shift_dirty_after_insert(index, 1);
435    }
436
437    pub(crate) fn remove_row(&mut self, index: usize) {
438        if index >= self.len() {
439            return;
440        }
441        self.src.remove(index);
442        self.cell_types.remove(index);
443        self.compiled_src.remove(index);
444        self.data.remove(index);
445        self.styles.remove(index);
446        self.drop_dirty_range(index, index + 1);
447    }
448
449    pub(crate) fn drain_rows<R: std::ops::RangeBounds<usize>>(&mut self, range: R) {
450        let start = match range.start_bound() {
451            std::ops::Bound::Included(&n) => n,
452            std::ops::Bound::Excluded(&n) => n + 1,
453            std::ops::Bound::Unbounded => 0,
454        };
455        let end = match range.end_bound() {
456            std::ops::Bound::Included(&n) => n + 1,
457            std::ops::Bound::Excluded(&n) => n,
458            std::ops::Bound::Unbounded => self.len(),
459        };
460        let start = start.min(self.len());
461        let end = end.min(self.len());
462        if start >= end {
463            return;
464        }
465        self.src.drain(start..end);
466        self.cell_types.drain(start..end);
467        self.compiled_src.drain(start..end);
468        self.data.drain(start..end);
469        self.styles.drain(start..end);
470        self.drop_dirty_range(start, end);
471    }
472
473    pub(crate) fn resize_rows(&mut self, len: usize) {
474        while self.len() < len {
475            self.push_row();
476        }
477        if self.len() > len {
478            self.drain_rows(len..);
479        }
480    }
481
482    fn drop_dirty_range(&mut self, start: usize, end: usize) {
483        let removed = end - start;
484        self.dirty_indices.retain(|&i| i < start || i >= end);
485        for i in self.dirty_indices.iter_mut() {
486            if *i >= end {
487                *i -= removed;
488            }
489        }
490    }
491
492    fn shift_dirty_after_insert(&mut self, index: usize, count: usize) {
493        for i in self.dirty_indices.iter_mut() {
494            if *i >= index {
495                *i += count;
496            }
497        }
498    }
499
500    pub(crate) fn insert(&mut self, position: ColumnPosition, input: &str) {
501        let ColumnPosition { row, char_offset } = position;
502        let index = row;
503        if index < self.src.len() {
504            if self.src[index].is_empty() {
505                self.src[index].push_str(input);
506            } else {
507                self.src[index].insert_str(char_offset, input);
508            }
509        } else {
510            self.resize_rows(index + 1);
511            self.src[index] = input.to_string();
512        }
513    }
514}