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    /// The computed values. Rebuilt on load, so not persisted.
297    #[serde(skip, default)]
298    pub(crate) data: ColumnData,
299    /// The raw text of each cell, exactly as typed. The only representation
300    /// that is persisted, and the one everything else is rebuilt from.
301    pub(crate) src: SharedVec<String>,
302    /// Intrinsic cell data types, matching Excel / OpenXML representations.
303    #[serde(default)]
304    pub(crate) cell_types: SharedVec<CellType>,
305    /// Cached compile output for each cell. Rebuilt on load.
306    #[serde(skip, default)]
307    pub(crate) compiled_src: SharedVec<CompiledFormula>,
308    /// Rows awaiting recomputation. Drained by `Sheet::commit`.
309    #[serde(skip, default)]
310    pub(crate) dirty_indices: SharedVec<usize>,
311    /// Per-cell styling, `None` where a cell has none. Carries a date cell's
312    /// number format.
313    #[serde(default)]
314    pub(crate) styles: SharedVec<Option<crate::core::CellStyle>>,
315}
316
317pub(crate) struct ColumnPosition {
318    pub row: usize,
319    pub char_offset: usize,
320}
321
322impl DataColumn {
323    /// A column of `size` empty rows, with every parallel vector sized to
324    /// match and a freshly generated id.
325    pub fn new(size: usize) -> Self {
326        Self {
327            id: generate_unique_id(),
328            name: String::new(),
329            data: ColumnData::new(size),
330            src: vec![String::new(); size].into(),
331            cell_types: vec![CellType::Auto; size].into(),
332            compiled_src: vec![CompiledFormula::default(); size].into(),
333            dirty_indices: SharedVec::new(),
334            styles: vec![None; size].into(),
335        }
336    }
337
338    /// Rows in the column. Every parallel vector has this length.
339    pub fn len(&self) -> usize {
340        self.src.len()
341    }
342
343    /// Whether the column has no rows.
344    pub fn is_empty(&self) -> bool {
345        self.src.is_empty()
346    }
347
348    /// The raw text of a cell, exactly as typed, or `None` past the end.
349    pub fn src(&self, row: usize) -> Option<&str> {
350        self.src.get(row).map(String::as_str)
351    }
352
353    /// The computed value of a cell, or `None` past the end.
354    ///
355    /// Reflects the last `Sheet::commit`; a cell edited since then still
356    /// reads as its old value.
357    pub fn value(&self, row: usize) -> Option<ResultData> {
358        self.data.get(row)
359    }
360
361    /// The whole value column, for callers that want to work with the typed
362    /// representation rather than row by row.
363    pub fn values(&self) -> &ColumnData {
364        &self.data
365    }
366
367    /// The intrinsic data type of a cell, or `None` past the end.
368    pub fn cell_type(&self, row: usize) -> Option<CellType> {
369        self.cell_types.get(row).copied()
370    }
371
372    /// Sets the intrinsic data type of a cell at `row`.
373    pub fn set_cell_type(&mut self, row: usize, cell_type: CellType) {
374        if row < self.cell_types.len() {
375            self.cell_types[row] = cell_type;
376        }
377    }
378
379    /// A cell's compiled formula, or `None` past the end. A cell holding a
380    /// literal has an empty one rather than no entry.
381    pub fn compiled(&self, row: usize) -> Option<&CompiledFormula> {
382        self.compiled_src.get(row)
383    }
384
385    /// A cell's style, or `None` if it has none or is past the end.
386    pub fn style(&self, row: usize) -> Option<&crate::core::CellStyle> {
387        self.styles.get(row).and_then(Option::as_ref)
388    }
389
390    pub(crate) fn mark_dirty(&mut self, row: usize) {
391        if !self.dirty_indices.contains(&row) {
392            self.dirty_indices.push(row);
393        }
394    }
395
396    /// A named column holding `src`, with every parallel vector sized to
397    /// match.
398    ///
399    /// The values start empty -- `Sheet::commit` is what fills them in from
400    /// the source text. Test-only: production builds sheets through
401    /// `Sheet::new` and `ensure_capacity`.
402    #[cfg(test)]
403    pub(crate) fn from_src(name: impl Into<String>, src: Vec<String>) -> Self {
404        let mut col = Self::new(src.len());
405        col.name = name.into();
406        col.src = src.into();
407        col
408    }
409
410    /// Rebuilds what serialization drops, restoring the length invariant.
411    ///
412    /// Only `src`, `cell_types` and `styles` are persisted, and `styles`/`cell_types`
413    /// are optional, so a workbook saved without them loads with a length of 0. Everything
414    /// is sized back to `src`, which is the authoritative length.
415    pub(crate) fn rebuild_after_load(&mut self) {
416        let size = self.src.len();
417        self.data.resize(size);
418        self.cell_types.resize(size, CellType::Auto);
419        self.compiled_src = vec![CompiledFormula::default(); size].into();
420        self.styles.resize(size, None);
421    }
422
423    /// Appends an empty row to every parallel vector.
424    pub(crate) fn push_row(&mut self) {
425        self.src.push(String::new());
426        self.cell_types.push(CellType::Auto);
427        self.compiled_src.push(CompiledFormula::default());
428        self.data.push(ResultData::None);
429        self.styles.push(None);
430    }
431
432    /// Inserts an empty row at `index` in every parallel vector, shifting the
433    /// rows below it down. Appends if `index` is at or past the end.
434    pub(crate) fn insert_row(&mut self, index: usize) {
435        if index >= self.len() {
436            self.push_row();
437            return;
438        }
439        self.src.insert(index, String::new());
440        self.cell_types.insert(index, CellType::Auto);
441        self.compiled_src.insert(index, CompiledFormula::default());
442        self.data.insert(index, ResultData::None);
443        self.styles.insert(index, None);
444        self.shift_dirty_after_insert(index, 1);
445    }
446
447    /// Removes row `index` from every parallel vector, shifting the rows below
448    /// it up. Ignored if `index` is past the end.
449    pub(crate) fn remove_row(&mut self, index: usize) {
450        if index >= self.len() {
451            return;
452        }
453        self.src.remove(index);
454        self.cell_types.remove(index);
455        self.compiled_src.remove(index);
456        self.data.remove(index);
457        self.styles.remove(index);
458        self.drop_dirty_range(index, index + 1);
459    }
460
461    /// Removes a range of rows from every parallel vector.
462    ///
463    /// The range is clamped to the column's length, so an out-of-range end is
464    /// not an error.
465    pub(crate) fn drain_rows<R: std::ops::RangeBounds<usize>>(&mut self, range: R) {
466        let start = match range.start_bound() {
467            std::ops::Bound::Included(&n) => n,
468            std::ops::Bound::Excluded(&n) => n + 1,
469            std::ops::Bound::Unbounded => 0,
470        };
471        let end = match range.end_bound() {
472            std::ops::Bound::Included(&n) => n + 1,
473            std::ops::Bound::Excluded(&n) => n,
474            std::ops::Bound::Unbounded => self.len(),
475        };
476        let start = start.min(self.len());
477        let end = end.min(self.len());
478        if start >= end {
479            return;
480        }
481        self.src.drain(start..end);
482        self.cell_types.drain(start..end);
483        self.compiled_src.drain(start..end);
484        self.data.drain(start..end);
485        self.styles.drain(start..end);
486        self.drop_dirty_range(start, end);
487    }
488
489    /// Grows or shrinks every parallel vector to `len` rows, filling with
490    /// empties when growing.
491    pub(crate) fn resize_rows(&mut self, len: usize) {
492        while self.len() < len {
493            self.push_row();
494        }
495        if self.len() > len {
496            self.drain_rows(len..);
497        }
498    }
499
500    /// Drops queued rows in `start..end` and rebases those below it.
501    fn drop_dirty_range(&mut self, start: usize, end: usize) {
502        let removed = end - start;
503        self.dirty_indices.retain(|&i| i < start || i >= end);
504        for i in self.dirty_indices.iter_mut() {
505            if *i >= end {
506                *i -= removed;
507            }
508        }
509    }
510
511    /// Rebases queued rows at or below `index` after an insert.
512    fn shift_dirty_after_insert(&mut self, index: usize, count: usize) {
513        for i in self.dirty_indices.iter_mut() {
514            if *i >= index {
515                *i += count;
516            }
517        }
518    }
519
520    /// Row is absolutely referenced
521    pub(crate) fn insert(&mut self, position: ColumnPosition, input: &str) {
522        let ColumnPosition { row, char_offset } = position;
523        let index = row;
524        if index < self.src.len() {
525            if self.src[index].is_empty() {
526                self.src[index].push_str(input);
527            } else {
528                self.src[index].insert_str(char_offset, input);
529            }
530        } else {
531            // Grow to cover `index`, then write into the new last row.
532            self.resize_rows(index + 1);
533            self.src[index] = input.to_string();
534        }
535    }
536}