Skip to main content

ravenrs_sheet/calc/
eval.rs

1//! The formula evaluator.
2//!
3//! Expressions evaluate to [`Ev`]: a value, a reference (one or more areas, kept unresolved so
4//! `ROW`, `OFFSET`, `INDEX`, `:` and friends can work on references) or a lambda. Cell reads go
5//! through [`Host`], which lets the recalculation engine evaluate dirty precedents on demand.
6
7use std::sync::Arc;
8
9use crate::base::{Array, CellError, CellRef, MAX_COLS, MAX_ROWS, RangeRef, Value, compare};
10use crate::formula::{BinOp, Expr, Reference, SheetSel, StructItem, StructRef, UnOp};
11use crate::functions::{Arg, Ctx};
12use crate::model::Workbook;
13
14mod array;
15mod call;
16mod format;
17mod lambda;
18mod reference_fns;
19mod special;
20
21use array::map_unary;
22pub use array::{binary_values, zip2};
23pub use format::r1c1_to_a1;
24pub use special::{SPECIAL_FUNCTIONS, is_known_function, precedents, resized_sum_range};
25
26/// Largest array a reference may expand to.
27pub const MAX_CELLS: u64 = 16_000_000;
28/// References up to this many cells (one whole row) are always read at their full size.
29const FULL_SIZE_CELLS: u64 = 16_384;
30/// Bounded references up to this many cells (one column's worth) are read at their full size too, as
31/// Excel does, so their shape lines up with the arrays next to them (`D3:AZ400*B3:B400` is 398 by
32/// 49 even when the data ends at column F) and their blanks count. References that run to the edge
33/// of the sheet (`A:A`, `A2:A1048576`, several whole rows) and larger blocks stop at the sheet's used
34/// range, so that formulas over them stay fast.
35const FULL_SIZE_BOUNDED_CELLS: u64 = 1_048_576;
36const MAX_DEPTH: usize = 400;
37/// Lambda invocations one cell may make. A semantically bounded formula in Excel can invoke
38/// its lambda at most once per cell of an array (capped here by `MAX_CELLS`), so this covers
39/// every legitimate call; hostile work that grows past one dimension (a LAMBDA that recurses
40/// or nest `MAP` into `MAP`) then stops at the same bound instead of squaring.
41const LAMBDA_FUEL: u64 = MAX_CELLS;
42
43#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
44pub struct Area {
45    pub sheet: usize,
46    pub range: RangeRef,
47}
48
49#[derive(Clone, Debug)]
50pub struct Lambda {
51    pub params: Vec<String>,
52    pub body: Expr,
53    pub env: Vec<(String, Ev)>,
54}
55
56#[derive(Clone, Debug)]
57pub enum Ev {
58    V(Value),
59    R(Vec<Area>),
60    L(Arc<Lambda>),
61}
62
63impl From<Value> for Ev {
64    fn from(v: Value) -> Self {
65        Ev::V(v)
66    }
67}
68
69fn err(e: CellError) -> Ev {
70    Ev::V(Value::Error(e))
71}
72
73/// LAMBDA calls that one formula may have in progress at once, counted the same way on every build.
74/// The next call answers `#NUM!`. A countdown from 131, the deepest that `MAX_DEPTH` allows, has 132
75/// calls in progress, so it still runs.
76const MAX_LAMBDA_CALLS: usize = 140;
77
78/// Native stack, in bytes, that one formula's evaluation may use below its first `eval`. Past it,
79/// `eval` answers `#NUM!`. It guards the stack and leaves 128 KiB of a 1 MiB thread for the frames
80/// above the formula. A cell that another cell's formula computes on demand shares the budget of the
81/// outermost formula ([`Host::stack_top`]).
82const STACK_BUDGET: usize = 896 * 1024;
83
84/// The address of a local variable, which shows how far down the native stack this call is.
85#[inline(always)]
86pub(crate) fn stack_position() -> usize {
87    let marker = 0u8;
88    std::hint::black_box(&marker) as *const u8 as usize
89}
90
91/// What the evaluator needs from its surroundings.
92pub trait Host {
93    fn workbook(&self) -> &Workbook;
94    /// The current value of a cell (evaluating it first if it is dirty).
95    fn cell_value(&mut self, sheet: usize, c: CellRef) -> Value;
96    fn now_serial(&self) -> f64;
97    fn random(&mut self) -> f64;
98    /// Called when a volatile or dynamic-reference function is used.
99    fn note_volatile(&mut self) {}
100    /// The spill range of the dynamic array anchored at `anchor` (`None`: it doesn't spill).
101    fn spill_range(&mut self, sheet: usize, anchor: CellRef) -> Option<RangeRef> {
102        self.workbook().sheet(sheet)?.spill_ranges.get(&anchor).copied()
103    }
104    /// Values of a block, row-major. Hosts can override with a sparse fast path.
105    fn range_values(&mut self, sheet: usize, range: RangeRef) -> Vec<Value> {
106        range.iter().map(|c| self.cell_value(sheet, c)).collect()
107    }
108    /// The sheet's used range (`Sheet::used_range`, which walks every stored row). Hosts that
109    /// keep the workbook unchanged while evaluating can remember it.
110    fn used_range(&mut self, sheet: usize) -> Option<RangeRef> {
111        self.workbook().sheet(sheet).and_then(|s| s.used_range())
112    }
113    /// Calls `f` on each stored value of a block, row-major (formula cells evaluated first),
114    /// until `f` returns false. `false` when this host can't (callers use `range_values`).
115    fn visit_range(&mut self, _sheet: usize, _range: RangeRef, _f: &mut dyn FnMut(&Value) -> bool) -> bool {
116        false
117    }
118    /// While a formula is calculated on demand inside another one: `stack_position()` where the
119    /// outermost one started, which the stack budget is counted from.
120    fn stack_top(&self) -> Option<usize> {
121        None
122    }
123    /// Called when a formula's evaluation runs past the stack budget.
124    fn stack_exhausted(&mut self) {}
125}
126
127pub struct Evaluator<'h> {
128    pub host: &'h mut dyn Host,
129    pub sheet: usize,
130    pub at: CellRef,
131    env: Vec<(String, Ev)>,
132    depth: usize,
133    /// LAMBDA calls in progress.
134    calls: usize,
135    /// `stack_position()` at the outermost `eval`.
136    stack_top: usize,
137    /// Lambda invocations this cell still has left. Every call spends one charge, so a
138    /// compact hostile formula (a LAMBDA that calls itself, nested `MAP`s over big arrays)
139    /// runs out of work after the same number of calls that a legitimately wide one could
140    /// make, instead of an amount of work that squares with array nesting (#2382).
141    fuel: u64,
142}
143
144struct FnCtx<'a, 'h> {
145    ev: &'a mut Evaluator<'h>,
146    /// The criteria argument of a database function, when it is a reference.
147    criteria: Option<Area>,
148    /// The LAMBDA given as the function argument of GROUPBY/PIVOTBY.
149    lambda: Option<Arc<Lambda>>,
150}
151
152impl Ctx for FnCtx<'_, '_> {
153    fn date_system(&self) -> crate::base::DateSystem {
154        self.ev.host.workbook().date_system
155    }
156    fn now_serial(&self) -> f64 {
157        self.ev.host.now_serial()
158    }
159    fn random(&mut self) -> f64 {
160        self.ev.host.random()
161    }
162    /// Excel's "formula as criteria": the formula is written for the first record, so its relative
163    /// references move down to `record`'s row, and the database's labels name its fields.
164    /// Known gap: the shifted references are not dependencies of the calling cell. One that lands
165    /// outside both the database and the criteria cell's own precedents (say `=G2<H2` with column H
166    /// outside the database) is read here but not tracked, so editing that cell doesn't recalculate
167    /// the D-function until something else does.
168    fn computed_criterion(&mut self, row: usize, col: usize, db: &Array, record: usize) -> Option<bool> {
169        let area = self.criteria?;
170        let at = area.range.start.offset(row as i64, col as i64).filter(|c| area.range.contains(*c))?;
171        let expr = self.ev.host.workbook().sheet(area.sheet)?.cell(at)?.formula.as_ref()?.expr_arc()?;
172        let shifted = crate::formula::adjust::shift_relative((*expr).clone(), record as i64 - 1, 0);
173        let env = (0..db.cols)
174            .filter_map(|c| match db.get(0, c) {
175                Some(Value::Text(t)) if !t.trim().is_empty() => {
176                    Some((t.trim().to_string(), Ev::V(db.get(record, c).cloned().unwrap_or(Value::Empty))))
177                }
178                _ => None,
179            })
180            .collect();
181        let mut ev = Evaluator {
182            host: &mut *self.ev.host,
183            sheet: area.sheet,
184            at,
185            env,
186            depth: self.ev.depth,
187            calls: self.ev.calls,
188            stack_top: self.ev.stack_top,
189            fuel: self.ev.fuel,
190        };
191        let v = matches!(ev.value(&shifted).scalar().to_bool(), Ok(true));
192        // The child's spend counts against this cell's budget.
193        self.ev.fuel = ev.fuel;
194        Some(v)
195    }
196    fn has_lambda(&self) -> bool {
197        self.lambda.is_some()
198    }
199    /// A LAMBDA must give one value per group; an array result is #CALC!.
200    fn call_lambda(&mut self, arg: Value) -> Option<Value> {
201        let l = self.lambda.clone()?;
202        let [p] = l.params.as_slice() else { return Some(Value::Error(CellError::Value)) };
203        let r = self.ev.call_lambda_values(&l, vec![(p.clone(), Ev::V(arg))]);
204        Some(match self.ev.deref(r) {
205            Value::Array(a) if a.data.len() != 1 => Value::Error(CellError::Calc),
206            v => v.scalar(),
207        })
208    }
209}
210
211impl<'h> Evaluator<'h> {
212    pub fn new(host: &'h mut dyn Host, sheet: usize, at: CellRef) -> Self {
213        Evaluator { host, sheet, at, env: Vec::new(), depth: 0, calls: 0, stack_top: 0, fuel: LAMBDA_FUEL }
214    }
215
216    /// Evaluates to a value (references are dereferenced; multi-cell → array).
217    pub fn value(&mut self, e: &Expr) -> Value {
218        let ev = self.eval(e);
219        self.deref(ev)
220    }
221
222    fn resolve_sheet(&self, s: &SheetSel) -> Result<Vec<usize>, CellError> {
223        let wb = self.host.workbook();
224        match s {
225            SheetSel::Current => Ok(vec![self.sheet]),
226            SheetSel::Named(n) => wb.sheet_index(n).map(|i| vec![i]).ok_or(CellError::Ref),
227            SheetSel::Span(a, b) => {
228                let (i, j) = (wb.sheet_index(a).ok_or(CellError::Ref)?, wb.sheet_index(b).ok_or(CellError::Ref)?);
229                Ok((i.min(j)..=i.max(j)).collect())
230            }
231        }
232    }
233
234    pub fn reference_areas(&self, r: &Reference) -> Result<Vec<Area>, CellError> {
235        let range = r.range();
236        Ok(self.resolve_sheet(&r.sheet)?.into_iter().map(|sheet| Area { sheet, range }).collect())
237    }
238
239    /// Dereferences: one cell → its value; a block → an array; several areas → `#VALUE!`.
240    ///
241    /// A block keeps its full shape (blanks included) when it holds at most `FULL_SIZE_CELLS`
242    /// cells, or when it holds at most `FULL_SIZE_BOUNDED_CELLS` cells (one full column's worth)
243    /// and does not run to the sheet's last row or column. Every other block (larger, or a
244    /// bigger one that runs to the sheet's edge, such as `A:A` or `1:1`) is trimmed to the
245    /// sheet's used range before its values are read. An array over `MAX_CELLS` is `#NUM!`.
246    pub fn deref(&mut self, ev: Ev) -> Value {
247        match ev {
248            Ev::V(v) => v,
249            Ev::L(_) => Value::Error(CellError::Calc),
250            Ev::R(areas) => match areas.as_slice() {
251                [a] => self.area_value(a),
252                _ => Value::Error(CellError::Value),
253            },
254        }
255    }
256
257    pub fn area_value(&mut self, a: &Area) -> Value {
258        if a.range.is_single() {
259            return self.host.cell_value(a.sheet, a.range.start);
260        }
261        // A reference keeps its size, so the array has its shape and its blanks count
262        // (`=A1:A10`, `SUMPRODUCT(--(A1:A10=""))`).
263        let cells = u64::from(a.range.height()) * u64::from(a.range.width());
264        // `A:A`, `1:1` and `A2:A1048576` run to the edge of the sheet: they stand for "the rest of
265        // the column", not for a block of a chosen size.
266        let to_edge = a.range.end.row == MAX_ROWS - 1 || a.range.end.col == MAX_COLS - 1;
267        let full_size = cells <= FULL_SIZE_CELLS || (!to_edge && cells <= FULL_SIZE_BOUNDED_CELLS);
268        let range = if full_size { a.range } else { self.trim(a) };
269        let (h, w) = (range.height() as usize, range.width() as usize);
270        if (h as u64) * (w as u64) > MAX_CELLS {
271            return Value::Error(CellError::Num);
272        }
273        let data = self.host.range_values(a.sheet, range);
274        Array::new(h, w, data).map(Value::from).unwrap_or(Value::Error(CellError::Value))
275    }
276
277    /// Trims the bottom/right of an area to the sheet's used range (keeps the top-left so
278    /// positions stay right).
279    pub fn trim(&mut self, a: &Area) -> RangeRef {
280        if self.host.workbook().sheet(a.sheet).is_none() {
281            return a.range;
282        }
283        let used = self.host.used_range(a.sheet);
284        let (mut r1, mut c1) = (a.range.end.row, a.range.end.col);
285        match used {
286            Some(u) => {
287                r1 = r1.min(u.end.row.max(a.range.start.row));
288                c1 = c1.min(u.end.col.max(a.range.start.col));
289            }
290            None => {
291                r1 = a.range.start.row;
292                c1 = a.range.start.col;
293            }
294        }
295        RangeRef::new(a.range.start, CellRef::new(r1, c1))
296    }
297
298    pub fn eval(&mut self, e: &Expr) -> Ev {
299        let here = stack_position();
300        if self.depth == 0 {
301            self.stack_top = self.host.stack_top().unwrap_or(here);
302        }
303        if self.stack_top.abs_diff(here) > STACK_BUDGET {
304            self.host.stack_exhausted();
305            return err(CellError::Num);
306        }
307        self.depth += 1;
308        if self.depth > MAX_DEPTH {
309            self.depth -= 1;
310            return err(CellError::Num);
311        }
312        let r = self.eval_inner(e);
313        self.depth -= 1;
314        r
315    }
316
317    fn eval_inner(&mut self, e: &Expr) -> Ev {
318        match e {
319            Expr::Number(n) => Ev::V(Value::number(*n)),
320            Expr::Text(t) => Ev::V(Value::Text(t.clone())),
321            Expr::Bool(b) => Ev::V(Value::Bool(*b)),
322            Expr::Error(x) => err(*x),
323            Expr::Missing => Ev::V(Value::Empty),
324            Expr::Paren(x) => self.eval(x),
325            Expr::Array(rows) => {
326                let h = rows.len();
327                let w = rows.first().map_or(0, Vec::len);
328                // A ragged literal is an error either way; one wide row and many empty ones must
329                // not reserve their rows-times-width product first.
330                if rows.iter().any(|row| row.len() != w) {
331                    return err(CellError::Value);
332                }
333                let mut data = Vec::with_capacity(h * w);
334                for row in rows {
335                    for el in row {
336                        let v = self.value(el);
337                        data.push(v);
338                    }
339                }
340                Ev::V(Array::new(h, w, data).map(Value::from).unwrap_or(Value::Error(CellError::Value)))
341            }
342            Expr::Ref(r) => match self.reference_areas(r) {
343                Ok(a) => Ev::R(a),
344                Err(x) => err(x),
345            },
346            Expr::Name(n) => self.name(n),
347            Expr::Struct(s) => self.structured(s),
348            Expr::Unary(op, x) => self.unary(*op, x),
349            Expr::Binary(op, a, b) => self.binary(*op, a, b),
350            Expr::Call(name, args) => self.call(name, args),
351            Expr::Invoke(callee, args) => {
352                let f = self.eval(callee);
353                match f {
354                    Ev::L(l) => self.apply_lambda(&l, args),
355                    Ev::V(Value::Error(x)) => err(x),
356                    _ => err(CellError::Value),
357                }
358            }
359        }
360    }
361
362    fn name(&mut self, n: &str) -> Ev {
363        if let Some((_, v)) = self.env.iter().rev().find(|(k, _)| crate::formula::same_name(k, n)) {
364            return v.clone();
365        }
366        let wb = self.host.workbook();
367        // Sheet-qualified `Sheet1!Name`. The parser stores the sheet unquoted, so it is used as
368        // is (`' Sales '` keeps its spaces) and split at the last `!` (`'a!b'!Name`).
369        let (scope_sheet, bare) = match n.rsplit_once('!') {
370            Some((s, b)) => match wb.sheet_index(s) {
371                Some(i) => (i, b),
372                None => return err(CellError::Name),
373            },
374            None => (self.sheet, n),
375        };
376        if let Some(def) = wb.name(bare, scope_sheet) {
377            let text = def.formula.clone();
378            let scope = def.scope.unwrap_or(self.sheet);
379            return match crate::formula::parse_name(&text) {
380                Ok(expr) => {
381                    // Relative references in a name are relative to A1: used here, they move by
382                    // this cell's offset from it.
383                    let expr = if crate::formula::adjust::has_relative_refs(&expr) {
384                        crate::formula::adjust::shift_relative_wrapping(expr, self.at.row as i64, self.at.col as i64)
385                    } else {
386                        expr
387                    };
388                    let saved = self.sheet;
389                    // Unqualified references in a name refer to the scope sheet.
390                    self.sheet = scope;
391                    let r = self.eval(&expr);
392                    self.sheet = saved;
393                    r
394                }
395                Err(_) => err(CellError::Name),
396            };
397        }
398        // A table name alone means its data body.
399        if let Some((si, ti)) = wb.table(bare)
400            && let Some(t) = wb.sheet(si).and_then(|s| s.tables.get(ti))
401            && let Some(d) = t.data_range()
402        {
403            return Ev::R(vec![Area { sheet: si, range: d }]);
404        }
405        err(CellError::Name)
406    }
407
408    fn structured(&mut self, s: &StructRef) -> Ev {
409        let wb = self.host.workbook();
410        let found = if s.table.is_empty() {
411            wb.sheet(self.sheet).and_then(|sh| sh.tables.iter().position(|t| t.range.contains(self.at)).map(|ti| (self.sheet, ti)))
412        } else {
413            wb.table(&s.table)
414        };
415        let Some((si, ti)) = found else { return err(CellError::Ref) };
416        let Some(t) = wb.sheet(si).and_then(|sh| sh.tables.get(ti)) else { return err(CellError::Ref) };
417        let mut r0 = t.range.start.row;
418        let mut r1 = t.range.end.row;
419        let data = t.data_range();
420        let items = &s.specifiers;
421        if items.is_empty() || items == &[StructItem::Data] {
422            match data {
423                Some(d) => {
424                    r0 = d.start.row;
425                    r1 = d.end.row;
426                }
427                None => return err(CellError::Ref),
428            }
429        } else if items.contains(&StructItem::ThisRow) {
430            if self.at.row < t.range.start.row || self.at.row > t.range.end.row {
431                return err(CellError::Value);
432            }
433            r0 = self.at.row;
434            r1 = self.at.row;
435        } else if !items.contains(&StructItem::All) {
436            let has = |i: StructItem| items.contains(&i);
437            let hdr = t.header_row.then_some(t.range.start.row);
438            let tot = t.totals_row.then_some(t.range.end.row);
439            let mut lo = u32::MAX;
440            let mut hi = 0;
441            let mut add = |a: u32, b: u32| {
442                lo = lo.min(a);
443                hi = hi.max(b);
444            };
445            if has(StructItem::Headers) {
446                match hdr {
447                    Some(h) => add(h, h),
448                    None => return err(CellError::Ref),
449                }
450            }
451            if has(StructItem::Data)
452                && let Some(d) = data
453            {
454                add(d.start.row, d.end.row);
455            }
456            if has(StructItem::Totals) {
457                match tot {
458                    Some(h) => add(h, h),
459                    None => return err(CellError::Ref),
460                }
461            }
462            if lo > hi {
463                return err(CellError::Ref);
464            }
465            r0 = lo;
466            r1 = hi;
467        }
468        let (mut c0, mut c1) = (t.range.start.col, t.range.end.col);
469        if let Some(a) = &s.col_start {
470            let Some(i) = t.column_index(a) else { return err(CellError::Ref) };
471            c0 = t.range.start.col + i as u32;
472            c1 = c0;
473            if let Some(b) = &s.col_end {
474                let Some(j) = t.column_index(b) else { return err(CellError::Ref) };
475                let cj = t.range.start.col + j as u32;
476                c0 = c0.min(cj);
477                c1 = c1.max(cj);
478            }
479        }
480        Ev::R(vec![Area { sheet: si, range: RangeRef::new(CellRef::new(r0, c0), CellRef::new(r1, c1)) }])
481    }
482
483    fn unary(&mut self, op: UnOp, x: &Expr) -> Ev {
484        if let UnOp::Trim(mode) = op {
485            return self.trim_ref(mode, x);
486        }
487        if op == UnOp::Spill {
488            return match self.eval(x) {
489                Ev::R(areas) => match areas.as_slice() {
490                    [a] if a.range.is_single() => match self.host.spill_range(a.sheet, a.range.start) {
491                        Some(range) => Ev::R(vec![Area { sheet: a.sheet, range }]),
492                        None => err(CellError::Ref),
493                    },
494                    _ => err(CellError::Ref),
495                },
496                Ev::V(Value::Error(e)) => err(e),
497                _ => err(CellError::Ref),
498            };
499        }
500        if op == UnOp::At {
501            let ev = self.eval(x);
502            return match ev {
503                Ev::R(areas) => match areas.as_slice() {
504                    [a] => match self.implicit_intersect(a) {
505                        Some(c) => Ev::V(self.host.cell_value(a.sheet, c)),
506                        None => err(CellError::Value),
507                    },
508                    _ => err(CellError::Value),
509                },
510                Ev::V(v) => Ev::V(v.scalar()),
511                l => l,
512            };
513        }
514        let v = self.value(x);
515        let sys = self.host.workbook().date_system;
516        Ev::V(map_unary(
517            &v,
518            &|n| match op {
519                UnOp::Neg => Value::number(-n),
520                UnOp::Plus => Value::number(n),
521                UnOp::Percent => Value::number(n / 100.0),
522                UnOp::At | UnOp::Spill | UnOp::Trim(_) => Value::number(n),
523            },
524            op == UnOp::Plus,
525            sys,
526        ))
527    }
528
529    fn implicit_intersect(&self, a: &Area) -> Option<CellRef> {
530        let r = a.range;
531        if r.is_single() {
532            return Some(r.start);
533        }
534        if r.width() == 1 && r.start.row <= self.at.row && self.at.row <= r.end.row {
535            return Some(CellRef::new(self.at.row, r.start.col));
536        }
537        if r.height() == 1 && r.start.col <= self.at.col && self.at.col <= r.end.col {
538            return Some(CellRef::new(r.start.row, self.at.col));
539        }
540        None
541    }
542
543    fn binary(&mut self, op: BinOp, a: &Expr, b: &Expr) -> Ev {
544        match op {
545            BinOp::Range => {
546                let (x, y) = (self.eval(a), self.eval(b));
547                let (Ev::R(xa), Ev::R(ya)) = (&x, &y) else {
548                    if let Ev::V(Value::Error(e)) = x {
549                        return err(e);
550                    }
551                    if let Ev::V(Value::Error(e)) = y {
552                        return err(e);
553                    }
554                    return err(CellError::Value);
555                };
556                let all: Vec<&Area> = xa.iter().chain(ya.iter()).collect();
557                let Some(first) = all.first() else { return err(CellError::Ref) };
558                if all.iter().any(|ar| ar.sheet != first.sheet) {
559                    return err(CellError::Value);
560                }
561                let mut r = first.range;
562                for ar in &all {
563                    r = r.union(&ar.range);
564                }
565                Ev::R(vec![Area { sheet: first.sheet, range: r }])
566            }
567            BinOp::Union => {
568                let (x, y) = (self.eval(a), self.eval(b));
569                match (x, y) {
570                    (Ev::R(mut xa), Ev::R(ya)) => {
571                        xa.extend(ya);
572                        Ev::R(xa)
573                    }
574                    (Ev::V(Value::Error(e)), _) | (_, Ev::V(Value::Error(e))) => err(e),
575                    _ => err(CellError::Value),
576                }
577            }
578            BinOp::Intersect => {
579                let (x, y) = (self.eval(a), self.eval(b));
580                match (x, y) {
581                    (Ev::R(xa), Ev::R(ya)) => {
582                        let mut out = Vec::new();
583                        for p in &xa {
584                            for q in &ya {
585                                if p.sheet == q.sheet
586                                    && let Some(i) = p.range.intersection(&q.range)
587                                {
588                                    out.push(Area { sheet: p.sheet, range: i });
589                                }
590                            }
591                        }
592                        if out.is_empty() { err(CellError::Null) } else { Ev::R(out) }
593                    }
594                    (Ev::V(Value::Error(e)), _) | (_, Ev::V(Value::Error(e))) => err(e),
595                    _ => err(CellError::Value),
596                }
597            }
598            _ => {
599                // A long chain such as A1+A2+…+A500 nests to the left: fold it in a loop instead
600                // of one level of recursion per operator (past the depth limit it was an error).
601                let mut rest = vec![(op, b)];
602                let mut left = a;
603                while let Expr::Binary(o, l, r) = left
604                    && !matches!(o, BinOp::Range | BinOp::Union | BinOp::Intersect)
605                {
606                    rest.push((*o, r.as_ref()));
607                    left = l.as_ref();
608                }
609                let mut acc = self.value(left);
610                while let Some((o, r)) = rest.pop() {
611                    let y = self.value(r);
612                    acc = binary_values(o, &acc, &y, self.host.workbook().date_system);
613                }
614                Ev::V(acc)
615            }
616        }
617    }
618
619    // ------------------------------------------------------------------ calls
620
621    fn arg_ev(&mut self, args: &[Expr], i: usize) -> Option<Ev> {
622        args.get(i).map(|e| self.eval(e))
623    }
624    fn arg_val(&mut self, args: &[Expr], i: usize) -> Option<Value> {
625        args.get(i).map(|e| self.value(e))
626    }
627    fn arg_num(&mut self, args: &[Expr], i: usize, default: f64) -> Result<f64, CellError> {
628        match args.get(i) {
629            None | Some(Expr::Missing) => Ok(default),
630            Some(e) => {
631                let v = self.value(e);
632                v.scalar().to_number()
633            }
634        }
635    }
636}
637
638trait Pipe: Sized {
639    fn pipe<T>(self, f: impl FnOnce(Self) -> T) -> T {
640        f(self)
641    }
642}
643impl<T> Pipe for T {}
644
645#[cfg(test)]
646mod fuel_tests {
647    use super::*;
648    use crate::model::Workbook;
649
650    struct Empty {
651        wb: Workbook,
652    }
653    impl Host for Empty {
654        fn workbook(&self) -> &Workbook {
655            &self.wb
656        }
657        fn cell_value(&mut self, _sheet: usize, _c: CellRef) -> Value {
658            Value::Empty
659        }
660        fn now_serial(&self) -> f64 {
661            0.0
662        }
663        fn random(&mut self) -> f64 {
664            0.0
665        }
666    }
667
668    fn eval_with(fuel: u64, src: &str) -> Value {
669        let mut host = Empty { wb: Workbook::default() };
670        let mut ev = Evaluator::new(&mut host, 0, CellRef::new(0, 0));
671        ev.fuel = fuel;
672        ev.value(&crate::formula::parser::parse(src).unwrap_or_else(|e| panic!("{e}")))
673    }
674
675    #[test]
676    fn lambda_calls_spend_fuel_and_run_out_quietly() {
677        // Three applications, one charge each: 3 inside the budget. (A nested MAP wouldn't do
678        // here: an inner MAP result is an array, and MAP of array elements is #CALC! on shape,
679        // which happens well before the budget does.)
680        let r = eval_with(3, "=LAMBDA(x,LAMBDA(y,LAMBDA(z,z+1)(3))(2))(1)");
681        assert_eq!(r, Value::number(4.0), "{r:?}");
682        // One charge short: the nest bottoms out in #CALC! instead of running unbounded.
683        let r = eval_with(2, "=LAMBDA(x,LAMBDA(y,LAMBDA(z,z+1)(3))(2))(1)");
684        assert!(matches!(r, Value::Error(CellError::Calc)), "{r:?}");
685        // One charge short of a single application: #CALC! with a zero budget.
686        let r = eval_with(0, "=LAMBDA(x,x+1)(2)");
687        assert!(matches!(r, Value::Error(CellError::Calc)), "{r:?}");
688        let r = eval_with(1, "=LAMBDA(x,x+1)(2)");
689        assert_eq!(r, Value::number(3.0));
690        // A flat helper map keeps working (3 of 3 invokes inside the budget).
691        let r = eval_with(3, "=MAP({1,2,3},LAMBDA(x,x*2))");
692        assert!(matches!(r, Value::Array(_)) && !matches!(&r, Value::Array(a) if a.data.iter().any(|v| matches!(v, Value::Error(_)))), "{r:?}");
693    }
694}