Skip to main content

visi_core/core/engine/
cell.rs

1use serde::{Deserialize, Serialize};
2
3/// A random 53-bit identifier for a sheet or column.
4///
5/// Capped to `2^53 - 1` so it survives a round trip through a JSON number,
6/// which is what a JavaScript host would deserialize it as. Falls back to the
7/// wall clock if the system random source is unavailable.
8pub fn generate_unique_id() -> u64 {
9    let mut buf = [0u8; 8];
10    let val = if getrandom::getrandom(&mut buf).is_err() {
11        let now = web_time::SystemTime::now()
12            .duration_since(web_time::SystemTime::UNIX_EPOCH)
13            .map(|d| d.as_nanos())
14            .unwrap_or(0);
15        now as u64
16    } else {
17        u64::from_le_bytes(buf)
18    };
19    val & 0x001F_FFFF_FFFF_FFFF
20}
21
22/// The intrinsic data type of a cell, matching Excel / OpenXML `<c t="...">` representations.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
24pub enum CellType {
25    /// Inferred on commit (default for newly created / untyped cells).
26    #[default]
27    Auto,
28    /// Blank / empty cell.
29    Empty,
30    /// Numeric cell (OOXML `t="n"` or omitted `t`). Includes date/time serials.
31    Number,
32    /// String / text cell (OOXML `t="s"`, `t="inlineStr"`, `t="str"`, or input with leading `'`).
33    String,
34    /// Boolean cell (OOXML `t="b"`).
35    Boolean,
36    /// Error cell (OOXML `t="e"`).
37    Error,
38    /// Formula cell.
39    Formula,
40}
41
42impl CellType {
43    /// Whether this cell type is explicitly a string/text cell.
44    pub fn is_string(&self) -> bool {
45        matches!(self, CellType::String)
46    }
47}
48
49/// For either a column or row
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
51pub enum RefType {
52    /// Written without a `$`, so it shifts when the formula is filled or
53    /// copied.
54    Relative,
55    /// Written with a `$`, so it stays put.
56    Absolute,
57}
58
59impl std::fmt::Display for RefType {
60    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61        match self {
62            RefType::Relative => write!(f, ""),
63            RefType::Absolute => write!(f, "$"),
64        }
65    }
66}
67
68/// A cell's position, plus whether it was written as absolute.
69///
70/// Coordinates are 0-based, as everywhere inside the engine; `A1` is
71/// `CellRef::new(0, 0)`.
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
73pub struct CellRef {
74    /// Row index, 0-based.
75    pub row: usize,
76    /// Column index, 0-based.
77    pub col: usize,
78    /// Whether the row was written with a `$`.
79    pub row_ref_type: RefType,
80    /// Whether the column was written with a `$`.
81    pub col_ref_type: RefType,
82}
83
84impl std::fmt::Display for CellRef {
85    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86        write!(
87            f,
88            "CELL({}{}, {}{})",
89            self.row_ref_type, self.row, self.col_ref_type, self.col
90        )
91    }
92}
93
94impl CellRef {
95    /// A relative reference to `(row, col)`, 0-based.
96    pub fn new(row: usize, col: usize) -> CellRef {
97        Self {
98            row,
99            col,
100            row_ref_type: RefType::Relative,
101            col_ref_type: RefType::Relative,
102        }
103    }
104}
105
106/// Something a formula reads, and therefore an edge in the recalculation
107/// graph.
108///
109/// The local/remote split is load-bearing. `Sheet::commit` propagates through
110/// the `Local` variants only -- a sheet cannot reach into its neighbors, so a
111/// remote edge it finds is recorded but not followed. Chasing those is
112/// `WorkbookManager::evaluate`'s job, which marks every sheet dirty and runs a
113/// fixed number of passes over the workbook; a cross-sheet chain deeper than
114/// that number of hops will not have converged when it stops.
115///
116/// Remote variants key on the sheet *name* rather than its id, since that is
117/// what a formula's text carries and what `Context` is indexed by.
118#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
119pub enum Dependency {
120    /// A cell on the same sheet.
121    Local(CellRef),
122    /// A whole column on the same sheet, by 0-based position.
123    LocalColumn(usize),
124    /// A cell on another sheet.
125    Remote {
126        /// Name of the sheet the cell is on.
127        sheet: String,
128        /// The cell, on that sheet.
129        cell: CellRef,
130    },
131    /// A whole column on another sheet.
132    RemoteColumn {
133        /// Name of the sheet the column is on.
134        sheet: String,
135        /// Column index, 0-based.
136        col: usize,
137    },
138}
139
140/// A caret position: a cell plus an offset within its source text, for the
141/// text-editing operations `Sheet::insert` and `Sheet::delete`.
142#[derive(Debug, Clone, Default)]
143pub struct TextCellRef {
144    /// Row index, 0-based.
145    pub row: usize,
146    /// Column index, 0-based.
147    pub col: usize,
148    /// Offset into the cell's source text, in characters rather than bytes.
149    pub char_offset: usize,
150}
151
152/// A formula that could not be evaluated at all.
153///
154/// Distinct from an Excel error value: `=1/0` evaluates successfully to
155/// `ResultData::Error("#DIV/0!")`, whereas this is for text that never became
156/// a computable formula.
157#[derive(Debug, Clone, PartialEq, Eq)]
158pub enum EvalError {
159    /// The formula could not be parsed, or named something unrecognized. The
160    /// string is the message, which for some failures is an Excel error code.
161    UnknownFunction(String),
162}
163
164impl std::error::Error for EvalError {}
165
166impl std::fmt::Display for EvalError {
167    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168        match self {
169            EvalError::UnknownFunction(func) => write!(f, "{}", func),
170        }
171    }
172}
173
174/// What the engine's evaluation entry points return on failure.
175#[derive(Debug, Clone, PartialEq, Eq)]
176pub enum EngineError {
177    /// A formula could not be evaluated.
178    EvalError(EvalError),
179}
180
181impl std::error::Error for EngineError {
182    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
183        match self {
184            EngineError::EvalError(err) => Some(err),
185        }
186    }
187}
188
189impl std::fmt::Display for EngineError {
190    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
191        match self {
192            EngineError::EvalError(err) => write!(f, "{}", err),
193        }
194    }
195}
196
197impl From<EvalError> for EngineError {
198    fn from(err: EvalError) -> Self {
199        EngineError::EvalError(err)
200    }
201}