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