Skip to main content

visi_core/core/
formula.rs

1//! Formula text with its references resolved to ids.
2//!
3//! `parser::compile_formula` turns formula text into a [`CompiledFormula`]:
4//! the literal stretches stay text, but every reference becomes a `sheet_id`
5//! or `col_id` rather than a name. `parser::serialize_formula` renders it back
6//! to A1 text using whatever the names are *now*, which is what makes renaming
7//! a sheet, an Excel Table or a table column non-destructive -- nothing has to
8//! find and rewrite the formulas that mention it.
9//!
10//! This is not the evaluation form. Evaluating goes through
11//! `parser::parse_excel_formula`, which produces an AST; `Sheet::commit`
12//! compiles, re-serializes, and then evaluates the re-serialized text.
13
14use crate::core::RefType;
15use serde::{Deserialize, Serialize};
16
17/// Which part of an Excel Table a structured reference selects, as in
18/// `Sales[#Headers]`.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
20pub enum SheetSection {
21    /// The body rows, excluding header and totals. The default.
22    Data,
23    /// The header row.
24    Headers,
25    /// The totals row.
26    Totals,
27    /// Header, data and totals together.
28    All,
29}
30
31/// One piece of a compiled formula: either literal text or a reference held by
32/// id.
33#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
34pub enum FormulaPart {
35    /// A literal stretch of the formula -- operators, function names,
36    /// constants -- copied through unchanged.
37    Text(String),
38    /// A single cell, as in `Sheet2!$A1`.
39    SheetReference {
40        /// Sheet the cell is on.
41        sheet_id: u64,
42        /// Row, 0-based.
43        row: usize,
44        /// Column, 0-based.
45        col: usize,
46        /// Whether the row was written with a `$`.
47        row_ref_type: RefType,
48        /// Whether the column was written with a `$`.
49        col_ref_type: RefType,
50    },
51    /// A whole column, held by column id so a column rename survives.
52    ColumnReference {
53        /// Sheet the column is on.
54        sheet_id: u64,
55        /// The column's identifier, not its position.
56        col_id: u64,
57    },
58    /// An Excel Table structured reference, as in `Sales[Amount]` or
59    /// `[@Amount]`.
60    StructuredReference {
61        /// Sheet the reference resolves against.
62        sheet_id: u64,
63        /// The referenced column, or `None` for a whole-table reference.
64        col_id: Option<u64>,
65        /// `true` for the `[@Amount]` form, which means the current row.
66        is_this_row: bool,
67        /// Which part of the table is selected.
68        section: SheetSection,
69    },
70    /// A rectangular range, as in `Sheet2!A1:$B$10`.
71    RangeReference {
72        /// Sheet the range is on.
73        sheet_id: u64,
74        /// First row, 0-based.
75        start_row: usize,
76        /// First column, 0-based.
77        start_col: usize,
78        /// Last row, 0-based and inclusive.
79        end_row: usize,
80        /// Last column, 0-based and inclusive.
81        end_col: usize,
82        /// Whether the start row was written with a `$`.
83        start_row_ref_type: RefType,
84        /// Whether the start column was written with a `$`.
85        start_col_ref_type: RefType,
86        /// Whether the end row was written with a `$`.
87        end_row_ref_type: RefType,
88        /// Whether the end column was written with a `$`.
89        end_col_ref_type: RefType,
90    },
91}
92
93/// A formula split into literal text and id-held references.
94///
95/// Cached per cell in `DataColumn::compiled_src`, and rendered back to A1 text
96/// on demand by `parser::serialize_formula`.
97#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
98pub struct CompiledFormula {
99    /// The pieces, in the order they appear in the formula text.
100    pub parts: Vec<FormulaPart>,
101}
102
103impl CompiledFormula {
104    /// Creates a plain formula from a raw string, without any parsed references.
105    /// Useful as a default constructor or fallback.
106    pub fn plain(text: String) -> Self {
107        Self {
108            parts: vec![FormulaPart::Text(text)],
109        }
110    }
111
112    /// Checks if the formula is empty
113    #[allow(dead_code)]
114    pub fn is_empty(&self) -> bool {
115        self.parts.is_empty()
116            || (self.parts.len() == 1
117                && match &self.parts[0] {
118                    FormulaPart::Text(s) => s.is_empty(),
119                    _ => false,
120                })
121    }
122}
123
124impl Default for CompiledFormula {
125    fn default() -> Self {
126        Self::plain(String::new())
127    }
128}