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