Skip to main content

visi_core/core/engine/
cell.rs

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