Skip to main content

visi_core/core/engine/
column.rs

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