Skip to main content

vla_lang/calc/
formula.rs

1//! The formula parser (KERNEL.7, 2026-10-08): operators and calls over the
2//! reference scanner the language already has.
3//!
4//! `refers::scan` reads every reference out of a formula's text and nothing
5//! else, held to the refers golden the reference writes; this module reads
6//! the rest, the literals, the operators and the function calls, splicing
7//! the scanner's records in by position, so that the one reading of what
8//! `A1`, `Data!B2:C9`, `Rate` or `'Q1 Data'!A:A` means stays in `refers.rs`.
9//! The grammar is the file's: a formula is stored in the invariant spelling,
10//! English function names, `,` between arguments and `.` as the decimal
11//! mark, whatever locale typed it (ISO/IEC 29500-1, 18.17). Excel's
12//! precedence, lowest first: the comparisons; `&`; `+` and `-`; `*` and
13//! `/`; `^`, left to right; a sign, so that `-2^2` is 4; `%`.
14//!
15//! A construct this slice does not read is not an error: the parse answers
16//! with the construct as written, and the cell shows `not computed here`
17//! naming it, the honest static label of `HORIZON.md` section 12.6. Those
18//! are: an array constant (`{1,2;3,4}`), implicit intersection by `@` or by
19//! a space between two references, a union in parentheses, a spill range
20//! (`A1#`), a structured reference (`Sales[Amount]`), a link into another
21//! workbook (`[Rates.xlsx]Sheet1!A1`), and a range where one value is
22//! wanted (`=B1:B2*2`), which a legacy formula intersects and a dynamic one
23//! spills; the reader cannot tell the two apart from the text alone, so
24//! neither is claimed (`KERNEL.8` takes them with the `@` operator).
25
26use crate::messages::{raise, Refusal};
27use crate::refers::{self, is_ident_char, FormulaRef, Kind, Shape};
28use crate::sheet::{MAX_COLUMN, MAX_ROW};
29
30use super::value::ErrorKind;
31
32/// One reference as the parser hands it to the evaluator: a rectangle on
33/// one sheet, or the same rectangle on every sheet of a span.
34#[derive(Clone, Debug, PartialEq, Eq)]
35pub struct RefExpr {
36    /// The sheet named in front of `!`, or `None` for the formula's own.
37    pub sheet: Option<String>,
38    /// The last sheet of a 3D span, `Jan:Dec!A1`.
39    pub last_sheet: Option<String>,
40    pub top: u32,
41    pub left: u32,
42    pub bottom: u32,
43    pub right: u32,
44    /// The reference as written, for a label.
45    pub written: String,
46    /// The reference's part as the scanner read it (a cell, a range, a
47    /// whole column or row) and its corners as written with their `$`
48    /// marks, which a move at an offset reads (KERNEL.22): the rectangle
49    /// above is these corners sorted.
50    pub shape: Shape,
51    pub corners: Corners,
52    /// The sheets named in front of `!`, the first and the last, found once
53    /// against the grid an engine's machine placed the formula in
54    /// (KERNEL.20), whose sheets are fixed for its life. `None` for a
55    /// reference to the formula's own sheet, for a sheet the grid does not
56    /// hold, for one past the 256th, and for any parse not placed, each of
57    /// which finds its sheets by name at every evaluation, as before. A byte
58    /// each, so that the field fits the padding the struct already had: a
59    /// parse's size is what `VLA.1`'s limits are pinned on, a stack of 1 MiB,
60    /// and the reference is the largest kind of expression (measured: two
61    /// words would have made every expression 24 bytes larger and the
62    /// deepest formula's load 192 KiB deeper).
63    pub found: Option<(u8, u8)>,
64}
65
66/// A reference's corners as written, each number with its `$` mark, as
67/// `refers::FormulaRef` holds them (a cell's second corner 0): what
68/// [`RefExpr::moved`] moves, corner by corner, as
69/// `refers::shift_a1_references` moves the text.
70#[derive(Clone, Copy, Debug, PartialEq, Eq)]
71pub struct Corners {
72    pub row1: u32,
73    pub col1: u32,
74    pub row2: u32,
75    pub col2: u32,
76    pub row_abs1: bool,
77    pub col_abs1: bool,
78    pub row_abs2: bool,
79    pub col_abs2: bool,
80}
81
82impl RefExpr {
83    /// Whether the reference names one cell.
84    pub fn is_cell(&self) -> bool {
85        self.top == self.bottom && self.left == self.right && self.last_sheet.is_none()
86    }
87
88    /// The rectangle this reference names in a cell `d_row` rows down and
89    /// `d_col` columns right of the cell whose formula it was read from: each
90    /// relative number moved and each `$` one kept, corner by corner as
91    /// written, then sorted, which is what parsing the text that
92    /// `refers::shift_a1_references` moves gives (KERNEL.22, a shape
93    /// evaluated at an offset); `None` when a corner leaves the sheet,
94    /// where the mover writes `#REF!`. At no offset it is the rectangle as
95    /// read.
96    pub fn moved(&self, d_row: i64, d_col: i64) -> Option<(u32, u32, u32, u32)> {
97        if d_row == 0 && d_col == 0 {
98            return Some((self.top, self.left, self.bottom, self.right));
99        }
100        let c = &self.corners;
101        let row = |n: u32, abs: bool| shifted(n, abs, d_row, MAX_ROW);
102        let col = |n: u32, abs: bool| shifted(n, abs, d_col, MAX_COLUMN);
103        match self.shape {
104            Shape::Cell => {
105                let k = col(c.col1, c.col_abs1)?;
106                let r = row(c.row1, c.row_abs1)?;
107                Some((r, k, r, k))
108            }
109            Shape::Range => {
110                let k1 = col(c.col1, c.col_abs1)?;
111                let r1 = row(c.row1, c.row_abs1)?;
112                let k2 = col(c.col2, c.col_abs2)?;
113                let r2 = row(c.row2, c.row_abs2)?;
114                Some((r1.min(r2), k1.min(k2), r1.max(r2), k1.max(k2)))
115            }
116            Shape::Column => {
117                let k1 = col(c.col1, c.col_abs1)?;
118                let k2 = col(c.col2, c.col_abs2)?;
119                Some((1, k1.min(k2), MAX_ROW, k1.max(k2)))
120            }
121            Shape::Row => {
122                let r1 = row(c.row1, c.row_abs1)?;
123                let r2 = row(c.row2, c.row_abs2)?;
124                Some((r1.min(r2), 1, r1.max(r2), MAX_COLUMN))
125            }
126            Shape::None => None,
127        }
128    }
129}
130
131/// A number moved by a distance unless its `$` holds it, `None` off the
132/// sheet: `refers.rs`'s rule for the text, kept the same here.
133fn shifted(n: u32, is_abs: bool, d: i64, max: u32) -> Option<u32> {
134    if is_abs {
135        return Some(n);
136    }
137    let m = i64::from(n) + d;
138    (m >= 1 && m <= i64::from(max)).then_some(m as u32)
139}
140
141/// The binary operators.
142#[derive(Clone, Copy, Debug, PartialEq, Eq)]
143pub enum BinOp {
144    Add,
145    Sub,
146    Mul,
147    Div,
148    Pow,
149    Concat,
150    Eq,
151    Ne,
152    Lt,
153    Gt,
154    Le,
155    Ge,
156}
157
158/// A parsed formula.
159#[derive(Clone, Debug, PartialEq)]
160pub enum Expr {
161    Number(f64),
162    Text(String),
163    Bool(bool),
164    Error(ErrorKind),
165    /// An argument left out, `IF(A1,,B1)`.
166    Empty,
167    Ref(RefExpr),
168    /// A defined name, with the sheet that scopes it when one is written.
169    Name {
170        name: String,
171        sheet: Option<String>,
172    },
173    /// A function call, the name in upper case.
174    Call(String, Vec<Expr>),
175    Neg(Box<Expr>),
176    Pos(Box<Expr>),
177    Percent(Box<Expr>),
178    Binary(BinOp, Box<Expr>, Box<Expr>),
179}
180
181/// What an expression reads besides its literals: a reference, or a defined
182/// name with the sheet written in front of it when there is one.
183#[derive(Clone, Copy, Debug, PartialEq)]
184pub enum Read<'e> {
185    Ref(&'e RefExpr),
186    Name {
187        name: &'e str,
188        sheet: Option<&'e str>,
189    },
190}
191
192impl Expr {
193    /// Every reference and every defined name the expression reads, in the
194    /// order they are written (KERNEL.22: a shape's edges are read off its
195    /// parse, as `graph::dependencies` reads them off the text).
196    pub fn each_read<'e>(&'e self, f: &mut dyn FnMut(Read<'e>)) {
197        let mut stack = vec![self];
198        while let Some(e) = stack.pop() {
199            match e {
200                Expr::Ref(r) => f(Read::Ref(r)),
201                Expr::Name { name, sheet } => f(Read::Name {
202                    name,
203                    sheet: sheet.as_deref(),
204                }),
205                other => other.push_children(&mut stack),
206            }
207        }
208    }
209
210    /// The functions the expression calls, upper-cased, in the order they
211    /// are written, repeats kept: [`calls`] over a parse.
212    pub fn each_call<'e>(&'e self, f: &mut dyn FnMut(&'e str)) {
213        let mut stack = vec![self];
214        while let Some(e) = stack.pop() {
215            if let Expr::Call(name, _) = e {
216                f(name);
217            }
218            e.push_children(&mut stack);
219        }
220    }
221
222    /// The walks' step (VLA.1): an expression's operands onto a stack, last
223    /// first, so that popping visits them in the order they are written.
224    /// The walks keep their own stack rather than the call stack, since a
225    /// chain as long as Excel holds is a tree 4,095 deep down its left side,
226    /// and a machine's plan reads every shape's references at its load.
227    fn push_children<'e>(&'e self, stack: &mut Vec<&'e Expr>) {
228        match self {
229            Expr::Call(_, args) => stack.extend(args.iter().rev()),
230            Expr::Neg(x) | Expr::Pos(x) | Expr::Percent(x) => stack.push(x),
231            Expr::Binary(_, a, b) => {
232                stack.push(b);
233                stack.push(a);
234            }
235            Expr::Ref(_)
236            | Expr::Name { .. }
237            | Expr::Number(_)
238            | Expr::Text(_)
239            | Expr::Bool(_)
240            | Expr::Error(_)
241            | Expr::Empty => {}
242        }
243    }
244}
245
246#[derive(Clone, Debug, PartialEq)]
247enum Tok {
248    Num(f64),
249    Str(String),
250    Bool(bool),
251    Err(ErrorKind),
252    Ref(RefExpr),
253    Name {
254        name: String,
255        sheet: Option<String>,
256    },
257    /// An identifier followed by `(`, upper-cased; the `(` follows as its
258    /// own token.
259    Func(String),
260    Op(BinOp),
261    Percent,
262    LParen,
263    RParen,
264    Comma,
265    /// A construct this slice does not read, as written.
266    Other(String),
267}
268
269/// The scanner's record as a reference expression, for the kinds that are
270/// a rectangle: a cell, a range, a whole column, a whole row, on one sheet
271/// or across a span. `None` for every other kind.
272pub fn ref_expr(r: &FormulaRef) -> Option<RefExpr> {
273    if !matches!(
274        r.kind,
275        Kind::Cell | Kind::Range | Kind::Column | Kind::Row | Kind::ThreeD
276    ) {
277        return None;
278    }
279    let (top, left, bottom, right) = match r.shape {
280        Shape::Cell => (r.row1, r.col1, r.row1, r.col1),
281        Shape::Range => (
282            r.row1.min(r.row2),
283            r.col1.min(r.col2),
284            r.row1.max(r.row2),
285            r.col1.max(r.col2),
286        ),
287        Shape::Column => (1, r.col1.min(r.col2), MAX_ROW, r.col1.max(r.col2)),
288        Shape::Row => (r.row1.min(r.row2), 1, r.row1.max(r.row2), MAX_COLUMN),
289        Shape::None => return None,
290    };
291    let (sheet, last_sheet) = if r.sheet_name.is_empty() {
292        (None, None)
293    } else {
294        match r.sheet_name.split_once(':') {
295            Some((first, last)) => (Some(first.to_string()), Some(last.to_string())),
296            None => (Some(r.sheet_name.clone()), None),
297        }
298    };
299    Some(RefExpr {
300        sheet,
301        last_sheet,
302        top,
303        left,
304        bottom,
305        right,
306        written: r.written.clone(),
307        shape: r.shape,
308        corners: Corners {
309            row1: r.row1,
310            col1: r.col1,
311            row2: r.row2,
312            col2: r.col2,
313            row_abs1: r.row_abs1,
314            col_abs1: r.col_abs1,
315            row_abs2: r.row_abs2,
316            col_abs2: r.col_abs2,
317        },
318        found: None,
319    })
320}
321
322fn tokenize(text: &str) -> Vec<Tok> {
323    let s: Vec<char> = text.chars().collect();
324    let refs: Vec<FormulaRef> = refers::scan(text)
325        .into_iter()
326        .filter(|r| r.kind != Kind::Unreadable)
327        .collect();
328    let mut toks = Vec::new();
329    let mut i = usize::from(s.first() == Some(&'='));
330    let mut next_ref = 0;
331    while i < s.len() {
332        while next_ref < refs.len() && refs[next_ref].pos < i {
333            next_ref += 1;
334        }
335        if next_ref < refs.len() && refs[next_ref].pos == i {
336            let r = &refs[next_ref];
337            next_ref += 1;
338            toks.push(match r.kind {
339                Kind::Cell | Kind::Range | Kind::Column | Kind::Row | Kind::ThreeD => {
340                    match ref_expr(r) {
341                        Some(re) => Tok::Ref(re),
342                        None => Tok::Other(r.written.clone()),
343                    }
344                }
345                Kind::Name => Tok::Name {
346                    name: r.part.clone(),
347                    sheet: (!r.sheet_name.is_empty()).then(|| r.sheet_name.clone()),
348                },
349                Kind::Broken => Tok::Err(ErrorKind::Ref),
350                Kind::Structured | Kind::External | Kind::Spill | Kind::Unreadable => {
351                    Tok::Other(r.written.clone())
352                }
353            });
354            i += r.span.max(1);
355            continue;
356        }
357        let c = s[i];
358        if c.is_whitespace() {
359            i += 1;
360        } else if c == '"' {
361            let (text, end) = read_string(&s, i);
362            toks.push(Tok::Str(text));
363            i = end;
364        } else if c.is_ascii_digit()
365            || (c == '.' && s.get(i + 1).is_some_and(|d| d.is_ascii_digit()))
366        {
367            let (tok, end) = read_number(&s, i);
368            toks.push(tok);
369            i = end;
370        } else if c == '#' {
371            let mut k = i + 1;
372            while k < s.len() && (s[k].is_ascii_alphanumeric() || s[k] == '/' || s[k] == '_') {
373                k += 1;
374            }
375            if matches!(s.get(k), Some('!') | Some('?')) {
376                k += 1;
377            }
378            let tok: String = s[i..k].iter().collect();
379            toks.push(match ErrorKind::parse(&tok) {
380                Some(kind) => Tok::Err(kind),
381                None => Tok::Other(tok),
382            });
383            i = k;
384        } else if c.is_ascii_alphabetic() || c == '_' {
385            let mut k = i;
386            while k < s.len() && is_ident_char(s[k]) {
387                k += 1;
388            }
389            let tok: String = s[i..k].iter().collect();
390            if s.get(k) == Some(&'(') {
391                toks.push(Tok::Func(tok.to_ascii_uppercase()));
392            } else {
393                match tok.to_ascii_lowercase().as_str() {
394                    "true" => toks.push(Tok::Bool(true)),
395                    "false" => toks.push(Tok::Bool(false)),
396                    _ => toks.push(Tok::Other(tok)),
397                }
398            }
399            i = k;
400        } else {
401            let two: String = s[i..(i + 2).min(s.len())].iter().collect();
402            let (tok, len) = match two.as_str() {
403                "<>" => (Tok::Op(BinOp::Ne), 2),
404                "<=" => (Tok::Op(BinOp::Le), 2),
405                ">=" => (Tok::Op(BinOp::Ge), 2),
406                _ => match c {
407                    '<' => (Tok::Op(BinOp::Lt), 1),
408                    '>' => (Tok::Op(BinOp::Gt), 1),
409                    '=' => (Tok::Op(BinOp::Eq), 1),
410                    '+' => (Tok::Op(BinOp::Add), 1),
411                    '-' => (Tok::Op(BinOp::Sub), 1),
412                    '*' => (Tok::Op(BinOp::Mul), 1),
413                    '/' => (Tok::Op(BinOp::Div), 1),
414                    '^' => (Tok::Op(BinOp::Pow), 1),
415                    '&' => (Tok::Op(BinOp::Concat), 1),
416                    '%' => (Tok::Percent, 1),
417                    '(' => (Tok::LParen, 1),
418                    ')' => (Tok::RParen, 1),
419                    ',' => (Tok::Comma, 1),
420                    other => (Tok::Other(other.to_string()), 1),
421                },
422            };
423            toks.push(tok);
424            i += len;
425        }
426    }
427    toks
428}
429
430/// A string literal at `i` (its opening quote): the text with `""` read as
431/// `"`, and the position after the closing quote, or the end of the text
432/// when it never closes.
433fn read_string(s: &[char], i: usize) -> (String, usize) {
434    let mut out = String::new();
435    let mut k = i + 1;
436    while k < s.len() {
437        if s[k] == '"' {
438            if s.get(k + 1) == Some(&'"') {
439                out.push('"');
440                k += 2;
441            } else {
442                return (out, k + 1);
443            }
444        } else {
445            out.push(s[k]);
446            k += 1;
447        }
448    }
449    (out, k)
450}
451
452/// A number literal at `i`: digits, an optional fraction, an optional
453/// exponent when digits follow it. A literal no double holds is `#NUM!`.
454fn read_number(s: &[char], i: usize) -> (Tok, usize) {
455    let mut k = i;
456    while k < s.len() && s[k].is_ascii_digit() {
457        k += 1;
458    }
459    if k < s.len() && s[k] == '.' {
460        k += 1;
461        while k < s.len() && s[k].is_ascii_digit() {
462            k += 1;
463        }
464    }
465    if k < s.len() && (s[k] == 'e' || s[k] == 'E') {
466        let mut j = k + 1;
467        if j < s.len() && (s[j] == '+' || s[j] == '-') {
468            j += 1;
469        }
470        let first = j;
471        while j < s.len() && s[j].is_ascii_digit() {
472            j += 1;
473        }
474        if j > first {
475            k = j;
476        }
477    }
478    let text: String = s[i..k].iter().collect();
479    match text.parse::<f64>() {
480        Ok(v) if v.is_finite() => (Tok::Num(v), k),
481        _ => (Tok::Err(ErrorKind::Num), k),
482    }
483}
484
485/// The deepest a formula may nest, Excel's own limit (VLA.1): each pair of
486/// parentheses, each call's arguments, each sign before an operand and each
487/// `%` after one counts a level, so that evaluation, which recurses once a
488/// level, never goes deeper. The deepest real formula nests 6 (Life's
489/// rule). A chain of binary operators is not nesting: it is read in a loop
490/// and evaluated in one ([`LENGTH_LIMIT`] bounds it).
491pub const NESTING_LIMIT: u32 = 64;
492
493/// The longest a formula's text may be, its leading `=` aside: Excel's own
494/// limit of 8,192 characters (VLA.1). It bounds a chain like `1+1+...+1` at
495/// 4,096 links, whose tree is freed recursively.
496pub const LENGTH_LIMIT: usize = 8192;
497
498/// The label [`parse`] answers for a formula nested past [`NESTING_LIMIT`].
499pub const TOO_DEEP: &str = "nesting deeper than 64";
500
501/// The label [`parse`] answers for a formula longer than [`LENGTH_LIMIT`].
502pub const TOO_LONG: &str = "longer than 8,192 characters";
503
504/// The refusal for a formula cell whose text does not parse (VLA.1): one of
505/// Excel's two limits by its own id, any other label as the construct this
506/// version does not read. An engine's load raises it; `frazaro calc` shows
507/// the label in the cell instead.
508pub fn unread_refusal(cell: &str, construct: &str) -> Refusal {
509    match construct {
510        TOO_DEEP => raise(
511            "calc-formula-too-deep",
512            &[("cell", cell), ("limit", &NESTING_LIMIT.to_string())],
513        ),
514        TOO_LONG => raise(
515            "calc-formula-too-long",
516            &[("cell", cell), ("limit", "8,192")],
517        ),
518        _ => raise(
519            "calc-construct-not-read",
520            &[("cell", cell), ("construct", construct)],
521        ),
522    }
523}
524
525struct Parser {
526    toks: Vec<Tok>,
527    at: usize,
528    /// The nesting levels open where the parser stands (VLA.1).
529    depth: u32,
530}
531
532impl Parser {
533    /// `f` one nesting level deeper, refused past [`NESTING_LIMIT`].
534    fn nested<T>(&mut self, f: impl FnOnce(&mut Self) -> Result<T, String>) -> Result<T, String> {
535        if self.depth >= NESTING_LIMIT {
536            return Err(TOO_DEEP.to_string());
537        }
538        self.depth += 1;
539        let r = f(self);
540        self.depth -= 1;
541        r
542    }
543
544    fn peek(&self) -> Option<&Tok> {
545        self.toks.get(self.at)
546    }
547
548    fn take(&mut self) -> Option<Tok> {
549        let t = self.toks.get(self.at).cloned();
550        if t.is_some() {
551            self.at += 1;
552        }
553        t
554    }
555
556    fn peek_op(&self) -> Option<BinOp> {
557        match self.peek() {
558            Some(Tok::Op(op)) => Some(*op),
559            _ => None,
560        }
561    }
562
563    /// What the token spells, for the label of a construct not read.
564    fn label(t: &Tok) -> String {
565        match t {
566            Tok::Other(s) => s.clone(),
567            Tok::Op(op) => match op {
568                BinOp::Add => "+",
569                BinOp::Sub => "-",
570                BinOp::Mul => "*",
571                BinOp::Div => "/",
572                BinOp::Pow => "^",
573                BinOp::Concat => "&",
574                BinOp::Eq => "=",
575                BinOp::Ne => "<>",
576                BinOp::Lt => "<",
577                BinOp::Gt => ">",
578                BinOp::Le => "<=",
579                BinOp::Ge => ">=",
580            }
581            .to_string(),
582            Tok::Percent => "%".to_string(),
583            Tok::LParen => "(".to_string(),
584            Tok::RParen => ")".to_string(),
585            Tok::Comma => ",".to_string(),
586            Tok::Num(v) => format!("{v}"),
587            Tok::Str(s) => format!("\"{s}\""),
588            Tok::Bool(b) => if *b { "TRUE" } else { "FALSE" }.to_string(),
589            Tok::Err(k) => k.text().to_string(),
590            Tok::Ref(r) => r.written.clone(),
591            Tok::Name { name, .. } => name.clone(),
592            Tok::Func(name) => name.clone(),
593        }
594    }
595
596    fn starts_operand(t: &Tok) -> bool {
597        matches!(
598            t,
599            Tok::Num(_)
600                | Tok::Str(_)
601                | Tok::Bool(_)
602                | Tok::Err(_)
603                | Tok::Ref(_)
604                | Tok::Name { .. }
605                | Tok::Func(_)
606                | Tok::LParen
607        )
608    }
609
610    fn comparison(&mut self) -> Result<Expr, String> {
611        let mut left = self.concat()?;
612        while let Some(op) = self.peek_op() {
613            if !matches!(
614                op,
615                BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Gt | BinOp::Le | BinOp::Ge
616            ) {
617                break;
618            }
619            self.at += 1;
620            let right = self.concat()?;
621            left = Expr::Binary(op, Box::new(left), Box::new(right));
622        }
623        Ok(left)
624    }
625
626    fn concat(&mut self) -> Result<Expr, String> {
627        let mut left = self.additive()?;
628        while self.peek_op() == Some(BinOp::Concat) {
629            self.at += 1;
630            let right = self.additive()?;
631            left = Expr::Binary(BinOp::Concat, Box::new(left), Box::new(right));
632        }
633        Ok(left)
634    }
635
636    fn additive(&mut self) -> Result<Expr, String> {
637        let mut left = self.term()?;
638        while let Some(op @ (BinOp::Add | BinOp::Sub)) = self.peek_op() {
639            self.at += 1;
640            let right = self.term()?;
641            left = Expr::Binary(op, Box::new(left), Box::new(right));
642        }
643        Ok(left)
644    }
645
646    fn term(&mut self) -> Result<Expr, String> {
647        let mut left = self.power()?;
648        while let Some(op @ (BinOp::Mul | BinOp::Div)) = self.peek_op() {
649            self.at += 1;
650            let right = self.power()?;
651            left = Expr::Binary(op, Box::new(left), Box::new(right));
652        }
653        Ok(left)
654    }
655
656    fn power(&mut self) -> Result<Expr, String> {
657        let mut left = self.unary()?;
658        while self.peek_op() == Some(BinOp::Pow) {
659            self.at += 1;
660            let right = self.unary()?;
661            left = Expr::Binary(BinOp::Pow, Box::new(left), Box::new(right));
662        }
663        Ok(left)
664    }
665
666    fn unary(&mut self) -> Result<Expr, String> {
667        match self.peek_op() {
668            Some(BinOp::Sub) => {
669                self.at += 1;
670                Ok(Expr::Neg(Box::new(self.nested(Self::unary)?)))
671            }
672            Some(BinOp::Add) => {
673                self.at += 1;
674                Ok(Expr::Pos(Box::new(self.nested(Self::unary)?)))
675            }
676            _ => self.postfix(),
677        }
678    }
679
680    fn postfix(&mut self) -> Result<Expr, String> {
681        let mut e = self.primary()?;
682        let mut percents = 0;
683        while self.peek() == Some(&Tok::Percent) {
684            // Each `%` wraps what came before, a level its evaluation
685            // recurses through (VLA.1).
686            if self.depth + percents >= NESTING_LIMIT {
687                return Err(TOO_DEEP.to_string());
688            }
689            percents += 1;
690            self.at += 1;
691            e = Expr::Percent(Box::new(e));
692        }
693        if let Some(t) = self.peek() {
694            if Self::starts_operand(t) {
695                // Two operands with nothing between them: Excel's
696                // intersection operator, a space.
697                return Err("intersection".to_string());
698            }
699        }
700        Ok(e)
701    }
702
703    fn primary(&mut self) -> Result<Expr, String> {
704        let Some(t) = self.take() else {
705            return Err("end of formula".to_string());
706        };
707        match t {
708            Tok::Num(v) => Ok(Expr::Number(v)),
709            Tok::Str(s) => Ok(Expr::Text(s)),
710            Tok::Bool(b) => Ok(Expr::Bool(b)),
711            Tok::Err(k) => Ok(Expr::Error(k)),
712            Tok::Ref(r) => Ok(Expr::Ref(r)),
713            Tok::Name { name, sheet } => Ok(Expr::Name { name, sheet }),
714            Tok::Func(name) => {
715                if self.take() != Some(Tok::LParen) {
716                    return Err(name);
717                }
718                self.nested(|p| p.call(name))
719            }
720            Tok::LParen => {
721                let e = self.nested(Self::comparison)?;
722                match self.take() {
723                    Some(Tok::RParen) => Ok(e),
724                    Some(Tok::Comma) => Err("union".to_string()),
725                    Some(other) => Err(Self::label(&other)),
726                    None => Err("end of formula".to_string()),
727                }
728            }
729            other => Err(Self::label(&other)),
730        }
731    }
732
733    /// A call's arguments after its `(`, through its `)`, a level deeper.
734    fn call(&mut self, name: String) -> Result<Expr, String> {
735        let mut args = Vec::new();
736        if self.peek() == Some(&Tok::RParen) {
737            self.at += 1;
738            return Ok(Expr::Call(name, args));
739        }
740        loop {
741            match self.peek() {
742                Some(Tok::Comma) | Some(Tok::RParen) => args.push(Expr::Empty),
743                _ => args.push(self.comparison()?),
744            }
745            match self.take() {
746                Some(Tok::Comma) => continue,
747                Some(Tok::RParen) => break,
748                Some(other) => return Err(Self::label(&other)),
749                None => return Err("end of formula".to_string()),
750            }
751        }
752        Ok(Expr::Call(name, args))
753    }
754}
755
756/// A formula's text, with or without its leading `=`, as an expression; or
757/// the construct this slice does not read, as written, for the label: one
758/// of [`TOO_LONG`] and [`TOO_DEEP`] past Excel's two limits (VLA.1).
759pub fn parse(text: &str) -> Result<Expr, String> {
760    if text.strip_prefix('=').unwrap_or(text).chars().count() > LENGTH_LIMIT {
761        return Err(TOO_LONG.to_string());
762    }
763    let mut p = Parser {
764        toks: tokenize(text),
765        at: 0,
766        depth: 0,
767    };
768    if p.toks.is_empty() {
769        return Err("end of formula".to_string());
770    }
771    let e = p.comparison()?;
772    match p.peek() {
773        None => Ok(e),
774        Some(t) => Err(Parser::label(t)),
775    }
776}
777
778/// The functions a formula's text calls, upper-cased, in order, repeats
779/// kept: what a histogram over a workbook counts (`frazaro reflect
780/// --functions`), read before anything is evaluated.
781pub fn calls(text: &str) -> Vec<String> {
782    tokenize(text)
783        .into_iter()
784        .filter_map(|t| match t {
785            Tok::Func(name) => Some(name),
786            _ => None,
787        })
788        .collect()
789}
790
791#[cfg(test)]
792mod tests {
793    use super::*;
794
795    fn cell(written: &str, row: u32, col: u32) -> Expr {
796        Expr::Ref(RefExpr {
797            sheet: None,
798            last_sheet: None,
799            top: row,
800            left: col,
801            bottom: row,
802            right: col,
803            written: written.to_string(),
804            shape: Shape::Cell,
805            corners: Corners {
806                row1: row,
807                col1: col,
808                row2: 0,
809                col2: 0,
810                row_abs1: false,
811                col_abs1: false,
812                row_abs2: false,
813                col_abs2: false,
814            },
815            found: None,
816        })
817    }
818
819    #[test]
820    fn literals_and_references_are_read() {
821        assert_eq!(parse("=5"), Ok(Expr::Number(5.0)));
822        assert_eq!(parse("1.5e3"), Ok(Expr::Number(1500.0)));
823        assert_eq!(parse(".5"), Ok(Expr::Number(0.5)));
824        assert_eq!(parse("=\"a\"\"b\""), Ok(Expr::Text("a\"b".to_string())));
825        assert_eq!(parse("=TRUE"), Ok(Expr::Bool(true)));
826        assert_eq!(parse("=false"), Ok(Expr::Bool(false)));
827        assert_eq!(parse("=#N/A"), Ok(Expr::Error(ErrorKind::NA)));
828        assert_eq!(parse("=#REF!"), Ok(Expr::Error(ErrorKind::Ref)));
829        assert_eq!(parse("=B2"), Ok(cell("B2", 2, 2)));
830        assert_eq!(
831            parse("=Rate"),
832            Ok(Expr::Name {
833                name: "Rate".to_string(),
834                sheet: None
835            })
836        );
837        assert_eq!(
838            parse("=Model!Local"),
839            Ok(Expr::Name {
840                name: "Local".to_string(),
841                sheet: Some("Model".to_string())
842            })
843        );
844        let r = match parse("='Q1 Data'!A:A").unwrap() {
845            Expr::Ref(r) => r,
846            other => panic!("{other:?}"),
847        };
848        assert_eq!(r.sheet.as_deref(), Some("Q1 Data"));
849        assert_eq!((r.top, r.left, r.bottom, r.right), (1, 1, MAX_ROW, 1));
850        let span = match parse("=Model:Scratch!B1").unwrap() {
851            Expr::Ref(r) => r,
852            other => panic!("{other:?}"),
853        };
854        assert_eq!(span.sheet.as_deref(), Some("Model"));
855        assert_eq!(span.last_sheet.as_deref(), Some("Scratch"));
856        assert_eq!((span.top, span.left, span.bottom, span.right), (1, 2, 1, 2));
857        assert!(
858            !span.is_cell(),
859            "a span is one cell on each of its sheets, not one cell"
860        );
861    }
862
863    #[test]
864    fn precedence_is_excel_s() {
865        // -2^2 is 4: the sign binds tighter than the power.
866        assert_eq!(
867            parse("=-2^2"),
868            Ok(Expr::Binary(
869                BinOp::Pow,
870                Box::new(Expr::Neg(Box::new(Expr::Number(2.0)))),
871                Box::new(Expr::Number(2.0))
872            ))
873        );
874        // 1+2*3 groups the product; 2^3^2 groups to the left.
875        assert_eq!(
876            parse("=1+2*3"),
877            Ok(Expr::Binary(
878                BinOp::Add,
879                Box::new(Expr::Number(1.0)),
880                Box::new(Expr::Binary(
881                    BinOp::Mul,
882                    Box::new(Expr::Number(2.0)),
883                    Box::new(Expr::Number(3.0))
884                ))
885            ))
886        );
887        assert_eq!(
888            parse("=2^3^2"),
889            Ok(Expr::Binary(
890                BinOp::Pow,
891                Box::new(Expr::Binary(
892                    BinOp::Pow,
893                    Box::new(Expr::Number(2.0)),
894                    Box::new(Expr::Number(3.0))
895                )),
896                Box::new(Expr::Number(2.0))
897            ))
898        );
899        // & below arithmetic, the comparisons below &.
900        assert_eq!(
901            parse("=1&2=\"12\""),
902            Ok(Expr::Binary(
903                BinOp::Eq,
904                Box::new(Expr::Binary(
905                    BinOp::Concat,
906                    Box::new(Expr::Number(1.0)),
907                    Box::new(Expr::Number(2.0))
908                )),
909                Box::new(Expr::Text("12".to_string()))
910            ))
911        );
912        assert_eq!(
913            parse("=50%"),
914            Ok(Expr::Percent(Box::new(Expr::Number(50.0))))
915        );
916        assert_eq!(
917            parse("=(1+2)*3").map(|e| matches!(e, Expr::Binary(BinOp::Mul, ..))),
918            Ok(true)
919        );
920    }
921
922    #[test]
923    fn calls_take_their_arguments_empty_ones_included() {
924        assert_eq!(
925            parse("=IF(B2>3,\"big\",\"small\")"),
926            Ok(Expr::Call(
927                "IF".to_string(),
928                vec![
929                    Expr::Binary(
930                        BinOp::Gt,
931                        Box::new(cell("B2", 2, 2)),
932                        Box::new(Expr::Number(3.0))
933                    ),
934                    Expr::Text("big".to_string()),
935                    Expr::Text("small".to_string()),
936                ]
937            ))
938        );
939        assert_eq!(
940            parse("=sum(A1,,2)"),
941            Ok(Expr::Call(
942                "SUM".to_string(),
943                vec![cell("A1", 1, 1), Expr::Empty, Expr::Number(2.0)]
944            ))
945        );
946        assert_eq!(parse("=PI()"), Ok(Expr::Call("PI".to_string(), vec![])));
947        assert_eq!(parse("=row()"), Ok(Expr::Call("ROW".to_string(), vec![])));
948        assert_eq!(
949            calls("=IF(SUM(A1:A3)>0,INDIRECT(\"B1\"),offset(A1,1,0))"),
950            vec!["IF", "SUM", "INDIRECT", "OFFSET"]
951        );
952        assert_eq!(calls("=\"SUM(\"&A1"), Vec::<String>::new());
953    }
954
955    #[test]
956    fn a_construct_this_slice_does_not_read_is_named_as_written() {
957        assert_eq!(parse("={1,2;3,4}"), Err("{".to_string()));
958        assert_eq!(parse("=@A1:A3"), Err("@".to_string()));
959        assert_eq!(parse("=A1 B1"), Err("intersection".to_string()));
960        assert_eq!(parse("=SUM((A1,B2))"), Err("union".to_string()));
961        assert_eq!(parse("=A1#"), Err("A1#".to_string()));
962        assert_eq!(
963            parse("=SUM(Sales[Amount])"),
964            Err("Sales[Amount]".to_string())
965        );
966        assert_eq!(
967            parse("=[Rates.xlsx]Sheet1!A1"),
968            Err("[Rates.xlsx]Sheet1!A1".to_string())
969        );
970        assert_eq!(parse("=#SPILL!"), Err("#SPILL!".to_string()));
971        assert_eq!(parse("="), Err("end of formula".to_string()));
972        assert_eq!(parse("=1+"), Err("end of formula".to_string()));
973        assert_eq!(parse("=)"), Err(")".to_string()));
974    }
975
976    #[test]
977    fn excel_s_two_limits_are_labels_of_their_own() {
978        // VLA.1: 64 levels of nesting parse and a 65th is TOO_DEEP, whether
979        // the levels are parentheses, a call's arguments, signs or percents.
980        let wrap =
981            |n: usize, open: &str, close: &str| format!("={}1{}", open.repeat(n), close.repeat(n));
982        for (open, close) in [("(", ")"), ("ABS(", ")"), ("-", ""), ("+", ""), ("", "%")] {
983            assert!(
984                parse(&wrap(64, open, close)).is_ok(),
985                "{open}...{close} at 64"
986            );
987            assert_eq!(
988                parse(&wrap(65, open, close)),
989                Err(TOO_DEEP.to_string()),
990                "{open}...{close} at 65"
991            );
992        }
993        // 8,192 characters after the `=` parse, a chain of 4,095 links; one
994        // more character is TOO_LONG.
995        let chain = format!("={}11", "1+".repeat(4095));
996        assert_eq!(chain.len() - 1, LENGTH_LIMIT);
997        assert!(parse(&chain).is_ok());
998        assert_eq!(parse(&format!("{chain}1")), Err(TOO_LONG.to_string()));
999    }
1000}