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}