Skip to main content

visi_core/core/
formula.rs

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