Skip to main content

excelize_rs/calc/
mod.rs

1//! Formula calculation engine.
2//!
3//! This is a Rust port of the Go `calc.go` formula evaluator.  The parser and
4//! evaluator are intentionally kept close to the original so that the 450+
5//! Excel functions can be translated mechanically into the `src/calc/`
6//! submodules.
7
8use std::cell::RefCell;
9use std::collections::HashMap;
10use std::sync::OnceLock;
11
12use crate::calc::arg::*;
13use crate::cell::read_cell_value;
14use crate::errors::Result;
15use crate::file::File;
16use crate::lib_util::{
17    column_name_to_number, column_number_to_name, coordinates_to_cell_name, split_cell_name,
18};
19use crate::options::Options;
20use crate::xml::shared_strings::XlsxSi;
21use crate::xml::worksheet::{XlsxC, XlsxWorksheet};
22
23pub mod arg;
24pub mod common;
25pub mod database;
26pub mod date;
27pub mod engineering;
28pub mod financial;
29pub mod info;
30pub mod logical;
31pub mod lookup;
32pub mod math;
33pub mod statistical;
34pub mod text;
35pub mod web;
36
37// ------------------------------------------------------------------
38// Public value types
39// ------------------------------------------------------------------
40
41/// A parsed cell reference.
42#[derive(Debug, Clone)]
43pub struct CellRef {
44    /// Sheet name, if explicitly qualified.
45    pub sheet: Option<String>,
46    /// 1-based column number.
47    pub col: i32,
48    /// 1-based row number.
49    pub row: i32,
50    /// Column was absolute (`$A`).
51    pub col_abs: bool,
52    /// Row was absolute (`$1`).
53    pub row_abs: bool,
54    /// Whole-column reference (e.g. `A:A`).
55    pub whole_col: bool,
56    /// Whole-row reference (e.g. `1:1`).
57    pub whole_row: bool,
58}
59
60impl CellRef {
61    /// Return the canonical `A1` style name for this reference.
62    pub fn to_cell_name(&self) -> String {
63        coordinates_to_cell_name(self.col, self.row, false).unwrap_or_default()
64    }
65}
66
67/// Evaluation context used to resolve references and detect circular
68/// dependencies.
69#[derive(Debug)]
70pub struct CalcContext<'a> {
71    pub file: &'a File,
72    pub sheet: &'a str,
73    pub cell: String,
74    pub entry: String,
75    pub stack: RefCell<Vec<(String, String)>>,
76    pub max_calc_iterations: u32,
77    pub iterations: RefCell<HashMap<String, u32>>,
78    pub iterations_cache: RefCell<HashMap<String, FormulaArg>>,
79    pub sheet_bounds: RefCell<HashMap<String, (i32, i32)>>,
80}
81
82impl<'a> CalcContext<'a> {
83    pub fn new(file: &'a File, sheet: &'a str) -> Self {
84        Self {
85            file,
86            sheet,
87            cell: String::new(),
88            entry: String::new(),
89            stack: RefCell::new(Vec::new()),
90            max_calc_iterations: 0,
91            iterations: RefCell::new(HashMap::new()),
92            iterations_cache: RefCell::new(HashMap::new()),
93            sheet_bounds: RefCell::new(HashMap::new()),
94        }
95    }
96
97    pub fn new_with_cell(file: &'a File, sheet: &'a str, cell: impl Into<String>) -> Self {
98        Self {
99            file,
100            sheet,
101            cell: cell.into(),
102            entry: String::new(),
103            stack: RefCell::new(Vec::new()),
104            max_calc_iterations: 0,
105            iterations: RefCell::new(HashMap::new()),
106            iterations_cache: RefCell::new(HashMap::new()),
107            sheet_bounds: RefCell::new(HashMap::new()),
108        }
109    }
110
111    /// Return the maximum used row and column for a sheet, cached per context.
112    pub fn worksheet_bounds(&self, sheet: &str) -> (i32, i32) {
113        if let Some(bounds) = self.sheet_bounds.borrow().get(sheet).copied() {
114            return bounds;
115        }
116        let mut max_row = 0;
117        let mut max_col = 0;
118        if let Ok(ws) = self.file.work_sheet_reader(sheet) {
119            for row in &ws.sheet_data.row {
120                if let Some(r) = row.r {
121                    max_row = max_row.max(r as i32);
122                }
123                for c in &row.c {
124                    if let Some(ref name) = c.r {
125                        if let Ok((col_name, row_num)) = split_cell_name(name) {
126                            max_row = max_row.max(row_num);
127                            if let Ok(col_num) = column_name_to_number(&col_name) {
128                                max_col = max_col.max(col_num);
129                            }
130                        }
131                    }
132                }
133            }
134        }
135        let bounds = (max_row.max(1), max_col.max(1));
136        self.sheet_bounds
137            .borrow_mut()
138            .insert(sheet.to_string(), bounds);
139        bounds
140    }
141}
142
143// ------------------------------------------------------------------
144// AST
145// ------------------------------------------------------------------
146
147#[derive(Debug, Clone)]
148pub(crate) enum Expr {
149    Number(f64),
150    String(String),
151    Bool(bool),
152    Cell(CellRef),
153    Range(CellRef, CellRef),
154    Range3D(String, String, CellRef, CellRef),
155    Name(String),
156    Array(Vec<Vec<Expr>>),
157    Call(String, Vec<Expr>),
158    Unary(String, Box<Expr>),
159    Binary(String, Box<Expr>, Box<Expr>),
160}
161
162// ------------------------------------------------------------------
163// Public parser entry point
164// ------------------------------------------------------------------
165
166pub(crate) fn parse_formula(formula: &str) -> Result<Expr> {
167    let formula = formula.trim_start_matches('=').trim();
168    if formula.is_empty() {
169        return Ok(Expr::String(String::new()));
170    }
171    let mut parser = Parser::new(formula)?;
172    let expr = parser.parse_expr()?;
173    // Go's efp-based parser stops evaluating once a complete expression has
174    // been consumed and silently ignores any trailing tokens.
175    Ok(expr)
176}
177
178// ------------------------------------------------------------------
179// Lexer
180// ------------------------------------------------------------------
181
182#[derive(Debug, Clone, PartialEq)]
183enum Token {
184    Number(f64),
185    Text(String),
186    Bool(bool),
187    Ident(String),
188    Op(String),
189    LParen,
190    RParen,
191    Comma,
192    Semicolon,
193    LBrace,
194    RBrace,
195    Eof,
196}
197
198struct Lexer<'a> {
199    input: &'a str,
200    pos: usize,
201}
202
203impl<'a> Lexer<'a> {
204    fn new(input: &'a str) -> Self {
205        Self { input, pos: 0 }
206    }
207
208    fn peek(&self) -> char {
209        self.input[self.pos..].chars().next().unwrap_or('\0')
210    }
211
212    fn peek_next(&self) -> char {
213        self.input[self.pos..].chars().nth(1).unwrap_or('\0')
214    }
215
216    fn advance(&mut self) -> char {
217        let c = self.peek();
218        self.pos += c.len_utf8();
219        c
220    }
221
222    fn skip_ws(&mut self) {
223        while self.peek().is_ascii_whitespace() {
224            self.advance();
225        }
226    }
227
228    /// Peek the next `n` tokens without advancing the lexer.
229    fn peek_tokens(&self, n: usize) -> Result<Vec<Token>> {
230        let mut tmp = Lexer::new(self.input);
231        tmp.pos = self.pos;
232        let mut tokens = Vec::with_capacity(n);
233        for _ in 0..n {
234            tokens.push(tmp.next_token()?);
235        }
236        Ok(tokens)
237    }
238
239    fn next_token(&mut self) -> Result<Token> {
240        self.skip_ws();
241        let c = self.peek();
242        if c == '\0' {
243            return Ok(Token::Eof);
244        }
245
246        if c == '"' {
247            return self.read_string();
248        }
249
250        if c.is_ascii_digit() || (c == '.' && self.peek_next().is_ascii_digit()) {
251            return self.read_number();
252        }
253
254        if c.is_ascii_alphabetic() || c == '_' || c == '$' {
255            let ident = self.read_ident();
256            let upper = ident.to_uppercase();
257            if upper == "TRUE" {
258                return Ok(Token::Bool(true));
259            }
260            if upper == "FALSE" {
261                return Ok(Token::Bool(false));
262            }
263            return Ok(Token::Ident(ident));
264        }
265
266        self.advance();
267        match c {
268            '(' => Ok(Token::LParen),
269            ')' => Ok(Token::RParen),
270            '{' => Ok(Token::LBrace),
271            '}' => Ok(Token::RBrace),
272            ',' => Ok(Token::Comma),
273            ';' => Ok(Token::Semicolon),
274            '+' => Ok(Token::Op("+".to_string())),
275            '-' => Ok(Token::Op("-".to_string())),
276            '*' => Ok(Token::Op("*".to_string())),
277            '/' => Ok(Token::Op("/".to_string())),
278            '^' => Ok(Token::Op("^".to_string())),
279            '&' => Ok(Token::Op("&".to_string())),
280            '!' => Ok(Token::Op("!".to_string())),
281            ':' => Ok(Token::Op(":".to_string())),
282            '=' => Ok(Token::Op("=".to_string())),
283            '<' => {
284                if self.peek() == '=' {
285                    self.advance();
286                    Ok(Token::Op("<=".to_string()))
287                } else if self.peek() == '>' {
288                    self.advance();
289                    Ok(Token::Op("<>".to_string()))
290                } else {
291                    Ok(Token::Op("<".to_string()))
292                }
293            }
294            '>' => {
295                if self.peek() == '=' {
296                    self.advance();
297                    Ok(Token::Op(">=".to_string()))
298                } else {
299                    Ok(Token::Op(">".to_string()))
300                }
301            }
302            _ => Err(format!("unexpected character {:?} in formula", c).into()),
303        }
304    }
305
306    fn read_number(&mut self) -> Result<Token> {
307        let start = self.pos;
308        let mut has_dot = false;
309        let mut has_exp = false;
310        while !self.input[self.pos..].is_empty() {
311            let c = self.peek();
312            if c.is_ascii_digit() {
313                self.advance();
314            } else if c == '.' && !has_dot && !has_exp {
315                has_dot = true;
316                self.advance();
317            } else if (c == 'e' || c == 'E') && !has_exp {
318                has_exp = true;
319                self.advance();
320                if self.peek() == '+' || self.peek() == '-' {
321                    self.advance();
322                }
323            } else {
324                break;
325            }
326        }
327        let s = &self.input[start..self.pos];
328        match s.parse::<f64>() {
329            Ok(n) => Ok(Token::Number(n)),
330            Err(_) => Err(format!("invalid numeric literal {}", s).into()),
331        }
332    }
333
334    fn read_string(&mut self) -> Result<Token> {
335        self.advance(); // opening quote
336        let mut s = String::new();
337        while !self.input[self.pos..].is_empty() {
338            let c = self.advance();
339            if c == '"' {
340                if self.peek() == '"' {
341                    self.advance();
342                    s.push('"');
343                } else {
344                    break;
345                }
346            } else {
347                s.push(c);
348            }
349        }
350        Ok(Token::Text(s))
351    }
352
353    fn read_ident(&mut self) -> String {
354        let start = self.pos;
355        while !self.input[self.pos..].is_empty() {
356            let c = self.peek();
357            if c.is_ascii_alphanumeric() || c == '_' || c == '.' || c == '?' || c == '$' {
358                self.advance();
359            } else {
360                break;
361            }
362        }
363        self.input[start..self.pos].to_string()
364    }
365}
366
367// ------------------------------------------------------------------
368// Parser
369// ------------------------------------------------------------------
370
371struct Parser<'a> {
372    lexer: Lexer<'a>,
373    current: Token,
374}
375
376impl<'a> Parser<'a> {
377    fn new(input: &'a str) -> Result<Self> {
378        let mut lexer = Lexer::new(input);
379        let current = lexer.next_token()?;
380        Ok(Self { lexer, current })
381    }
382
383    fn bump(&mut self) -> Result<()> {
384        self.current = self.lexer.next_token()?;
385        Ok(())
386    }
387
388    fn expect_rparen(&mut self) -> Result<()> {
389        if !matches!(self.current, Token::RParen) {
390            return Err(format!("expected ')' got {:?}", self.current).into());
391        }
392        self.bump()
393    }
394
395    fn parse_expr(&mut self) -> Result<Expr> {
396        self.parse_compare()
397    }
398
399    fn parse_compare(&mut self) -> Result<Expr> {
400        let mut left = self.parse_concat()?;
401        if let Token::Op(ref op) = self.current {
402            if is_comparison(op) {
403                let op = op.clone();
404                self.bump()?;
405                let right = self.parse_concat()?;
406                left = Expr::Binary(op, Box::new(left), Box::new(right));
407            }
408        }
409        Ok(left)
410    }
411
412    fn parse_concat(&mut self) -> Result<Expr> {
413        let mut left = self.parse_add_sub()?;
414        while let Token::Op(ref op) = self.current {
415            if op == "&" {
416                self.bump()?;
417                let right = self.parse_add_sub()?;
418                left = Expr::Binary("&".to_string(), Box::new(left), Box::new(right));
419            } else {
420                break;
421            }
422        }
423        Ok(left)
424    }
425
426    fn parse_add_sub(&mut self) -> Result<Expr> {
427        let mut left = self.parse_mul_div()?;
428        while let Token::Op(ref op) = self.current {
429            if op == "+" || op == "-" {
430                let op = op.clone();
431                self.bump()?;
432                let right = self.parse_mul_div()?;
433                left = Expr::Binary(op, Box::new(left), Box::new(right));
434            } else {
435                break;
436            }
437        }
438        Ok(left)
439    }
440
441    fn parse_mul_div(&mut self) -> Result<Expr> {
442        let mut left = self.parse_power()?;
443        while let Token::Op(ref op) = self.current {
444            if op == "*" || op == "/" {
445                let op = op.clone();
446                self.bump()?;
447                let right = self.parse_power()?;
448                left = Expr::Binary(op, Box::new(left), Box::new(right));
449            } else {
450                break;
451            }
452        }
453        Ok(left)
454    }
455
456    fn parse_power(&mut self) -> Result<Expr> {
457        let left = self.parse_unary()?;
458        if let Token::Op(ref op) = self.current {
459            if op == "^" {
460                let op = op.clone();
461                self.bump()?;
462                let right = self.parse_power()?;
463                return Ok(Expr::Binary(op, Box::new(left), Box::new(right)));
464            }
465        }
466        Ok(left)
467    }
468
469    fn parse_unary(&mut self) -> Result<Expr> {
470        if let Token::Op(ref op) = self.current {
471            if op == "+" || op == "-" {
472                let op = op.clone();
473                self.bump()?;
474                let expr = self.parse_unary()?;
475                return Ok(Expr::Unary(op, Box::new(expr)));
476            }
477        }
478        self.parse_primary()
479    }
480
481    fn parse_primary(&mut self) -> Result<Expr> {
482        match self.current.clone() {
483            Token::Number(n) => {
484                self.bump()?;
485                // Whole-row reference, e.g. `1:1` or `3:5`.
486                if n.fract() == 0.0 && n >= 1.0 {
487                    if let Token::Op(ref op) = self.current {
488                        if op == ":" {
489                            let first = CellRef {
490                                sheet: None,
491                                col: 0,
492                                row: n as i32,
493                                col_abs: false,
494                                row_abs: false,
495                                whole_col: false,
496                                whole_row: true,
497                            };
498                            return self.parse_multi_area_range(first);
499                        }
500                    }
501                }
502                Ok(Expr::Number(n))
503            }
504            Token::Text(s) => {
505                self.bump()?;
506                Ok(Expr::String(s))
507            }
508            Token::Bool(b) => {
509                self.bump()?;
510                // Excel also accepts TRUE() and FALSE() as zero-argument functions.
511                if let Token::LParen = self.current {
512                    self.bump()?;
513                    self.expect_rparen()?;
514                    let name = if b { "TRUE" } else { "FALSE" }.to_string();
515                    return Ok(Expr::Call(name, Vec::new()));
516                }
517                Ok(Expr::Bool(b))
518            }
519            Token::LParen => {
520                self.bump()?;
521                let expr = self.parse_expr()?;
522                self.expect_rparen()?;
523                Ok(expr)
524            }
525            Token::LBrace => self.parse_array(),
526            Token::Ident(name) => self.parse_ident_expr(name),
527            ref t => Err(format!("unexpected token {:?}", t).into()),
528        }
529    }
530
531    fn parse_array(&mut self) -> Result<Expr> {
532        self.bump()?; // consume '{'
533        let mut rows: Vec<Vec<Expr>> = vec![Vec::new()];
534        loop {
535            if matches!(self.current, Token::RBrace) {
536                self.bump()?;
537                break;
538            }
539            let expr = self.parse_expr()?;
540            rows.last_mut().unwrap().push(expr);
541            match self.current {
542                Token::Comma => self.bump()?,
543                Token::Semicolon => {
544                    self.bump()?;
545                    rows.push(Vec::new());
546                }
547                Token::RBrace => {
548                    self.bump()?;
549                    break;
550                }
551                _ => return Err("expected ',', ';' or '}' in array literal".into()),
552            }
553        }
554        Ok(Expr::Array(rows))
555    }
556
557    fn parse_ident_expr(&mut self, name: String) -> Result<Expr> {
558        self.bump()?; // consume identifier
559
560        // Function call: NAME(...)
561        if let Token::LParen = self.current {
562            self.bump()?;
563            let mut args = Vec::new();
564            if !matches!(self.current, Token::RParen) {
565                loop {
566                    args.push(self.parse_expr()?);
567                    if let Token::Comma = self.current {
568                        self.bump()?;
569                        continue;
570                    }
571                    break;
572                }
573            }
574            self.expect_rparen()?;
575            return Ok(Expr::Call(normalize_function_name(&name), args));
576        }
577
578        // 3D reference: Sheet1:Sheet2!A1 or Sheet1:Sheet2!A1:B2
579        if let Token::Op(ref op) = self.current {
580            if op == ":" {
581                let peek = self.lexer.peek_tokens(2)?;
582                if let [Token::Ident(last_sheet), Token::Op(bang)] = peek.as_slice() {
583                    if bang == "!" {
584                        let last = last_sheet.clone();
585                        self.bump()?; // ':'
586                        self.bump()?; // last sheet
587                        self.bump()?; // '!'
588                        let expr = self.parse_expr()?;
589                        let (start, end) = match expr {
590                            Expr::Cell(r) => (r.clone(), r),
591                            Expr::Range(s, e) => (s, e),
592                            _ => {
593                                return Err(
594                                    "expected cell or range reference in 3D reference".into()
595                                );
596                            }
597                        };
598                        return Ok(Expr::Range3D(name, last, start, end));
599                    }
600                }
601            }
602        }
603
604        // Sheet-qualified reference: Sheet!A1 or Sheet!1:1
605        let first_ref = if let Token::Op(ref op) = self.current {
606            if op == "!" {
607                self.bump()?;
608                let sheet = Some(name.clone());
609                match self.current.clone() {
610                    Token::Ident(n) => {
611                        self.bump()?;
612                        parse_cell_ref(&n, sheet)?
613                    }
614                    Token::Number(n) if n.fract() == 0.0 && n >= 1.0 => {
615                        self.bump()?;
616                        CellRef {
617                            sheet,
618                            col: 0,
619                            row: n as i32,
620                            col_abs: false,
621                            row_abs: false,
622                            whole_col: false,
623                            whole_row: true,
624                        }
625                    }
626                    t => {
627                        return Err(
628                            format!("expected cell reference after '!', got {:?}", t).into()
629                        );
630                    }
631                }
632            } else {
633                match parse_cell_ref(&name, None) {
634                    Ok(r) => r,
635                    Err(_) => {
636                        if op == ":" {
637                            return Err(format!("invalid cell reference {}", name).into());
638                        }
639                        return Ok(Expr::Name(name));
640                    }
641                }
642            }
643        } else {
644            match parse_cell_ref(&name, None) {
645                Ok(r) => r,
646                Err(_) => return Ok(Expr::Name(name)),
647            }
648        };
649
650        // Range reference: A1:B2, Sheet!A1:B2, or multi-area A1:B1:C1.
651        if let Token::Op(ref op) = self.current {
652            if op == ":" {
653                return self.parse_multi_area_range(first_ref);
654            }
655        }
656
657        Ok(Expr::Cell(first_ref))
658    }
659
660    /// Parse a multi-area reference starting with `first`. Subsequent refs may be
661    /// cell references (`A1`), optionally sheet-qualified (`Sheet!A1`), or whole
662    /// rows for row-only references (`1:1`). The result is a single bounding range.
663    fn parse_multi_area_range(&mut self, first: CellRef) -> Result<Expr> {
664        let mut refs = vec![first];
665        while let Token::Op(ref op) = self.current {
666            if op != ":" {
667                break;
668            }
669            self.bump()?;
670            let (next_sheet, cell_name, is_row_number) = if let Token::Ident(n) =
671                self.current.clone()
672            {
673                self.bump()?;
674                if let Token::Op(ref op) = self.current {
675                    if op == "!" {
676                        self.bump()?;
677                        let cell = match self.current.clone() {
678                            Token::Ident(c) => c,
679                            t => {
680                                return Err(format!(
681                                    "expected cell reference after '!', got {:?}",
682                                    t
683                                )
684                                .into());
685                            }
686                        };
687                        self.bump()?;
688                        (Some(n), cell, false)
689                    } else {
690                        (None, n, false)
691                    }
692                } else {
693                    (None, n, false)
694                }
695            } else if let Token::Number(n) = self.current {
696                self.bump()?;
697                if n.fract() == 0.0 && n >= 1.0 {
698                    (None, (n as i32).to_string(), true)
699                } else {
700                    return Err("expected integer row reference".into());
701                }
702            } else {
703                return Err(
704                    format!("expected cell reference after ':', got {:?}", self.current).into(),
705                );
706            };
707            let inherited = next_sheet.or_else(|| refs[0].sheet.clone());
708            let cell_ref = if is_row_number {
709                CellRef {
710                    sheet: inherited,
711                    col: 0,
712                    row: cell_name.parse::<i32>().unwrap(),
713                    col_abs: false,
714                    row_abs: false,
715                    whole_col: false,
716                    whole_row: true,
717                }
718            } else {
719                parse_cell_ref(&cell_name, inherited)?
720            };
721            refs.push(cell_ref);
722        }
723        let (start, end) = build_bounding_range(&refs)?;
724        Ok(Expr::Range(start, end))
725    }
726}
727
728fn is_comparison(op: &str) -> bool {
729    matches!(op, "=" | "<>" | "<" | ">" | "<=" | ">=")
730}
731
732/// Build a single bounding range from a list of cell references joined by `:`.
733/// This replicates Go's excelize behavior for multi-area references such as
734/// `A1:B1:C1` or `Sheet1!A1:Sheet1!A1:A2`: the result is the smallest range
735/// that contains every referenced cell, not a true union of disjoint areas.
736fn build_bounding_range(refs: &[CellRef]) -> Result<(CellRef, CellRef)> {
737    use crate::constants::{MAX_COLUMNS, TOTAL_ROWS};
738    if refs.is_empty() {
739        return Err("empty reference list".into());
740    }
741    let sheet = refs[0].sheet.clone();
742    if refs.iter().any(|r| r.sheet != sheet) {
743        return Err("multi-sheet reference is not supported".into());
744    }
745
746    let mut all_whole_col = true;
747    let mut all_whole_row = true;
748    let mut any_ref = false;
749    let mut min_col = i32::MAX;
750    let mut max_col = 0;
751    let mut min_row = i32::MAX;
752    let mut max_row = 0;
753
754    for r in refs {
755        if r.whole_col {
756            all_whole_row = false;
757            any_ref = true;
758            min_col = min_col.min(r.col);
759            max_col = max_col.max(r.col);
760            min_row = min_row.min(1);
761            max_row = max_row.max(TOTAL_ROWS as i32);
762        } else if r.whole_row {
763            all_whole_col = false;
764            any_ref = true;
765            min_row = min_row.min(r.row);
766            max_row = max_row.max(r.row);
767            min_col = min_col.min(1);
768            max_col = max_col.max(MAX_COLUMNS as i32);
769        } else {
770            all_whole_col = false;
771            all_whole_row = false;
772            any_ref = true;
773            min_col = min_col.min(r.col);
774            max_col = max_col.max(r.col);
775            min_row = min_row.min(r.row);
776            max_row = max_row.max(r.row);
777        }
778    }
779
780    if !any_ref {
781        return Err("no valid references".into());
782    }
783
784    let start = CellRef {
785        sheet: sheet.clone(),
786        col: min_col,
787        row: min_row,
788        col_abs: false,
789        row_abs: false,
790        whole_col: all_whole_col,
791        whole_row: all_whole_row,
792    };
793    let end = CellRef {
794        sheet,
795        col: max_col,
796        row: max_row,
797        col_abs: false,
798        row_abs: false,
799        whole_col: all_whole_col,
800        whole_row: all_whole_row,
801    };
802    Ok((start, end))
803}
804
805fn parse_cell_ref(name: &str, sheet: Option<String>) -> Result<CellRef> {
806    let without_dollar = name.replace('$', "");
807
808    // Whole-column reference (e.g. `A:A`).
809    if without_dollar.chars().all(|c| c.is_ascii_alphabetic()) {
810        let col = column_name_to_number(&without_dollar)
811            .map_err(|e| format!("invalid column {}: {}", name, e))?;
812        return Ok(CellRef {
813            sheet,
814            col,
815            row: 0,
816            col_abs: name.starts_with('$'),
817            row_abs: false,
818            whole_col: true,
819            whole_row: false,
820        });
821    }
822
823    // Whole-row reference (e.g. `1:1`).
824    if without_dollar.chars().all(|c| c.is_ascii_digit()) {
825        let row = without_dollar
826            .parse::<i32>()
827            .map_err(|_| format!("invalid row {}: {}", name, without_dollar))?;
828        return Ok(CellRef {
829            sheet,
830            col: 0,
831            row,
832            col_abs: false,
833            row_abs: name.starts_with('$'),
834            whole_col: false,
835            whole_row: true,
836        });
837    }
838
839    let (col_name, row) = split_cell_name(&without_dollar)
840        .map_err(|e| format!("invalid cell reference {}: {}", name, e))?;
841    let col = column_name_to_number(&col_name)
842        .map_err(|e| format!("invalid column {}: {}", col_name, e))?;
843
844    let col_abs = name.starts_with('$');
845    let row_abs = {
846        let without_leading = name.strip_prefix('$').unwrap_or(name);
847        without_leading.contains('$')
848    };
849
850    Ok(CellRef {
851        sheet,
852        col,
853        row,
854        col_abs,
855        row_abs,
856        whole_col: false,
857        whole_row: false,
858    })
859}
860
861/// Normalize an Excel function name to the registry key used by this port.
862/// Replicates Go's `formulaFnNameReplacer`: removes `_xlfn.` and replaces
863/// `.` with `dot`.
864pub(crate) fn normalize_function_name(name: &str) -> String {
865    name.to_uppercase()
866        .replace("_XLFN.", "")
867        .replace('.', "dot")
868}
869
870// ------------------------------------------------------------------
871// Function registry
872// ------------------------------------------------------------------
873
874pub(crate) type FormulaFn = fn(&CalcContext, &[FormulaArg]) -> FormulaArg;
875
876fn build_registry() -> HashMap<&'static str, FormulaFn> {
877    let mut m: HashMap<&'static str, FormulaFn> = HashMap::with_capacity(512);
878
879    // Common functions already implemented in this module.
880    m.insert("IF", calc_if);
881
882    // Math and trigonometric functions.
883    math::register(&mut m);
884    // Statistical functions.
885    statistical::register(&mut m);
886    // Engineering functions.
887    engineering::register(&mut m);
888    // Logical functions.
889    logical::register(&mut m);
890    // Information functions.
891    info::register(&mut m);
892    // Lookup and reference functions.
893    lookup::register(&mut m);
894    // Text functions.
895    text::register(&mut m);
896    // Date and time functions.
897    date::register(&mut m);
898    // Financial functions.
899    financial::register(&mut m);
900    // Database functions.
901    database::register(&mut m);
902    // Web and miscellaneous functions.
903    web::register(&mut m);
904
905    m
906}
907
908fn formula_registry() -> &'static HashMap<&'static str, FormulaFn> {
909    static REGISTRY: OnceLock<HashMap<&'static str, FormulaFn>> = OnceLock::new();
910    REGISTRY.get_or_init(build_registry)
911}
912
913// ------------------------------------------------------------------
914// Evaluation
915// ------------------------------------------------------------------
916
917fn eval(ctx: &CalcContext, expr: &Expr) -> FormulaArg {
918    match expr {
919        Expr::Number(n) => new_number_formula_arg(*n),
920        Expr::String(s) => new_string_formula_arg(s.clone()),
921        Expr::Bool(b) => new_bool_formula_arg(*b),
922        Expr::Cell(r) => eval_cell_ref(ctx, r),
923        Expr::Range(start, end) => eval_range(ctx, start, end),
924        Expr::Range3D(s1, s2, start, end) => eval_range_3d(ctx, s1, s2, start, end),
925        Expr::Name(name) => eval_name(ctx, name),
926        Expr::Array(rows) => eval_array(ctx, rows),
927        Expr::Call(name, args) => eval_call(ctx, name, args),
928        Expr::Unary(op, e) => eval_unary(op, eval(ctx, e)),
929        Expr::Binary(op, l, r) => eval_binary(op, eval(ctx, l), eval(ctx, r)),
930    }
931}
932
933fn eval_call(ctx: &CalcContext, name: &str, args: &[Expr]) -> FormulaArg {
934    let evaluated: Vec<FormulaArg> = args.iter().map(|e| eval_arg(ctx, e)).collect();
935    dispatch_function(ctx, name, &evaluated)
936}
937
938/// Evaluate a single argument expression.  If the top-level expression is a
939/// cell or range reference, the resulting `FormulaArg` keeps that metadata so
940/// reference-aware functions such as `ISREF`, `ROW`, `COLUMN`, and
941/// `FORMULATEXT` can inspect it.
942fn eval_arg(ctx: &CalcContext, expr: &Expr) -> FormulaArg {
943    let mut arg = eval(ctx, expr);
944    match expr {
945        Expr::Cell(r) => arg.cell_refs.push(r.clone()),
946        Expr::Range(start, end) => arg.cell_ranges.push((start.clone(), end.clone())),
947        _ => {}
948    }
949    arg
950}
951
952fn eval_array(ctx: &CalcContext, rows: &[Vec<Expr>]) -> FormulaArg {
953    let mut matrix = Vec::new();
954    for row in rows {
955        let mut line = Vec::new();
956        for e in row {
957            let v = eval(ctx, e);
958            if v.is_error() {
959                return v;
960            }
961            line.push(v);
962        }
963        matrix.push(line);
964    }
965    new_matrix_formula_arg(matrix)
966}
967
968fn eval_range_3d(
969    ctx: &CalcContext,
970    first_sheet: &str,
971    last_sheet: &str,
972    start: &CellRef,
973    end: &CellRef,
974) -> FormulaArg {
975    let sheets = match ctx.file.expand_3d_sheet_range(first_sheet, last_sheet) {
976        Ok(v) => v,
977        Err(_) => return new_error_formula_arg(FORMULA_ERROR_REF),
978    };
979    let mut matrix = Vec::new();
980    for sheet in sheets {
981        let mut start_ref = start.clone();
982        let mut end_ref = end.clone();
983        start_ref.sheet = Some(sheet.clone());
984        end_ref.sheet = Some(sheet.clone());
985        let arg = eval_range(ctx, &start_ref, &end_ref);
986        if arg.is_error() {
987            return arg;
988        }
989        match arg.typ {
990            ArgType::Matrix => matrix.extend(arg.matrix),
991            _ => matrix.push(vec![arg]),
992        }
993    }
994    new_matrix_formula_arg(matrix)
995}
996
997fn eval_name(ctx: &CalcContext, name: &str) -> FormulaArg {
998    let ref_to = ctx.file.get_defined_name_ref_to(name, ctx.sheet);
999    if ref_to.is_empty() {
1000        return new_error_formula_arg(FORMULA_ERROR_NAME);
1001    }
1002    let formula = ref_to.trim_start_matches('=').trim();
1003    if formula.is_empty() {
1004        return new_error_formula_arg(FORMULA_ERROR_NAME);
1005    }
1006    match parse_formula(formula) {
1007        Ok(expr) => eval(ctx, &expr),
1008        Err(_) => new_error_formula_arg(FORMULA_ERROR_NAME),
1009    }
1010}
1011
1012fn dispatch_function(ctx: &CalcContext, name: &str, args: &[FormulaArg]) -> FormulaArg {
1013    let registry = formula_registry();
1014    // Registry stores Excel dotted function names with `dot` replacing `.`
1015    // (e.g. `BETA.DIST` is registered as `BETAdotDIST`).
1016    let key = name.replace('.', "dot");
1017    match registry.get(key.as_str()) {
1018        Some(f) => f(ctx, args),
1019        None => new_error_formula_arg(FORMULA_ERROR_NAME),
1020    }
1021}
1022
1023fn calc_if(_ctx: &CalcContext, args: &[FormulaArg]) -> FormulaArg {
1024    if args.is_empty() || args.len() > 3 {
1025        return new_error_formula_arg(FORMULA_ERROR_VALUE);
1026    }
1027    let cond = match args[0].typ {
1028        ArgType::String => match args[0].string.parse::<bool>() {
1029            Ok(b) => b,
1030            Err(_) => return new_error_formula_arg(FORMULA_ERROR_VALUE),
1031        },
1032        ArgType::Number => args[0].number == 1.0,
1033        _ => args[0].as_bool(),
1034    };
1035    if args.len() == 1 {
1036        return new_bool_formula_arg(cond);
1037    }
1038    if cond {
1039        if args[1].typ == ArgType::Number {
1040            args[1].to_number()
1041        } else {
1042            new_string_formula_arg(args[1].value())
1043        }
1044    } else if args.len() == 2 {
1045        new_bool_formula_arg(false)
1046    } else if args[2].typ == ArgType::Number {
1047        args[2].to_number()
1048    } else {
1049        new_string_formula_arg(args[2].value())
1050    }
1051}
1052
1053fn eval_unary(op: &str, arg: FormulaArg) -> FormulaArg {
1054    if arg.is_error() {
1055        return arg;
1056    }
1057    match op {
1058        "+" => arg,
1059        "-" => match arg.to_number().as_number() {
1060            Some(n) => new_number_formula_arg(-n),
1061            None => new_error_formula_arg(FORMULA_ERROR_VALUE),
1062        },
1063        _ => new_error_formula_arg(FORMULA_ERROR_VALUE),
1064    }
1065}
1066
1067fn eval_binary(op: &str, left: FormulaArg, right: FormulaArg) -> FormulaArg {
1068    if left.is_error() {
1069        return left;
1070    }
1071    if right.is_error() {
1072        return right;
1073    }
1074    match op {
1075        "+" => numeric_op(left, right, |a, b| a + b),
1076        "-" => numeric_op(left, right, |a, b| a - b),
1077        "*" => numeric_op(left, right, |a, b| a * b),
1078        "/" => {
1079            let a = left.to_number().as_number();
1080            let b = right.to_number().as_number();
1081            match (a, b) {
1082                (Some(_), Some(0.0)) => new_error_formula_arg(FORMULA_ERROR_DIV),
1083                (Some(x), Some(y)) => new_number_formula_arg(x / y),
1084                _ => new_error_formula_arg(FORMULA_ERROR_VALUE),
1085            }
1086        }
1087        "^" => numeric_op(left, right, |a, b| a.powf(b)),
1088        "&" => new_string_formula_arg(format!("{}{}", left.value(), right.value())),
1089        "=" => new_bool_formula_arg(compare_equal(&left, &right)),
1090        "<>" => new_bool_formula_arg(!compare_equal(&left, &right)),
1091        "<" | ">" | "<=" | ">=" => comparison_op(op, left, right),
1092        _ => new_error_formula_arg(FORMULA_ERROR_VALUE),
1093    }
1094}
1095
1096fn numeric_op<F: FnOnce(f64, f64) -> f64>(left: FormulaArg, right: FormulaArg, f: F) -> FormulaArg {
1097    match (left.to_number().as_number(), right.to_number().as_number()) {
1098        (Some(a), Some(b)) => new_number_formula_arg(f(a, b)),
1099        _ => new_error_formula_arg(FORMULA_ERROR_VALUE),
1100    }
1101}
1102
1103fn comparison_op(op: &str, left: FormulaArg, right: FormulaArg) -> FormulaArg {
1104    fn as_number(arg: &FormulaArg) -> Option<f64> {
1105        match arg.typ {
1106            ArgType::Number => Some(arg.number),
1107            ArgType::Empty => Some(0.0),
1108            _ => None,
1109        }
1110    }
1111
1112    // Two numeric operands (including empty cells and booleans, which are
1113    // stored as numbers) compare by value.
1114    if let (Some(a), Some(b)) = (as_number(&left), as_number(&right)) {
1115        let res = match op {
1116            "<" => a < b,
1117            ">" => a > b,
1118            "<=" => a <= b,
1119            ">=" => a >= b,
1120            _ => false,
1121        };
1122        return new_bool_formula_arg(res);
1123    }
1124
1125    // Mixed number/text: numbers are always less than text strings.
1126    let number_vs_text = |number_on_left: bool| match op {
1127        "<" | "<=" => number_on_left,
1128        ">" | ">=" => !number_on_left,
1129        _ => false,
1130    };
1131    if left.typ == ArgType::Number && right.typ == ArgType::String {
1132        return new_bool_formula_arg(number_vs_text(true));
1133    }
1134    if left.typ == ArgType::String && right.typ == ArgType::Number {
1135        return new_bool_formula_arg(number_vs_text(false));
1136    }
1137
1138    // Text vs text (or any other mix) compares the displayed values.
1139    let a = left.value().to_uppercase();
1140    let b = right.value().to_uppercase();
1141    let res = match op {
1142        "<" => a < b,
1143        ">" => a > b,
1144        "<=" => a <= b,
1145        ">=" => a >= b,
1146        _ => false,
1147    };
1148    new_bool_formula_arg(res)
1149}
1150
1151fn eval_cell_ref(ctx: &CalcContext, reference: &CellRef) -> FormulaArg {
1152    let cell_name = reference.to_cell_name();
1153    let sheet = reference.sheet.as_deref().unwrap_or(ctx.sheet);
1154    let ref_key = format!("{}!{}", sheet, cell_name);
1155
1156    // Whole-column/whole-row references on the active sheet may be defined
1157    // names. Resolve them when the cell name itself is not a valid A1 address.
1158    if reference.sheet.is_none() && (reference.whole_col || reference.whole_row) {
1159        let name = if reference.whole_col {
1160            column_number_to_name(reference.col).unwrap_or_default()
1161        } else {
1162            reference.row.to_string()
1163        };
1164        let ref_to = ctx.file.get_defined_name_ref_to(&name, ctx.sheet);
1165        if !ref_to.is_empty() {
1166            return eval_name(ctx, &ref_to);
1167        }
1168    }
1169
1170    // Match Go's excelize behavior for the top-level formula cell: when a
1171    // formula references its own cell, return the raw stored value instead of
1172    // evaluating the formula recursively. This avoids circular-reference
1173    // errors for cases like `=A1+(B1-C1)` in C1.
1174    if sheet == ctx.sheet && cell_name == ctx.cell {
1175        let ws = match ctx.file.work_sheet_reader(sheet) {
1176            Ok(ws) => ws,
1177            Err(_) => return new_error_formula_arg(FORMULA_ERROR_REF),
1178        };
1179        return if let Some(c) = find_cell(&ws, &cell_name) {
1180            cell_to_arg(ctx.file, &c)
1181        } else {
1182            new_empty_formula_arg()
1183        };
1184    }
1185
1186    if ctx
1187        .stack
1188        .borrow()
1189        .contains(&(sheet.to_string(), cell_name.clone()))
1190    {
1191        return new_error_formula_arg(FORMULA_ERROR_REF);
1192    }
1193
1194    let ws = match ctx.file.work_sheet_reader(sheet) {
1195        Ok(ws) => ws,
1196        Err(_) => return new_error_formula_arg(FORMULA_ERROR_REF),
1197    };
1198
1199    let cell = find_cell(&ws, &cell_name);
1200    let result = if let Some(ref c) = cell {
1201        if let Some(f) = &c.f {
1202            let formula = f.content.trim_start_matches('=').trim();
1203            if !formula.is_empty() {
1204                // Reuse cached argument values for non-entry cells.
1205                if ref_key != ctx.entry {
1206                    if let Some(cached) = ctx
1207                        .file
1208                        .formula_arg_cache
1209                        .lock()
1210                        .unwrap()
1211                        .get(&ref_key)
1212                        .cloned()
1213                    {
1214                        return cached;
1215                    }
1216                }
1217
1218                // Iterative calculation for cells that are not the entry point.
1219                if ctx.max_calc_iterations > 0 && ref_key != ctx.entry {
1220                    let mut iterations = ctx.iterations.borrow_mut();
1221                    let count = iterations.entry(ref_key.clone()).or_insert(0);
1222                    if *count <= ctx.max_calc_iterations {
1223                        *count += 1;
1224                        drop(iterations);
1225                        ctx.stack
1226                            .borrow_mut()
1227                            .push((sheet.to_string(), cell_name.clone()));
1228                        let res = match parse_formula(formula) {
1229                            Ok(expr) => eval(ctx, &expr),
1230                            Err(_) => new_error_formula_arg(FORMULA_ERROR_VALUE),
1231                        };
1232                        ctx.stack.borrow_mut().pop();
1233                        ctx.iterations_cache
1234                            .borrow_mut()
1235                            .insert(ref_key.clone(), res.clone());
1236                        ctx.file
1237                            .formula_arg_cache
1238                            .lock()
1239                            .unwrap()
1240                            .insert(ref_key.clone(), res.clone());
1241                        return res;
1242                    }
1243                    return ctx
1244                        .iterations_cache
1245                        .borrow()
1246                        .get(&ref_key)
1247                        .cloned()
1248                        .unwrap_or_else(new_empty_formula_arg);
1249                }
1250
1251                ctx.stack
1252                    .borrow_mut()
1253                    .push((sheet.to_string(), cell_name.clone()));
1254                let res = match parse_formula(formula) {
1255                    Ok(expr) => eval(ctx, &expr),
1256                    Err(_) => new_error_formula_arg(FORMULA_ERROR_VALUE),
1257                };
1258                ctx.stack.borrow_mut().pop();
1259                if ref_key != ctx.entry {
1260                    ctx.file
1261                        .formula_arg_cache
1262                        .lock()
1263                        .unwrap()
1264                        .insert(ref_key.clone(), res.clone());
1265                }
1266                res
1267            } else {
1268                cell_to_arg(ctx.file, c)
1269            }
1270        } else {
1271            cell_to_arg(ctx.file, c)
1272        }
1273    } else {
1274        new_empty_formula_arg()
1275    };
1276
1277    result
1278}
1279
1280fn eval_range(ctx: &CalcContext, start: &CellRef, end: &CellRef) -> FormulaArg {
1281    let sheet = start.sheet.as_deref().unwrap_or(ctx.sheet);
1282    let (max_row, max_col) = ctx.worksheet_bounds(sheet);
1283    let (col1, col2, row1, row2) = if start.whole_col && end.whole_col {
1284        let c1 = start.col.min(end.col);
1285        let c2 = start.col.max(end.col);
1286        (c1, c2, 1, max_row)
1287    } else if start.whole_row && end.whole_row {
1288        let r1 = start.row.min(end.row);
1289        let r2 = start.row.max(end.row);
1290        (1, max_col, r1, r2)
1291    } else {
1292        let c1 = start.col.min(end.col);
1293        let c2 = start.col.max(end.col);
1294        let r1 = start.row.min(end.row);
1295        let r2 = start.row.max(end.row);
1296        (c1, c2, r1, r2)
1297    };
1298
1299    let mut matrix = Vec::new();
1300    for row in row1..=row2 {
1301        let mut line = Vec::new();
1302        for col in col1..=col2 {
1303            let reference = CellRef {
1304                sheet: Some(sheet.to_string()),
1305                col,
1306                row,
1307                col_abs: false,
1308                row_abs: false,
1309                whole_col: false,
1310                whole_row: false,
1311            };
1312            let value = eval_cell_ref(ctx, &reference);
1313            if value.is_error() {
1314                return value;
1315            }
1316            line.push(value);
1317        }
1318        matrix.push(line);
1319    }
1320    new_matrix_formula_arg(matrix)
1321}
1322
1323fn find_cell(ws: &XlsxWorksheet, cell: &str) -> Option<XlsxC> {
1324    for row in &ws.sheet_data.row {
1325        for c in &row.c {
1326            if c.r
1327                .as_deref()
1328                .map(|r| r.eq_ignore_ascii_case(cell))
1329                .unwrap_or(false)
1330            {
1331                return Some(c.clone());
1332            }
1333        }
1334    }
1335    None
1336}
1337
1338fn cell_to_arg(file: &File, c: &XlsxC) -> FormulaArg {
1339    match c.t.as_deref() {
1340        Some("s") => {
1341            if let Some(v) = &c.v {
1342                if let Ok(idx) = v.parse::<i32>() {
1343                    return new_string_formula_arg(read_shared_string(file, idx));
1344                }
1345            }
1346            new_empty_formula_arg()
1347        }
1348        Some("inlineStr") => new_string_formula_arg(inline_string_text(c.is.as_ref())),
1349        Some("b") => new_bool_formula_arg(c.v.as_deref() == Some("1")),
1350        Some("str") => new_string_formula_arg(c.v.clone().unwrap_or_default()),
1351        _ => {
1352            if let Some(v) = &c.v {
1353                if let Ok(n) = v.parse::<f64>() {
1354                    new_number_formula_arg(n)
1355                } else {
1356                    new_string_formula_arg(v.clone())
1357                }
1358            } else {
1359                new_empty_formula_arg()
1360            }
1361        }
1362    }
1363}
1364
1365fn read_shared_string(file: &File, idx: i32) -> String {
1366    let sst = file.shared_strings_reader().unwrap_or_default();
1367    sst.si
1368        .get(idx as usize)
1369        .map(|si| string_from_si(si))
1370        .unwrap_or_default()
1371}
1372
1373fn inline_string_text(si: Option<&XlsxSi>) -> String {
1374    si.map(string_from_si).unwrap_or_default()
1375}
1376
1377fn string_from_si(si: &XlsxSi) -> String {
1378    if let Some(t) = &si.t {
1379        return t.val.clone();
1380    }
1381    si.r.iter()
1382        .filter_map(|r| r.t.as_ref().map(|t| t.val.clone()))
1383        .collect()
1384}
1385
1386// ------------------------------------------------------------------
1387// File extension: CalcCellValue equivalent
1388// ------------------------------------------------------------------
1389
1390impl File {
1391    /// Calculate the value of a single cell.  If the cell contains a formula,
1392    /// the formula is parsed and evaluated; otherwise the cached/raw value is
1393    /// returned. Uses the [`Options`] stored on the workbook.
1394    pub fn calc_cell_value(&self, sheet: &str, cell: &str) -> Result<String> {
1395        let opts = self.options.lock().unwrap().clone();
1396        self.calc_cell_value_with_options(sheet, cell, &opts)
1397    }
1398
1399    /// Calculate the value of a single cell with an explicit set of options.
1400    pub fn calc_cell_value_with_options(
1401        &self,
1402        sheet: &str,
1403        cell: &str,
1404        opts: &Options,
1405    ) -> Result<String> {
1406        let cell_upper = cell.to_uppercase();
1407        let entry = format!("{}!{}", sheet, cell_upper);
1408        if opts.raw_cell_value {
1409            if let Some(v) = self.calc_raw_cache.lock().unwrap().get(&entry).cloned() {
1410                return Ok(v);
1411            }
1412        } else {
1413            if let Some(v) = self.calc_cache.lock().unwrap().get(&entry).cloned() {
1414                return Ok(v);
1415            }
1416        }
1417
1418        let ws = self.work_sheet_reader(sheet)?;
1419        let c = find_cell(&ws, &cell_upper);
1420
1421        let token = if let Some(c) = c {
1422            if let Some(f) = &c.f {
1423                let formula = f.content.trim_start_matches('=').trim();
1424                if !formula.is_empty() {
1425                    let mut ctx = CalcContext::new_with_cell(self, sheet, cell_upper.clone());
1426                    ctx.entry = entry.clone();
1427                    ctx.max_calc_iterations = opts.max_calc_iterations;
1428                    ctx.stack
1429                        .borrow_mut()
1430                        .push((sheet.to_string(), cell_upper.clone()));
1431                    let expr = parse_formula(formula)?;
1432                    let result = eval(&ctx, &expr);
1433                    ctx.stack.borrow_mut().pop();
1434                    result
1435                } else {
1436                    cell_to_arg(self, &c)
1437                }
1438            } else {
1439                cell_to_arg(self, &c)
1440            }
1441        } else {
1442            new_empty_formula_arg()
1443        };
1444
1445        let style_idx = if opts.raw_cell_value {
1446            0
1447        } else {
1448            self.get_cell_style(sheet, &cell_upper).unwrap_or(0)
1449        };
1450
1451        let result = format_calc_result(self, &token, style_idx, opts.raw_cell_value)?;
1452        if opts.raw_cell_value {
1453            self.calc_raw_cache
1454                .lock()
1455                .unwrap()
1456                .insert(entry, result.clone());
1457        } else {
1458            self.calc_cache
1459                .lock()
1460                .unwrap()
1461                .insert(entry, result.clone());
1462        }
1463        Ok(result)
1464    }
1465}
1466
1467/// Format a calculated formula result into the textual value returned by
1468/// [`File::calc_cell_value`], applying the cell's number format unless raw
1469/// values were requested.
1470fn format_calc_result(
1471    file: &File,
1472    token: &FormulaArg,
1473    style_idx: i32,
1474    raw: bool,
1475) -> Result<String> {
1476    if token.typ == ArgType::Number && !token.boolean {
1477        let mut c = XlsxC::default();
1478        c.s = Some(style_idx as i64);
1479        c.v = Some(format_number_for_calc(token.number));
1480        Ok(read_cell_value(file, &c, raw))
1481    } else {
1482        let mut c = XlsxC::default();
1483        c.t = Some("str".to_string());
1484        c.v = Some(token.value());
1485        Ok(read_cell_value(file, &c, raw))
1486    }
1487}
1488
1489/// Format a numeric result the way Go `calcCellValue` does: integers are
1490/// printed without a decimal point, very large/small values use uppercase
1491/// scientific notation, and everything else uses Rust's default fixed-point
1492/// representation.
1493fn format_number_for_calc(n: f64) -> String {
1494    if n.is_nan() || n.is_infinite() {
1495        return n.to_string();
1496    }
1497    if n.fract() == 0.0 && n >= i64::MIN as f64 && n <= i64::MAX as f64 {
1498        return format!("{}", n as i64);
1499    }
1500    let abs = n.abs();
1501    if abs >= 1e15 || (abs > 0.0 && abs < 1e-15) {
1502        format!("{:.14e}", n).to_uppercase()
1503    } else {
1504        format!("{}", n)
1505    }
1506}
1507
1508// ------------------------------------------------------------------
1509// Tests
1510// ------------------------------------------------------------------
1511
1512#[cfg(test)]
1513mod tests {
1514    use super::*;
1515
1516    fn new_file() -> File {
1517        File::new_with_options(crate::options::Options::default())
1518    }
1519
1520    #[test]
1521    fn parse_numeric_literal() {
1522        let expr = parse_formula("123.45").unwrap();
1523        assert!(matches!(expr, Expr::Number(n) if (n - 123.45).abs() < 1e-9));
1524    }
1525
1526    #[test]
1527    fn parse_string_literal() {
1528        let expr = parse_formula("\"hello\"").unwrap();
1529        assert!(matches!(expr, Expr::String(s) if s == "hello"));
1530    }
1531
1532    #[test]
1533    fn parse_boolean_literals() {
1534        let expr = parse_formula("TRUE").unwrap();
1535        assert!(matches!(expr, Expr::Bool(true)));
1536        let expr = parse_formula("FALSE").unwrap();
1537        assert!(matches!(expr, Expr::Bool(false)));
1538    }
1539
1540    #[test]
1541    fn parse_cell_reference() {
1542        let expr = parse_formula("$A$1").unwrap();
1543        match expr {
1544            Expr::Cell(r) => {
1545                assert_eq!(r.col, 1);
1546                assert_eq!(r.row, 1);
1547                assert!(r.col_abs);
1548                assert!(r.row_abs);
1549            }
1550            _ => panic!("expected cell reference"),
1551        }
1552    }
1553
1554    #[test]
1555    fn parse_sheet_qualified_reference() {
1556        let expr = parse_formula("Sheet1!B2").unwrap();
1557        match expr {
1558            Expr::Cell(r) => {
1559                assert_eq!(r.sheet.as_deref(), Some("Sheet1"));
1560                assert_eq!(r.col, 2);
1561                assert_eq!(r.row, 2);
1562            }
1563            _ => panic!("expected qualified reference"),
1564        }
1565    }
1566
1567    #[test]
1568    fn parse_range_reference() {
1569        let expr = parse_formula("A1:B3").unwrap();
1570        match expr {
1571            Expr::Range(start, end) => {
1572                assert_eq!(start.col, 1);
1573                assert_eq!(start.row, 1);
1574                assert_eq!(end.col, 2);
1575                assert_eq!(end.row, 3);
1576            }
1577            _ => panic!("expected range"),
1578        }
1579    }
1580
1581    #[test]
1582    fn parse_function_call() {
1583        let expr = parse_formula("SUM(A1:A3, 5)").unwrap();
1584        match expr {
1585            Expr::Call(name, args) => {
1586                assert_eq!(name, "SUM");
1587                assert_eq!(args.len(), 2);
1588            }
1589            _ => panic!("expected function call"),
1590        }
1591    }
1592
1593    #[test]
1594    fn parse_binary_operators() {
1595        let expr = parse_formula("1+2*3^2").unwrap();
1596        match expr {
1597            Expr::Binary(op, _, _) => assert_eq!(op, "+"),
1598            _ => panic!("expected binary expression"),
1599        }
1600    }
1601
1602    #[test]
1603    fn calc_sum_range() {
1604        let f = new_file();
1605        f.set_cell_int("Sheet1", "A1", 1).unwrap();
1606        f.set_cell_int("Sheet1", "A2", 2).unwrap();
1607        f.set_cell_int("Sheet1", "A3", 3).unwrap();
1608        f.set_cell_formula("Sheet1", "A4", "SUM(A1:A3)").unwrap();
1609        assert_eq!(f.calc_cell_value("Sheet1", "A4").unwrap(), "6");
1610    }
1611
1612    #[test]
1613    fn calc_average_and_count() {
1614        let f = new_file();
1615        f.set_cell_int("Sheet1", "B1", 10).unwrap();
1616        f.set_cell_int("Sheet1", "B2", 20).unwrap();
1617        f.set_cell_int("Sheet1", "B3", 30).unwrap();
1618        f.set_cell_formula("Sheet1", "B4", "AVERAGE(B1:B3)")
1619            .unwrap();
1620        f.set_cell_formula("Sheet1", "B5", "COUNT(B1:B3)").unwrap();
1621        assert_eq!(f.calc_cell_value("Sheet1", "B4").unwrap(), "20");
1622        assert_eq!(f.calc_cell_value("Sheet1", "B5").unwrap(), "3");
1623    }
1624
1625    #[test]
1626    fn calc_max_min() {
1627        let f = new_file();
1628        f.set_cell_int("Sheet1", "C1", 5).unwrap();
1629        f.set_cell_int("Sheet1", "C2", 9).unwrap();
1630        f.set_cell_int("Sheet1", "C3", 2).unwrap();
1631        f.set_cell_formula("Sheet1", "C4", "MAX(C1:C3)").unwrap();
1632        f.set_cell_formula("Sheet1", "C5", "MIN(C1:C3)").unwrap();
1633        assert_eq!(f.calc_cell_value("Sheet1", "C4").unwrap(), "9");
1634        assert_eq!(f.calc_cell_value("Sheet1", "C5").unwrap(), "2");
1635    }
1636
1637    #[test]
1638    fn calc_if() {
1639        let f = new_file();
1640        f.set_cell_int("Sheet1", "D1", 5).unwrap();
1641        f.set_cell_formula("Sheet1", "D2", "IF(D1>3,\"yes\",\"no\")")
1642            .unwrap();
1643        f.set_cell_formula("Sheet1", "D3", "IF(D1<3,\"yes\",\"no\")")
1644            .unwrap();
1645        assert_eq!(f.calc_cell_value("Sheet1", "D2").unwrap(), "yes");
1646        assert_eq!(f.calc_cell_value("Sheet1", "D3").unwrap(), "no");
1647    }
1648
1649    #[test]
1650    fn calc_vlookup_exact() {
1651        let f = new_file();
1652        f.set_cell_int("Sheet1", "A1", 1).unwrap();
1653        f.set_cell_str("Sheet1", "B1", "one").unwrap();
1654        f.set_cell_int("Sheet1", "A2", 2).unwrap();
1655        f.set_cell_str("Sheet1", "B2", "two").unwrap();
1656        f.set_cell_int("Sheet1", "A3", 3).unwrap();
1657        f.set_cell_str("Sheet1", "B3", "three").unwrap();
1658        f.set_cell_formula("Sheet1", "C1", "VLOOKUP(2,A1:B3,2,FALSE)")
1659            .unwrap();
1660        assert_eq!(f.calc_cell_value("Sheet1", "C1").unwrap(), "two");
1661    }
1662
1663    #[test]
1664    fn calc_concatenate_and_ampersand() {
1665        let f = new_file();
1666        f.set_cell_str("Sheet1", "E1", "Hello").unwrap();
1667        f.set_cell_str("Sheet1", "E2", "World").unwrap();
1668        f.set_cell_formula("Sheet1", "E3", "CONCATENATE(E1,\" \",E2)")
1669            .unwrap();
1670        f.set_cell_formula("Sheet1", "E4", "E1&\" \"&E2").unwrap();
1671        assert_eq!(f.calc_cell_value("Sheet1", "E3").unwrap(), "Hello World");
1672        assert_eq!(f.calc_cell_value("Sheet1", "E4").unwrap(), "Hello World");
1673    }
1674
1675    #[test]
1676    fn calc_abs_and_round() {
1677        let f = new_file();
1678        f.set_cell_formula("Sheet1", "F1", "ABS(-12.5)").unwrap();
1679        f.set_cell_formula("Sheet1", "F2", "ROUND(3.14159,2)")
1680            .unwrap();
1681        assert_eq!(f.calc_cell_value("Sheet1", "F1").unwrap(), "12.5");
1682        assert_eq!(f.calc_cell_value("Sheet1", "F2").unwrap(), "3.14");
1683    }
1684
1685    #[test]
1686    fn calc_cross_sheet_reference() {
1687        let f = new_file();
1688        f.set_cell_int("Sheet1", "G1", 42).unwrap();
1689        f.set_cell_formula("Sheet1", "G2", "Sheet1!G1+8").unwrap();
1690        assert_eq!(f.calc_cell_value("Sheet1", "G2").unwrap(), "50");
1691    }
1692
1693    #[test]
1694    fn calc_today_and_now_are_serials() {
1695        let f = new_file();
1696        f.set_cell_formula("Sheet1", "H1", "TODAY()").unwrap();
1697        f.set_cell_formula("Sheet1", "H2", "NOW()").unwrap();
1698        let today = f.calc_cell_value("Sheet1", "H1").unwrap();
1699        assert!(today.parse::<f64>().is_ok(), "TODAY() returned {:?}", today);
1700        assert!(today.parse::<f64>().unwrap() > 0.0);
1701
1702        let now = f.calc_cell_value("Sheet1", "H2").unwrap();
1703        assert!(now.parse::<f64>().is_ok(), "NOW() returned {:?}", now);
1704        assert!(now.parse::<f64>().unwrap() > 0.0);
1705    }
1706
1707    #[test]
1708    fn calc_unsupported_function_returns_name_error() {
1709        let f = new_file();
1710        f.set_cell_formula("Sheet1", "I1", "NOTSUPPORTED(1)")
1711            .unwrap();
1712        assert_eq!(f.calc_cell_value("Sheet1", "I1").unwrap(), "#NAME?");
1713    }
1714
1715    #[test]
1716    fn calc_non_formula_cell_falls_back() {
1717        let f = new_file();
1718        f.set_cell_str("Sheet1", "J1", "plain").unwrap();
1719        assert_eq!(f.calc_cell_value("Sheet1", "J1").unwrap(), "plain");
1720    }
1721
1722    #[test]
1723    fn calc_indirect() {
1724        let f = new_file();
1725        f.set_cell_int("Sheet1", "A1", 42).unwrap();
1726        f.set_cell_formula("Sheet1", "B1", "INDIRECT(\"A1\")")
1727            .unwrap();
1728        assert_eq!(f.calc_cell_value("Sheet1", "B1").unwrap(), "42");
1729
1730        f.set_cell_formula("Sheet1", "C1", "INDIRECT(\"R1C1\",FALSE)")
1731            .unwrap();
1732        assert_eq!(f.calc_cell_value("Sheet1", "C1").unwrap(), "42");
1733    }
1734
1735    #[test]
1736    fn calc_formulatext_and_isref() {
1737        let f = new_file();
1738        f.set_cell_formula("Sheet1", "A1", "1+2").unwrap();
1739        f.set_cell_formula("Sheet1", "B1", "FORMULATEXT(A1)")
1740            .unwrap();
1741        assert_eq!(f.calc_cell_value("Sheet1", "B1").unwrap(), "=1+2");
1742
1743        f.set_cell_formula("Sheet1", "C1", "ISREF(A1)").unwrap();
1744        f.set_cell_formula("Sheet1", "D1", "ISREF(\"A1\")").unwrap();
1745        assert_eq!(f.calc_cell_value("Sheet1", "C1").unwrap(), "TRUE");
1746        assert_eq!(f.calc_cell_value("Sheet1", "D1").unwrap(), "FALSE");
1747    }
1748
1749    #[test]
1750    fn calc_column_row_sheet_sheets() {
1751        let f = new_file();
1752        f.new_sheet("Sheet2").unwrap();
1753        f.set_cell_formula("Sheet1", "A1", "COLUMN(B3)").unwrap();
1754        f.set_cell_formula("Sheet1", "A2", "ROW(B3)").unwrap();
1755        f.set_cell_formula("Sheet1", "A3", "SHEETS()").unwrap();
1756        f.set_cell_formula("Sheet1", "A4", "SHEET(Sheet1!A1)")
1757            .unwrap();
1758        f.set_cell_formula("Sheet1", "A5", "SHEET(Sheet2!A1)")
1759            .unwrap();
1760        assert_eq!(f.calc_cell_value("Sheet1", "A1").unwrap(), "2");
1761        assert_eq!(f.calc_cell_value("Sheet1", "A2").unwrap(), "3");
1762        assert_eq!(f.calc_cell_value("Sheet1", "A3").unwrap(), "2");
1763        let sheet1: i32 = f.calc_cell_value("Sheet1", "A4").unwrap().parse().unwrap();
1764        let sheet2: i32 = f.calc_cell_value("Sheet1", "A5").unwrap().parse().unwrap();
1765        assert!(sheet1 < sheet2);
1766    }
1767
1768    #[test]
1769    fn calc_norm_dist_and_sortby() {
1770        let f = new_file();
1771        f.set_cell_formula("Sheet1", "A1", "NORM.DIST(0,0,1,FALSE)")
1772            .unwrap();
1773        let v: f64 = f.calc_cell_value("Sheet1", "A1").unwrap().parse().unwrap();
1774        assert!((v - 0.398_942_280_4).abs() < 1e-6);
1775
1776        f.set_cell_int("Sheet1", "C1", 3).unwrap();
1777        f.set_cell_int("Sheet1", "C2", 1).unwrap();
1778        f.set_cell_int("Sheet1", "C3", 2).unwrap();
1779        f.set_cell_int("Sheet1", "D1", 30).unwrap();
1780        f.set_cell_int("Sheet1", "D2", 10).unwrap();
1781        f.set_cell_int("Sheet1", "D3", 20).unwrap();
1782        f.set_cell_formula("Sheet1", "E1", "SORTBY(C1:C3,D1:D3)")
1783            .unwrap();
1784        assert_eq!(f.calc_cell_value("Sheet1", "E1").unwrap(), "1");
1785    }
1786
1787    #[test]
1788    fn calc_array_literal() {
1789        let f = new_file();
1790        f.set_cell_formula("Sheet1", "A1", "SUM({1,2;3,4})")
1791            .unwrap();
1792        assert_eq!(f.calc_cell_value("Sheet1", "A1").unwrap(), "10");
1793    }
1794
1795    #[test]
1796    fn calc_3d_reference() {
1797        let f = new_file();
1798        f.new_sheet("Sheet2").unwrap();
1799        f.new_sheet("Sheet3").unwrap();
1800        f.set_cell_int("Sheet1", "A1", 1).unwrap();
1801        f.set_cell_int("Sheet2", "A1", 10).unwrap();
1802        f.set_cell_int("Sheet3", "A1", 100).unwrap();
1803        f.set_cell_formula("Sheet1", "B1", "SUM(Sheet1:Sheet3!A1)")
1804            .unwrap();
1805        assert_eq!(f.calc_cell_value("Sheet1", "B1").unwrap(), "111");
1806    }
1807
1808    #[test]
1809    fn calc_defined_name() {
1810        let f = new_file();
1811        f.set_cell_int("Sheet1", "A1", 5).unwrap();
1812        f.set_cell_int("Sheet1", "A2", 7).unwrap();
1813        f.set_defined_name(&crate::xml::workbook::DefinedName {
1814            name: "MyRange".to_string(),
1815            refers_to: "A1:A2".to_string(),
1816            ..Default::default()
1817        })
1818        .unwrap();
1819        f.set_cell_formula("Sheet1", "B1", "SUM(MyRange)").unwrap();
1820        assert_eq!(f.calc_cell_value("Sheet1", "B1").unwrap(), "12");
1821    }
1822
1823    #[test]
1824    fn calc_iterative_circular() {
1825        let mut opts = crate::options::Options::default();
1826        opts.max_calc_iterations = 5;
1827        let f = File::new_with_options(opts);
1828        f.set_cell_formula("Sheet1", "A1", "A2+1").unwrap();
1829        f.set_cell_formula("Sheet1", "A2", "A1+1").unwrap();
1830        assert_eq!(f.calc_cell_value("Sheet1", "A1").unwrap(), "2");
1831    }
1832
1833    #[test]
1834    fn calc_raw_cell_value_cache() {
1835        let mut opts = crate::options::Options::default();
1836        opts.raw_cell_value = true;
1837        let f = File::new_with_options(opts);
1838        f.set_cell_formula("Sheet1", "A1", "1+1").unwrap();
1839        assert_eq!(f.calc_cell_value("Sheet1", "A1").unwrap(), "2");
1840        // set_cell_value does not clear the calc cache, so the cached result
1841        // should still be returned until the cache is explicitly cleared.
1842        f.set_cell_int("Sheet1", "A1", 99).unwrap();
1843        assert_eq!(f.calc_cell_value("Sheet1", "A1").unwrap(), "2");
1844        f.clear_calc_cache();
1845        assert_eq!(f.calc_cell_value("Sheet1", "A1").unwrap(), "99");
1846    }
1847
1848    #[test]
1849    fn calc_iferror_arity() {
1850        let f = new_file();
1851        f.set_cell_formula("Sheet1", "A1", "IFERROR(1,2,3)")
1852            .unwrap();
1853        assert_eq!(f.calc_cell_value("Sheet1", "A1").unwrap(), "#VALUE!");
1854    }
1855
1856    #[test]
1857    fn calc_sumif_text_numbers() {
1858        let f = new_file();
1859        f.set_cell_str("Sheet1", "A1", "1").unwrap();
1860        f.set_cell_str("Sheet1", "A2", "2").unwrap();
1861        f.set_cell_int("Sheet1", "A3", 3).unwrap();
1862        f.set_cell_formula("Sheet1", "A4", "SUMIF(A1:A3,\">0\")")
1863            .unwrap();
1864        assert_eq!(f.calc_cell_value("Sheet1", "A4").unwrap(), "6");
1865    }
1866
1867    #[test]
1868    fn calc_text_formats() {
1869        let f = new_file();
1870
1871        // Common numeric formats.
1872        f.set_cell_formula("Sheet1", "A1", "TEXT(1234.5,\"#,##0.00\")")
1873            .unwrap();
1874        assert_eq!(f.calc_cell_value("Sheet1", "A1").unwrap(), "1,234.50");
1875
1876        // Underscore alignment: positive section leaves a space the width of ')'.
1877        f.set_cell_formula("Sheet1", "A2", "TEXT(1234.5,\"0.00_);(0.00)\")")
1878            .unwrap();
1879        assert_eq!(f.calc_cell_value("Sheet1", "A2").unwrap(), "1234.50 ");
1880
1881        // Three-section format picks the positive section.
1882        f.set_cell_formula("Sheet1", "A3", "TEXT(1234.5,\"0.00;[Red](0.00);zero\")")
1883            .unwrap();
1884        assert_eq!(f.calc_cell_value("Sheet1", "A3").unwrap(), "1234.50");
1885
1886        // Zero section.
1887        f.set_cell_formula("Sheet1", "A4", "TEXT(0,\"0.00;[Red](0.00);zero\")")
1888            .unwrap();
1889        assert_eq!(f.calc_cell_value("Sheet1", "A4").unwrap(), "zero");
1890
1891        // Fixed-width integer pattern.
1892        f.set_cell_formula("Sheet1", "A5", "TEXT(1234,\"0000\")")
1893            .unwrap();
1894        assert_eq!(f.calc_cell_value("Sheet1", "A5").unwrap(), "1234");
1895
1896        // Four-section format: text value uses the fourth section.
1897        f.set_cell_formula("Sheet1", "A6", "TEXT(\"abc\",\"0.00;[Red](0.00);zero;@\")")
1898            .unwrap();
1899        assert_eq!(f.calc_cell_value("Sheet1", "A6").unwrap(), "abc");
1900
1901        // Go-compatible baseline cases.
1902        f.set_cell_formula("Sheet1", "A7", "TEXT(\"07/07/2015\",\"mm/dd/yyyy\")")
1903            .unwrap();
1904        assert_eq!(f.calc_cell_value("Sheet1", "A7").unwrap(), "07/07/2015");
1905        f.set_cell_formula("Sheet1", "A8", "TEXT(42192,\"mm/dd/yyyy\")")
1906            .unwrap();
1907        assert_eq!(f.calc_cell_value("Sheet1", "A8").unwrap(), "07/07/2015");
1908        f.set_cell_formula("Sheet1", "A9", "TEXT(42192,\"mmm dd yyyy\")")
1909            .unwrap();
1910        assert_eq!(f.calc_cell_value("Sheet1", "A9").unwrap(), "Jul 07 2015");
1911        f.set_cell_formula("Sheet1", "A10", "TEXT(0.75,\"hh:mm\")")
1912            .unwrap();
1913        assert_eq!(f.calc_cell_value("Sheet1", "A10").unwrap(), "18:00");
1914        f.set_cell_formula("Sheet1", "A11", "TEXT(36.363636,\"0.00\")")
1915            .unwrap();
1916        assert_eq!(f.calc_cell_value("Sheet1", "A11").unwrap(), "36.36");
1917        f.set_cell_formula("Sheet1", "A12", "TEXT(567.9,\"$#,##0.00\")")
1918            .unwrap();
1919        assert_eq!(f.calc_cell_value("Sheet1", "A12").unwrap(), "$567.90");
1920        f.set_cell_formula(
1921            "Sheet1",
1922            "A13",
1923            "TEXT(-5,\"+ $#,##0.00;- $#,##0.00;$0.00\")",
1924        )
1925        .unwrap();
1926        assert_eq!(f.calc_cell_value("Sheet1", "A13").unwrap(), "- $5.00");
1927        f.set_cell_formula("Sheet1", "A14", "TEXT(5,\"+ $#,##0.00;- $#,##0.00;$0.00\")")
1928            .unwrap();
1929        assert_eq!(f.calc_cell_value("Sheet1", "A14").unwrap(), "+ $5.00");
1930    }
1931
1932    #[test]
1933    fn calc_text_conditional_formats() {
1934        let f = new_file();
1935
1936        // First conditional section matches.
1937        f.set_cell_formula("Sheet1", "A1", "TEXT(150,\"[>100]0.00;0.0\")")
1938            .unwrap();
1939        assert_eq!(f.calc_cell_value("Sheet1", "A1").unwrap(), "150.00");
1940
1941        // Value does not meet the condition, use the default section.
1942        f.set_cell_formula("Sheet1", "A2", "TEXT(50,\"[>100]0.00;0.0\")")
1943            .unwrap();
1944        assert_eq!(f.calc_cell_value("Sheet1", "A2").unwrap(), "50.0");
1945
1946        // Multiple conditional sections with text-only bodies.
1947        f.set_cell_formula(
1948            "Sheet1",
1949            "A3",
1950            "TEXT(85,\"[>=90]\"\"A\"\";[>=60]\"\"B\"\";\"\"C\"\"\")",
1951        )
1952        .unwrap();
1953        assert_eq!(f.calc_cell_value("Sheet1", "A3").unwrap(), "B");
1954
1955        // Color metadata is stripped; negative default section keeps parentheses.
1956        f.set_cell_formula("Sheet1", "A4", "TEXT(-10,\"[>0]0.00;[Red](0.00)\")")
1957            .unwrap();
1958        assert_eq!(f.calc_cell_value("Sheet1", "A4").unwrap(), "(10.00)");
1959    }
1960}