Skip to main content

excelize_rs/calc/
arg.rs

1//! Formula argument representation.
2//!
3//! This mirrors the Go `formulaArg` struct so that the 450+ Excel formula
4//! functions can be translated mechanically.
5
6use std::fmt;
7
8use crate::calc::CellRef;
9
10/// Excel formula argument data type.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum ArgType {
13    Unknown,
14    Number,
15    String,
16    List,
17    Matrix,
18    Error,
19    Empty,
20}
21
22/// A single formula argument, value, or result.
23#[derive(Debug, Clone)]
24pub struct FormulaArg {
25    pub sheet_name: String,
26    pub number: f64,
27    pub string: String,
28    pub list: Vec<FormulaArg>,
29    pub matrix: Vec<Vec<FormulaArg>>,
30    pub boolean: bool,
31    pub error: String,
32    pub cell_refs: Vec<CellRef>,
33    pub cell_ranges: Vec<(CellRef, CellRef)>,
34    pub typ: ArgType,
35}
36
37impl Default for FormulaArg {
38    fn default() -> Self {
39        new_empty_formula_arg()
40    }
41}
42
43/// Create an empty/unset formula argument.
44pub fn new_empty_formula_arg() -> FormulaArg {
45    FormulaArg {
46        sheet_name: String::new(),
47        number: 0.0,
48        string: String::new(),
49        list: Vec::new(),
50        matrix: Vec::new(),
51        boolean: false,
52        error: String::new(),
53        cell_refs: Vec::new(),
54        cell_ranges: Vec::new(),
55        typ: ArgType::Empty,
56    }
57}
58
59/// Create a numeric formula argument.
60pub fn new_number_formula_arg(n: f64) -> FormulaArg {
61    FormulaArg {
62        sheet_name: String::new(),
63        number: n,
64        string: String::new(),
65        list: Vec::new(),
66        matrix: Vec::new(),
67        boolean: false,
68        error: String::new(),
69        cell_refs: Vec::new(),
70        cell_ranges: Vec::new(),
71        typ: ArgType::Number,
72    }
73}
74
75/// Create a string formula argument.
76pub fn new_string_formula_arg(s: impl Into<String>) -> FormulaArg {
77    FormulaArg {
78        sheet_name: String::new(),
79        number: 0.0,
80        string: s.into(),
81        list: Vec::new(),
82        matrix: Vec::new(),
83        boolean: false,
84        error: String::new(),
85        cell_refs: Vec::new(),
86        cell_ranges: Vec::new(),
87        typ: ArgType::String,
88    }
89}
90
91/// Create a boolean formula argument.  In Excel compatibility booleans are
92/// stored as numbers with the boolean flag set.
93pub fn new_bool_formula_arg(b: bool) -> FormulaArg {
94    FormulaArg {
95        sheet_name: String::new(),
96        number: if b { 1.0 } else { 0.0 },
97        string: String::new(),
98        list: Vec::new(),
99        matrix: Vec::new(),
100        boolean: true,
101        error: String::new(),
102        cell_refs: Vec::new(),
103        cell_ranges: Vec::new(),
104        typ: ArgType::Number,
105    }
106}
107
108/// Create an error formula argument.
109pub fn new_error_formula_arg(err: impl Into<String>) -> FormulaArg {
110    FormulaArg {
111        sheet_name: String::new(),
112        number: 0.0,
113        string: String::new(),
114        list: Vec::new(),
115        matrix: Vec::new(),
116        boolean: false,
117        error: err.into(),
118        cell_refs: Vec::new(),
119        cell_ranges: Vec::new(),
120        typ: ArgType::Error,
121    }
122}
123
124/// Create a one-dimensional list argument.
125pub fn new_list_formula_arg(list: Vec<FormulaArg>) -> FormulaArg {
126    FormulaArg {
127        sheet_name: String::new(),
128        number: 0.0,
129        string: String::new(),
130        list,
131        matrix: Vec::new(),
132        boolean: false,
133        error: String::new(),
134        cell_refs: Vec::new(),
135        cell_ranges: Vec::new(),
136        typ: ArgType::List,
137    }
138}
139
140/// Create a two-dimensional matrix argument.
141pub fn new_matrix_formula_arg(matrix: Vec<Vec<FormulaArg>>) -> FormulaArg {
142    FormulaArg {
143        sheet_name: String::new(),
144        number: 0.0,
145        string: String::new(),
146        list: Vec::new(),
147        matrix,
148        boolean: false,
149        error: String::new(),
150        cell_refs: Vec::new(),
151        cell_ranges: Vec::new(),
152        typ: ArgType::Matrix,
153    }
154}
155
156impl FormulaArg {
157    /// Return the Excel textual representation of this argument.
158    pub fn value(&self) -> String {
159        match self.typ {
160            ArgType::Number => {
161                if self.boolean {
162                    if self.number == 0.0 {
163                        "FALSE".to_string()
164                    } else {
165                        "TRUE".to_string()
166                    }
167                } else {
168                    format!("{}", self.number)
169                }
170            }
171            ArgType::String => self.string.clone(),
172            ArgType::Matrix => {
173                if let Some(first) = self.to_list().first() {
174                    return first.value();
175                }
176                String::new()
177            }
178            ArgType::Error => self.error.clone(),
179            _ => String::new(),
180        }
181    }
182
183    /// Convert the argument to a number argument, returning `#VALUE!` on
184    /// failure.
185    pub fn to_number(&self) -> FormulaArg {
186        match self.typ {
187            ArgType::String => match self.string.parse::<f64>() {
188                Ok(n) => new_number_formula_arg(n),
189                Err(_) => new_error_formula_arg("#VALUE!"),
190            },
191            ArgType::Number => new_number_formula_arg(self.number),
192            ArgType::Matrix => {
193                if let Some(first) = self.to_list().first() {
194                    return first.to_number();
195                }
196                new_number_formula_arg(0.0)
197            }
198            ArgType::Empty => new_number_formula_arg(0.0),
199            _ => new_error_formula_arg("#VALUE!"),
200        }
201    }
202
203    /// Convert the argument to a boolean argument.
204    pub fn to_bool(&self) -> FormulaArg {
205        match self.typ {
206            ArgType::String => match self.string.to_lowercase().parse::<bool>() {
207                Ok(b) => new_bool_formula_arg(b),
208                Err(_) => new_error_formula_arg("#VALUE!"),
209            },
210            ArgType::Number => new_bool_formula_arg(self.number == 1.0),
211            ArgType::Matrix => {
212                if let Some(first) = self.to_list().first() {
213                    return first.to_bool();
214                }
215                new_bool_formula_arg(false)
216            }
217            ArgType::Empty => new_bool_formula_arg(false),
218            _ => new_error_formula_arg("#VALUE!"),
219        }
220    }
221
222    /// Flatten a matrix/list/number/string/error to a one-dimensional list.
223    pub fn to_list(&self) -> Vec<FormulaArg> {
224        match self.typ {
225            ArgType::Matrix => self
226                .matrix
227                .iter()
228                .flat_map(|row| row.iter().cloned())
229                .collect(),
230            ArgType::List => self.list.clone(),
231            ArgType::Empty => Vec::new(),
232            _ => vec![self.clone()],
233        }
234    }
235
236    /// Try to interpret the argument as a number.
237    pub fn as_number(&self) -> Option<f64> {
238        match self.typ {
239            ArgType::Number => Some(self.number),
240            ArgType::String => self.string.parse::<f64>().ok(),
241            ArgType::Empty => Some(0.0),
242            _ => None,
243        }
244    }
245
246    /// Interpret the argument as a boolean.
247    pub fn as_bool(&self) -> bool {
248        match self.typ {
249            ArgType::Empty => false,
250            ArgType::Number => self.number != 0.0,
251            ArgType::String => !self.string.is_empty(),
252            ArgType::Error => false,
253            ArgType::List => !self.list.is_empty(),
254            ArgType::Matrix => !self.matrix.is_empty(),
255            ArgType::Unknown => false,
256        }
257    }
258
259    /// Convert the argument to its string representation.
260    pub fn as_string(&self) -> String {
261        match self.typ {
262            ArgType::Number => {
263                if self.number.is_nan() || self.number.is_infinite() {
264                    return "#NUM!".to_string();
265                }
266                if self.boolean {
267                    return self.value();
268                }
269                if self.number.fract() == 0.0 {
270                    format!("{}", self.number as i64)
271                } else {
272                    format!("{}", self.number)
273                }
274            }
275            ArgType::String => self.string.clone(),
276            ArgType::Empty => String::new(),
277            ArgType::Error => self.error.clone(),
278            ArgType::List | ArgType::Matrix => String::new(),
279            ArgType::Unknown => String::new(),
280        }
281    }
282
283    /// Return `true` if this argument represents an Excel error.
284    pub fn is_error(&self) -> bool {
285        self.typ == ArgType::Error
286    }
287}
288
289impl fmt::Display for FormulaArg {
290    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291        write!(f, "{}", self.value())
292    }
293}
294
295/// Collect all numeric values from a (possibly nested) argument into `out`,
296/// propagating the first error found.  Returns the error argument if one is
297/// encountered.
298pub fn collect_numbers(arg: &FormulaArg, out: &mut Vec<f64>) -> Option<FormulaArg> {
299    match arg.typ {
300        ArgType::Number => out.push(arg.number),
301        ArgType::List => {
302            for x in &arg.list {
303                if let Some(e) = collect_numbers(x, out) {
304                    return Some(e);
305                }
306            }
307        }
308        ArgType::Matrix => {
309            for row in &arg.matrix {
310                for x in row {
311                    if let Some(e) = collect_numbers(x, out) {
312                        return Some(e);
313                    }
314                }
315            }
316        }
317        ArgType::Error => return Some(arg.clone()),
318        _ => {}
319    }
320    None
321}
322
323/// Extract all numbers from a slice of arguments, propagating errors.
324pub fn numbers_from_args(args: &[FormulaArg]) -> (Vec<f64>, Option<FormulaArg>) {
325    let mut out = Vec::new();
326    for a in args {
327        if let Some(e) = collect_numbers(a, &mut out) {
328            return (out, Some(e));
329        }
330    }
331    (out, None)
332}
333
334/// Flatten an argument to a single string.
335pub fn flatten_string(arg: &FormulaArg) -> String {
336    match arg.typ {
337        ArgType::String => arg.string.clone(),
338        ArgType::List => arg.list.iter().map(flatten_string).collect(),
339        ArgType::Matrix => arg
340            .matrix
341            .iter()
342            .flat_map(|r| r.iter().map(flatten_string))
343            .collect(),
344        _ => arg.as_string(),
345    }
346}
347
348/// Compare two arguments for equality using Excel rules (numeric when
349/// possible, otherwise case-insensitive string comparison).
350pub fn compare_equal(a: &FormulaArg, b: &FormulaArg) -> bool {
351    if let (Some(a), Some(b)) = (a.as_number(), b.as_number()) {
352        return a == b;
353    }
354    a.as_string().to_uppercase() == b.as_string().to_uppercase()
355}
356
357/// Excel formula error constants.
358pub const FORMULA_ERROR_DIV: &str = "#DIV/0!";
359pub const FORMULA_ERROR_NAME: &str = "#NAME?";
360pub const FORMULA_ERROR_NA: &str = "#N/A";
361pub const FORMULA_ERROR_NUM: &str = "#NUM!";
362pub const FORMULA_ERROR_VALUE: &str = "#VALUE!";
363pub const FORMULA_ERROR_REF: &str = "#REF!";
364pub const FORMULA_ERROR_NULL: &str = "#NULL!";
365pub const FORMULA_ERROR_SPILL: &str = "#SPILL!";
366pub const FORMULA_ERROR_CALC: &str = "#CALC!";
367pub const FORMULA_ERROR_GETTING_DATA: &str = "#GETTING_DATA";