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