Skip to main content

vla_lang/calc/
shape.rs

1//! The plan of shapes (KERNEL.22, 2026-10-09): every formula of a grid
2//! parsed once per shape, each formula cell evaluated at its offset from
3//! the shape's first cell, and the order of evaluation read off the parse.
4//!
5//! An engine evaluates every formula every frame, so a parse at each
6//! evaluation, as [`super::Calc`] makes, costs most of a frame (Life's
7//! 62,964 formulas: about two seconds of a frame's 2.5, measured 2026-10-09),
8//! and a parse of every cell held once costs memory no browser holds (8.9 KB
9//! for one of Life's formulas, 562 MB for all of them). A shape is the
10//! answer: a shared formula's text is read once at its master and its other
11//! cells are the same formula moved, and two plain formula cells whose R1C1
12//! text is the same (`refers::r1c1`, the reference's own normal form) are
13//! the same formula moved too, so a save of Life, 62,964 plain formula rows,
14//! loads back to one shape. A cell at an offset from its shape's first cell
15//! is evaluated with every reference moved by that offset, which is what the
16//! text `refers::shift_a1_references` moves would parse to
17//! ([`super::formula::RefExpr::moved`]); the references inside a defined
18//! name's text never move.
19//!
20//! [`super::Calc`] keeps its plan of texts: it evaluates each formula once,
21//! so a parse held would buy it nothing. The plan here is an engine's, kept
22//! for the life of a grid and read again when a write changes a formula; a
23//! shape's references find the sheets they name once, as it is placed,
24//! among the grid's and its twins, which are fixed for the grid's life
25//! (KERNEL.20), where a reference found its sheet by name at every
26//! evaluation.
27//!
28//! The order is by level, a cell's being one past the deepest it reads, then
29//! by shape and cell, which computes the frame any order after what each cell
30//! reads computes; its runs are the cells of a shape that stand together in
31//! it, on one sheet, reading none of one another, and an engine evaluates
32//! each run at once, node by node (`super::vector`, KERNEL.20), and a cell
33//! alone, or a run's few, by its shape's parse lowered once to a program
34//! (`super::program`, KERNEL.44). What each
35//! shape reads is found with the order, so that an engine follows a changed
36//! cell to the cells of the order that read it and computes those alone
37//! (`super::reach`, KERNEL.20); and so are the sheets a read takes as an
38//! array, a range of them or a run's moving cell, which an engine's machine
39//! keeps a frame's values of as arrays (KERNEL.31, [`Shapes::read_as_array`]).
40//!
41//! The plan is printed as rows on request ([`Shapes::census`], KERNEL.30,
42//! `super::census`): each shape's cells, the levels of the order it spans,
43//! its runs and the paths its reads take, so that a grid's cost is read
44//! before a frame runs. Of all that, the plan keeps for it only where each
45//! level of the order ends, the array its counting sort already makes.
46
47use std::collections::HashMap;
48
49use crate::fx::FxHashMap;
50use crate::messages::{raise, Refusal};
51use crate::refers::{self, quote_sheet};
52use crate::sheet::{cell_ref, shared_masters, strip_future_prefixes, Content, Workbook};
53
54use super::eval::{Computed, Ctx, Env, Library, Reason};
55use super::formula::{self, Expr, Read, RefExpr};
56use super::graph::{
57    self, areas_at, cells_in, dependencies, is_formula, Area, CellId, Extent, Names,
58};
59use super::program::{Program, Why};
60use super::reach::{Positions, Reach};
61use super::vector::{self, Lanes, Repeats};
62
63/// One formula, parsed once, and the cell it was read at.
64#[derive(Clone, Debug)]
65pub struct Shape {
66    /// The text at the shape's first cell, without its `=`, as the formula
67    /// bar shows it.
68    pub text: String,
69    /// The parse, or the construct it stopped at.
70    pub expr: Result<Expr, String>,
71    /// The cell the text was read at; every other cell of the shape is at
72    /// an offset from it.
73    pub anchor: CellId,
74    /// The functions the parse calls that the library does not hold,
75    /// sorted, each once: what a load refuses, read once a shape.
76    missing: Vec<String>,
77    /// Whether a cell of the shape may stand for a text the parse does not
78    /// read: the text holds a 3D span (`Jan:Dec!A1`) or a broken reference
79    /// with a cell after it (`#REF!A1`), each of which the mover writes as
80    /// a construct when it moves off the sheet (`Jan:Dec!#REF!`,
81    /// `#REF!#REF!`). Found 2026-10-09 by holding the offset to the moved
82    /// text's parse over the refers fixture.
83    pub(crate) risky: bool,
84    /// The most buffers an evaluation of the parse at many cells holds at
85    /// once, found once (KERNEL.20, `vector::holds`), a lane for each of its
86    /// repeated parts with them (KERNEL.27).
87    holds: (usize, usize),
88    /// The parts the parse writes more than once that a run computes once
89    /// (KERNEL.27, `vector::Repeats`).
90    repeats: Repeats,
91    /// The index of the shared formula the shape is, on its first cell's
92    /// sheet, whose master is its first cell; `None` for the shape of plain
93    /// formulas of one R1C1 text (KERNEL.34).
94    fill: Option<u32>,
95    /// The parse lowered once, for a cell evaluated alone and a run's few
96    /// cells (KERNEL.44, `super::program`), or why it is left to `Ctx`;
97    /// `None` for a text not read and a risky shape, whose cells `Ctx`
98    /// evaluates as it always has.
99    lowered: Option<Result<Program, Why>>,
100}
101
102impl Shape {
103    /// The most buffers of each kind a run of the shape holds at once.
104    #[cfg(test)]
105    pub(crate) fn holds(&self) -> (usize, usize) {
106        self.holds
107    }
108
109    /// The functions the parse calls that the library does not hold, sorted,
110    /// each once (KERNEL.30's census prints them).
111    pub(crate) fn missing(&self) -> &[String] {
112        &self.missing
113    }
114
115    /// Why the parse is left to `Ctx` (KERNEL.44's census prints it); `None`
116    /// for a parse lowered, a text not read and a risky shape.
117    pub(crate) fn unlowered(&self) -> Option<Why> {
118        match &self.lowered {
119            Some(Err(why)) => Some(*why),
120            _ => None,
121        }
122    }
123
124    /// The construct a cell at this offset stands for that the shape's
125    /// first cell does not, as the moved text's parse names it; `None` when
126    /// the moved text reads as the shape does, which for a shape that is not
127    /// risky it always does (the refers fixture moved to every edge of the
128    /// sheet holds that).
129    pub fn unread_at(&self, d_row: i64, d_col: i64) -> Option<String> {
130        if !self.risky || (d_row == 0 && d_col == 0) || self.expr.is_err() {
131            return None;
132        }
133        formula::parse(&refers::shift_a1_references(&self.text, d_row, d_col)).err()
134    }
135}
136
137/// Where a formula cell's formula comes from: its shape, and the cell's
138/// offset from the shape's first cell.
139#[derive(Clone, Copy, Debug, PartialEq, Eq)]
140pub struct Place {
141    pub shape: usize,
142    pub d_row: i64,
143    pub d_col: i64,
144}
145
146/// The plan: every formula cell placed on a shape, the order, the cycles.
147#[derive(Clone, Debug, Default)]
148pub struct Shapes {
149    shapes: Vec<Shape>,
150    /// A plain formula's shape, by its R1C1 text.
151    by_r1c1: HashMap<String, usize>,
152    /// A shared formula's shape, by its sheet and its index.
153    by_shared: HashMap<(usize, u32), usize>,
154    /// Every formula cell's place, looked up at each of its evaluations,
155    /// under the crate's own hash (KERNEL.25).
156    places: FxHashMap<CellId, Place>,
157    /// A cell of a risky shape whose own text the parse does not read: the
158    /// construct it names, found once when the cell is placed.
159    unread: FxHashMap<CellId, String>,
160    order: Vec<CellId>,
161    cycles: Vec<Vec<CellId>>,
162    /// The runs of the order an engine evaluates at once (KERNEL.20), by
163    /// their first position: each of at least [`RUN`] cells of one parsed
164    /// shape that is not risky, on one sheet, consecutive in the order, none
165    /// reading another.
166    runs: Vec<Run>,
167    /// What each shape reads, found with the order (KERNEL.20).
168    reach: Reach,
169    /// Where each level of the order ends, by level (KERNEL.30): the
170    /// position after its last cell, the order's own counting sort kept
171    /// rather than dropped, so that the census finds each cell's level
172    /// without planning again.
173    level_ends: Vec<u32>,
174    /// By sheet of the grid, whether a read takes it as an array (KERNEL.31,
175    /// [`Shapes::read_as_array`]), found with the order.
176    arrays: Vec<bool>,
177}
178
179/// A run of the order: its cells are `order[start..end]`, of one shape.
180#[derive(Clone, Copy, Debug, PartialEq, Eq)]
181struct Run {
182    start: usize,
183    end: usize,
184    shape: usize,
185}
186
187/// The fewest cells a run is evaluated at once for; fewer are evaluated one
188/// by one, as every cell was before (KERNEL.20).
189pub(crate) const RUN: usize = 4;
190
191/// The fewest cells of a run evaluated at once when its shape has a program
192/// (KERNEL.44): fewer are evaluated one by one by the program, which costs
193/// them less than a run's start costs them. Measured over 4 to 64: Kairos's
194/// runs of about 14 cells a frame selects cost 143 ns a cell against a
195/// program's 40, and Life's rule breaks even near 20 (its program 108 ns a
196/// cell, a run's 32 and a start of about 1.5 µs), 64 costing the gun.
197pub(crate) const SHORT: usize = 16;
198
199impl Shapes {
200    /// A plan with nothing in it.
201    pub fn new() -> Shapes {
202        Shapes::default()
203    }
204
205    /// Every formula cell of the sheets `0..sheets` placed, the shapes
206    /// parsed, and the order found. The plan is made in a box: every formula
207    /// is parsed beneath this frame, the deepest a load reaches, which
208    /// `VLA.1`'s limits pin on a stack of 1 MiB, and the plan held here by
209    /// value would stand on it, 480 bytes since it kept its level ends
210    /// (KERNEL.30).
211    pub fn of(
212        model: &Workbook,
213        sheets: usize,
214        library: &Library,
215        names: &Names,
216        extents: &[Extent],
217    ) -> Shapes {
218        let mut s: Box<Shapes> = Box::default();
219        for (si, sheet) in model.sheets.iter().enumerate().take(sheets) {
220            let masters = shared_masters(sheet);
221            for (&(row, col), cell) in &sheet.cells {
222                s.place_with(model, (si, row, col), &cell.content, &masters, library);
223            }
224        }
225        s.plan(model, names, extents);
226        *s
227    }
228
229    /// Place one cell after a write: its old place forgotten, and a new one
230    /// found when it holds a formula. The order waits for [`Shapes::plan`].
231    pub fn replace(&mut self, model: &Workbook, id: CellId, library: &Library) {
232        self.places.remove(&id);
233        self.unread.remove(&id);
234        let Some(sheet) = model.sheets.get(id.0) else {
235            return;
236        };
237        let Some(cell) = sheet.cells.get(&(id.1, id.2)) else {
238            return;
239        };
240        if !is_formula(&cell.content) {
241            return;
242        }
243        let masters = shared_masters(sheet);
244        self.place_with(model, id, &cell.content, &masters, library);
245    }
246
247    /// Many cells placed after a write, as [`Shapes::replace`] places one,
248    /// each sheet's shared formulas found once for all its cells, where
249    /// `replace` walks the sheet for each (KERNEL.34: Life's rule written
250    /// whole walked its sheet 62,964 times, 3.96 billion cells visited, in
251    /// 13.3 s); then the plan made the one a load of the same grid makes
252    /// ([`Shapes::as_loaded`]). The order waits for [`Shapes::plan`].
253    pub(crate) fn replace_many(&mut self, model: &Workbook, ids: &[CellId], library: &Library) {
254        for id in ids {
255            self.places.remove(id);
256            self.unread.remove(id);
257        }
258        let mut sheets: Vec<usize> = ids.iter().map(|id| id.0).collect();
259        sheets.sort_unstable();
260        sheets.dedup();
261        for s in sheets {
262            let Some(sheet) = model.sheets.get(s) else {
263                continue;
264            };
265            let masters = shared_masters(sheet);
266            for &id in ids.iter().filter(|id| id.0 == s) {
267                let Some(cell) = sheet.cells.get(&(id.1, id.2)) else {
268                    continue;
269                };
270                if is_formula(&cell.content) {
271                    self.place_with(model, id, &cell.content, &masters, library);
272                }
273            }
274        }
275        self.as_loaded(model, library);
276    }
277
278    /// The plan made the one [`Shapes::of`] makes over the same grid, once a
279    /// write has placed some of its cells again (KERNEL.34): the shapes in
280    /// the order of their first cells in the grid's order, sheet, row and
281    /// column; a fill's shape anchored at its master and a plain formula's
282    /// at its first cell, read again from that cell's text where the write
283    /// moved or respelled it; a shape no cell holds dropped. Every cell's
284    /// place and every construct a cell leaves unread are then a load's, and
285    /// so, once planned, are the order, its runs and the reach, and the
286    /// census prints a load's. A write left a fill's other cells on the
287    /// shape of its first cell even when that cell had been written over,
288    /// and kept a shape no cell held, so that the census after a write was
289    /// numbered and anchored otherwise than a load's.
290    fn as_loaded(&mut self, model: &Workbook, library: &Library) {
291        // Each shape's first cell, the least of its places, found in one pass:
292        // sorting the places to find them took a write into Sand 14 ms.
293        let mut first: Vec<Option<CellId>> = vec![None; self.shapes.len()];
294        for (&id, p) in &self.places {
295            match first[p.shape] {
296                Some(f) if f <= id => {}
297                _ => first[p.shape] = Some(id),
298            }
299        }
300        let mut order: Vec<usize> = (0..self.shapes.len())
301            .filter(|&s| first[s].is_some())
302            .collect();
303        order.sort_unstable_by_key(|&s| first[s]);
304        // Each shape's first cell as a load anchors it, with its text there.
305        let mut masters: HashMap<usize, HashMap<u32, (&str, u32, u32)>> = HashMap::new();
306        let mut again: Vec<(usize, CellId, String)> = Vec::new();
307        for &s in &order {
308            let f = first[s].expect("a shape in the order has a first cell");
309            let shape = &self.shapes[s];
310            let (anchor, raw) = match shape.fill {
311                Some(index) => {
312                    let sheet = shape.anchor.0;
313                    let found = masters
314                        .entry(sheet)
315                        .or_insert_with(|| shared_masters(&model.sheets[sheet]))
316                        .get(&index);
317                    match found {
318                        Some(&(text, row, col)) => ((sheet, row, col), text),
319                        None => continue,
320                    }
321                }
322                None => match model.sheets[f.0].cells.get(&(f.1, f.2)).map(|c| &c.content) {
323                    Some(Content::Formula(t) | Content::DynamicFormula(t)) => (f, t.as_str()),
324                    _ => continue,
325                },
326            };
327            if anchor != shape.anchor
328                || (raw != shape.text && strip_future_prefixes(raw) != shape.text)
329            {
330                again.push((s, anchor, strip_future_prefixes(raw)));
331            }
332        }
333        let numbered = order.iter().enumerate().all(|(k, &s)| k == s);
334        if again.is_empty() && numbered && order.len() == self.shapes.len() {
335            return;
336        }
337        // The shapes read again from their first cells, then every shape in
338        // the order of its first cell, each place renumbered and, for a shape
339        // read again, moved from its new first cell.
340        let mut renewed: HashMap<usize, Shape> = HashMap::new();
341        for (s, anchor, text) in again {
342            let fill = self.shapes[s].fill;
343            self.add(model, text, anchor, library);
344            let mut shape = self.shapes.pop().expect("the shape just made");
345            shape.fill = fill;
346            renewed.insert(s, shape);
347        }
348        let mut number: Vec<Option<usize>> = vec![None; self.shapes.len()];
349        let mut moved: Vec<bool> = Vec::with_capacity(order.len());
350        let mut old: Vec<Option<Shape>> = std::mem::take(&mut self.shapes)
351            .into_iter()
352            .map(Some)
353            .collect();
354        for (k, &s) in order.iter().enumerate() {
355            number[s] = Some(k);
356            moved.push(renewed.contains_key(&s));
357            let shape = renewed
358                .remove(&s)
359                .or_else(|| old[s].take())
360                .expect("each shape once");
361            self.shapes.push(shape);
362        }
363        let shapes = &self.shapes;
364        for (id, p) in self.places.iter_mut() {
365            p.shape = number[p.shape].expect("a placed cell's shape is kept");
366            if !moved[p.shape] {
367                continue;
368            }
369            let shape = &shapes[p.shape];
370            p.d_row = i64::from(id.1) - i64::from(shape.anchor.1);
371            p.d_col = i64::from(id.2) - i64::from(shape.anchor.2);
372            match shape.unread_at(p.d_row, p.d_col) {
373                Some(construct) => self.unread.insert(*id, construct),
374                None => self.unread.remove(id),
375            };
376        }
377        self.by_shared.retain(|_, s| number[*s].is_some());
378        self.by_r1c1.retain(|_, s| number[*s].is_some());
379        for s in self.by_shared.values_mut().chain(self.by_r1c1.values_mut()) {
380            *s = number[*s].expect("kept");
381        }
382    }
383
384    fn place_with(
385        &mut self,
386        model: &Workbook,
387        id: CellId,
388        content: &Content,
389        masters: &HashMap<u32, (&str, u32, u32)>,
390        library: &Library,
391    ) {
392        let (si, row, col) = id;
393        let (shape, anchor) = match content {
394            Content::SharedMaster { si: index, .. } | Content::SharedChild { si: index } => {
395                let Some(&(text, m_row, m_col)) = masters.get(index) else {
396                    // A child whose master is gone has no text; the machine
397                    // never leaves one (a write over a master makes the
398                    // fill's first cell left its master, or gives each of its
399                    // cells its own text, first).
400                    return;
401                };
402                let anchor = (si, m_row, m_col);
403                let key = (si, *index);
404                let shape = match self.by_shared.get(&key) {
405                    Some(&s) => s,
406                    None => {
407                        let s = self.add(model, strip_future_prefixes(text), anchor, library);
408                        self.shapes[s].fill = Some(*index);
409                        self.by_shared.insert(key, s);
410                        s
411                    }
412                };
413                (shape, anchor)
414            }
415            Content::Formula(t) | Content::DynamicFormula(t) => {
416                let text = strip_future_prefixes(t);
417                let key = refers::r1c1(&text, row, col);
418                match self.by_r1c1.get(&key) {
419                    Some(&s) => (s, self.shapes[s].anchor),
420                    None => {
421                        let s = self.add(model, text, id, library);
422                        self.by_r1c1.insert(key, s);
423                        (s, id)
424                    }
425                }
426            }
427            _ => return,
428        };
429        let (d_row, d_col) = (
430            i64::from(row) - i64::from(anchor.1),
431            i64::from(col) - i64::from(anchor.2),
432        );
433        self.places.insert(
434            id,
435            Place {
436                shape,
437                d_row,
438                d_col,
439            },
440        );
441        match self.shapes[shape].unread_at(d_row, d_col) {
442            Some(construct) => self.unread.insert(id, construct),
443            None => self.unread.remove(&id),
444        };
445    }
446
447    fn add(&mut self, model: &Workbook, text: String, anchor: CellId, library: &Library) -> usize {
448        let mut expr = formula::parse(&text);
449        if let Ok(e) = expr.as_mut() {
450            find_sheets(e, model);
451        }
452        // The calls as the text's tokens hold them, parsed or not, as
453        // `Calc::missing_functions` reads them.
454        let mut missing: Vec<String> = formula::calls(&text)
455            .into_iter()
456            .filter(|name| !library.has(name))
457            .collect();
458        let risky = refers::scan(&text).iter().any(|r| {
459            r.kind == refers::Kind::ThreeD
460                || (r.kind == refers::Kind::Broken && r.shape != refers::Shape::None)
461        });
462        missing.sort_unstable();
463        missing.dedup();
464        self.push(text, expr, anchor, missing, risky, library)
465    }
466
467    /// A shape kept, with what an evaluation of it at many cells holds found
468    /// once (KERNEL.20, `vector::holds`), the parts it writes more than once
469    /// (KERNEL.27, `vector::Repeats`) and its parse lowered (KERNEL.44,
470    /// `super::program`; a risky shape keeps none, its cells read as it does
471    /// only in a grid that loads), in a frame of its own: `add`'s frame
472    /// stands beneath the parse, the deepest a load reaches, which `VLA.1`'s
473    /// limits pin on a stack of 1 MiB.
474    #[inline(never)]
475    fn push(
476        &mut self,
477        text: String,
478        expr: Result<Expr, String>,
479        anchor: CellId,
480        missing: Vec<String>,
481        risky: bool,
482        library: &Library,
483    ) -> usize {
484        let (holds, repeats) = match &expr {
485            Ok(e) => {
486                let repeats = Repeats::of(library, e);
487                let (values, positions) = vector::holds(library, e);
488                ((values + repeats.parts(), positions), repeats)
489            }
490            Err(_) => ((0, 0), Repeats::default()),
491        };
492        let lowered = match &expr {
493            Ok(e) if !risky => Some(Program::lower(library, e)),
494            _ => None,
495        };
496        self.shapes.push(Shape {
497            text,
498            expr,
499            anchor,
500            missing,
501            risky,
502            holds,
503            repeats,
504            fill: None,
505            lowered,
506        });
507        self.shapes.len() - 1
508    }
509
510    /// The order found again from the places: every formula cell's edges
511    /// read off its shape's parse at its offset (a defined name's text read
512    /// as `graph::dependencies` reads it), then Tarjan's components, as
513    /// `graph::plan` finds them for a plan of texts.
514    ///
515    /// Each shape's references are read off its parse once (KERNEL.33,
516    /// asked by Alonzo's playtest pass of Sand, whose load spent 341 ms of
517    /// 491 finding 2.37 million edges a cell at a time): each reference once,
518    /// one to a sheet holding no formula left out, one every `$` holds found
519    /// once for the whole shape; each cell then moves only the rest. A cell's
520    /// vertex is found as reach finds a position, by an array over a sheet
521    /// whose formulas stand close together, and the edges are one flat array.
522    /// Every cell's edges are the ones a walk of its parse at its offset
523    /// finds, sorted and each once, so the order, its runs and its cycles
524    /// are the same.
525    pub fn plan(&mut self, model: &Workbook, names: &Names, extents: &[Extent]) {
526        let mut ids: Vec<CellId> = self.places.keys().copied().collect();
527        ids.sort_unstable();
528        let index = Positions::of(&ids, model.sheets.len());
529        let mut holds = vec![false; model.sheets.len()];
530        for id in &ids {
531            holds[id.0] = true;
532        }
533        let vertices = Vertices {
534            model,
535            extents,
536            index: &index,
537            holds: &holds,
538        };
539        let reads: Vec<ShapeReads> = self
540            .shapes
541            .iter()
542            .map(|shape| vertices.reads_of(shape))
543            .collect();
544        // Every cell's edges in one array, cell `v`'s at
545        // `targets[offsets[v]..offsets[v + 1]]`, where a vector of edges was
546        // made for each cell.
547        let n = ids.len();
548        let mut offsets: Vec<usize> = Vec::with_capacity(n + 1);
549        let mut targets: Vec<u32> = Vec::with_capacity(n * 8);
550        offsets.push(0);
551        let mut shape_of: Vec<usize> = Vec::with_capacity(n);
552        let mut deps: Vec<CellId> = Vec::new();
553        let mut edges: Vec<u32> = Vec::new();
554        for id in &ids {
555            edges.clear();
556            deps.clear();
557            let p = self.places[id];
558            shape_of.push(p.shape);
559            let shape = &self.shapes[p.shape];
560            let read = !self.unread.contains_key(id);
561            match &shape.expr {
562                Ok(e) if read => {
563                    let r = &reads[p.shape];
564                    edges.extend_from_slice(&r.fixed);
565                    for re in &r.moving {
566                        let Ok(areas) = areas_at(model, id.0, re, p.d_row, p.d_col) else {
567                            continue;
568                        };
569                        for a in &areas {
570                            vertices.formulas_in(a, &mut edges);
571                        }
572                    }
573                    if r.named {
574                        e.each_read(&mut |r| {
575                            if let Read::Name { name, sheet } = r {
576                                let scope = sheet.and_then(|s| model.find_sheet(s));
577                                if let Some(t) = names.resolve(name, scope, id.0) {
578                                    dependencies(
579                                        model,
580                                        names,
581                                        extents,
582                                        scope.unwrap_or(id.0),
583                                        t,
584                                        &mut deps,
585                                    );
586                                }
587                            }
588                        });
589                    }
590                }
591                // A text this version does not parse, here or at this
592                // offset: its edges as the text scan reads them, so that a
593                // cycle through it is still found.
594                _ => {
595                    let moved = refers::shift_a1_references(&shape.text, p.d_row, p.d_col);
596                    dependencies(model, names, extents, id.0, &moved, &mut deps);
597                }
598            }
599            for d in &deps {
600                if let Some(v) = index.get(d.0, d.1, d.2) {
601                    edges.push(vertex(v));
602                }
603            }
604            edges.sort_unstable();
605            edges.dedup();
606            targets.extend_from_slice(&edges);
607            offsets.push(targets.len());
608        }
609        let (members, starts) = graph::tarjan_flat(&offsets, &targets);
610        let shape_at =
611            self.order_and_runs(&ids, (&offsets, &targets), &shape_of, (&members, &starts));
612        self.arrays = read_as_arrays(model, &self.shapes, (&ids, &shape_of), &self.runs);
613        let places = &self.places;
614        let formula = |id: CellId| places.contains_key(&id);
615        self.reach = Reach::of(model, &self.shapes, &self.order, shape_at, &formula);
616    }
617
618    /// The order, the cycles and the runs, from the cells, each one's edges
619    /// (one flat array, as [`graph::tarjan_flat`] reads them) and shape, and
620    /// the components, which come each after everything it reads; the shape
621    /// of each cell of the order answered.
622    ///
623    /// Each cell's level is 0 when it reads no formula of the plan, else one
624    /// past the deepest it reads, its component's. The order is by level,
625    /// then shape, then cell, sheet first: any order that puts a cell after
626    /// what it reads computes the same frame, and in this one the cells of a
627    /// shape that read none of one another stand together, whether the shape
628    /// fills rows or a column beside others (KERNEL.20).
629    fn order_and_runs(
630        &mut self,
631        ids: &[CellId],
632        (offsets, targets): (&[usize], &[u32]),
633        shape_of: &[usize],
634        (members, starts): (&[u32], &[usize]),
635    ) -> Vec<u32> {
636        self.order.clear();
637        self.cycles.clear();
638        self.runs.clear();
639        let adj = |v: usize| &targets[offsets[v]..offsets[v + 1]];
640        let mut component = vec![0; ids.len()];
641        let mut level = vec![0; ids.len()];
642        let mut alone = vec![false; ids.len()];
643        for (c, ends) in starts.windows(2).enumerate() {
644            let comp = &members[ends[0]..ends[1]];
645            for &v in comp {
646                component[v as usize] = c;
647            }
648            let deepest = comp
649                .iter()
650                .flat_map(|&v| adj(v as usize))
651                .filter(|&&u| component[u as usize] != c)
652                .map(|&u| level[u as usize] + 1)
653                .max()
654                .unwrap_or(0);
655            for &v in comp {
656                level[v as usize] = deepest;
657            }
658            if comp.len() == 1 && !adj(comp[0] as usize).contains(&comp[0]) {
659                alone[comp[0] as usize] = true;
660            } else {
661                let mut cells: Vec<CellId> = comp.iter().map(|&i| ids[i as usize]).collect();
662                cells.sort_unstable();
663                self.cycles.push(cells);
664            }
665        }
666        self.cycles.sort();
667        // By shape and then by level, each pass keeping the order before it,
668        // so by level, shape and cell, a cell's sheet first among its own;
669        // the second pass's counts are where each level ends (KERNEL.30).
670        let cells: Vec<usize> = (0..ids.len()).filter(|&v| alone[v]).collect();
671        let (cells, _) = by_key(&cells, self.shapes.len(), |v| shape_of[v]);
672        let levels = level.iter().max().map_or(0, |deepest| deepest + 1);
673        let (order, ends) = by_key(&cells, levels, |v| level[v]);
674        self.level_ends = ends;
675        // The run each cell of the order joined, by the run's first position:
676        // a cell joins the run before it when it is of the run's shape and
677        // sheet and reads no cell of the run, so that the run's cells may be
678        // computed in any order, all at once among them (KERNEL.20).
679        let mut joined = vec![usize::MAX; ids.len()];
680        let mut run = Run {
681            start: 0,
682            end: 0,
683            shape: usize::MAX,
684        };
685        let mut shape_at = Vec::with_capacity(order.len());
686        for v in order {
687            let (id, shape) = (ids[v], shape_of[v]);
688            let pos = self.order.len();
689            let joins = shape == run.shape
690                && id.0 == self.order[run.start].0
691                && adj(v).iter().all(|&u| joined[u as usize] != run.start);
692            if !joins {
693                self.keep(run);
694                run = Run {
695                    start: pos,
696                    end: pos,
697                    shape,
698                };
699            }
700            joined[v] = run.start;
701            run.end = pos + 1;
702            self.order.push(id);
703            shape_at.push(shape as u32);
704        }
705        self.keep(run);
706        shape_at
707    }
708
709    /// A run of the order kept to be evaluated at once when it is long
710    /// enough and its shape parsed and not risky, so that every cell of it
711    /// reads as the shape does (KERNEL.20). (A risky shape's cells all read
712    /// as it does in a grid that loaded, every other being refused; the
713    /// shape is evaluated cell by cell all the same.)
714    fn keep(&mut self, run: Run) {
715        let Some(shape) = self.shapes.get(run.shape) else {
716            return;
717        };
718        if run.end - run.start >= RUN && !shape.risky && shape.expr.is_ok() {
719            self.runs.push(run);
720        }
721    }
722
723    /// How many cells of the order are in runs.
724    #[cfg(test)]
725    pub(crate) fn cells_in_runs(&self) -> usize {
726        self.runs.iter().map(|r| r.end - r.start).sum()
727    }
728
729    /// The plan spelled whole, each map in order: every shape with its first
730    /// cell, its fill, its text and its parse; every cell's place and the
731    /// construct it leaves unread; the two maps a cell finds its shape by;
732    /// the order, its cycles, runs and levels; the reach. KERNEL.34's oracle,
733    /// a write leaving the plan a load of its grid makes, part for part.
734    #[cfg(test)]
735    pub(crate) fn spelled(&self) -> String {
736        use std::fmt::Write;
737        let mut out = String::new();
738        for (k, s) in self.shapes.iter().enumerate() {
739            let _ = writeln!(
740                out,
741                "shape {k} {:?} {:?} {:?} {:?} {:?} {:?} {:?} {:?} {:?}",
742                s.anchor, s.fill, s.text, s.expr, s.missing, s.risky, s.holds, s.repeats, s.lowered
743            );
744        }
745        let mut places: Vec<(&CellId, &Place)> = self.places.iter().collect();
746        places.sort_by_key(|(id, _)| **id);
747        let mut unread: Vec<(&CellId, &String)> = self.unread.iter().collect();
748        unread.sort();
749        let mut shared: Vec<(&(usize, u32), &usize)> = self.by_shared.iter().collect();
750        shared.sort();
751        let mut r1c1: Vec<(&String, &usize)> = self.by_r1c1.iter().collect();
752        r1c1.sort();
753        let _ = writeln!(out, "places {places:?}\nunread {unread:?}");
754        let _ = writeln!(out, "by shared {shared:?}\nby r1c1 {r1c1:?}");
755        let _ = writeln!(
756            out,
757            "order {:?}\ncycles {:?}\nruns {:?}\nlevels {:?}\narrays {:?}",
758            self.order, self.cycles, self.runs, self.level_ends, self.arrays
759        );
760        out.push_str(&self.reach.spelled());
761        out
762    }
763
764    /// Whether a read takes a sheet as an array (KERNEL.31): a shape reads a
765    /// range of it, or a shape with runs reads a cell of it by a reference
766    /// no `$` holds at both corners, which its runs read by slices of the
767    /// sheet's array where one is kept. A sheet whose cells only cells alone
768    /// read, or only references every `$` holds, a run reading such a cell
769    /// once, is taken as none: a cell's read by an array's slot costs what
770    /// its read by its position costs (measured in the item's scoping).
771    pub(crate) fn read_as_array(&self, sheet: usize) -> bool {
772        self.arrays.get(sheet).is_some_and(|&b| b)
773    }
774
775    /// Whether a sheet of the grid the plan was made in keeps its frame's
776    /// values as an array (KERNEL.31): a read takes it as one, and its cells
777    /// stand close enough together, by the rule a machine keeps it by
778    /// (`dense::limit`), so that a door's census and a machine's name the
779    /// same reads alike.
780    pub(crate) fn keeps_array(&self, model: &Workbook, sheet: usize) -> bool {
781        self.read_as_array(sheet)
782            && model.sheets.get(sheet).is_some_and(|s| {
783                let most = crate::machine::CELLS as u64;
784                super::dense::fits(s.extent(), super::dense::limit(s.cells.len(), most))
785            })
786    }
787
788    /// The first run of the order that ends after a position, by its index
789    /// among the runs: the one the position is in, or the next (KERNEL.44, a
790    /// step walking the runs beside the positions).
791    pub(crate) fn run_after(&self, pos: usize) -> usize {
792        self.runs.partition_point(|r| r.end <= pos)
793    }
794
795    /// A run by its index among the runs: its first position, the position
796    /// after its last cell, and its shape; `None` past the last.
797    #[inline]
798    pub(crate) fn run_span(&self, k: usize) -> Option<(usize, usize, usize)> {
799        self.runs.get(k).map(|r| (r.start, r.end, r.shape))
800    }
801
802    /// A shape's program and its first cell, from which each of its cells'
803    /// offset is, by its index (KERNEL.44); `None` for a shape left to `Ctx`.
804    #[inline]
805    pub(crate) fn program(&self, index: usize) -> Option<(&Program, CellId)> {
806        let s = self.shapes.get(index)?;
807        match &s.lowered {
808            Some(Ok(p)) => Some((p, s.anchor)),
809            _ => None,
810        }
811    }
812
813    /// Cells of one run, a part of it or the whole, evaluated at once
814    /// against the grid as `env` shows it (KERNEL.20, `super::vector`), each
815    /// cell's answer pushed to `out` in order: what [`Shapes::evaluate`]
816    /// answers for each, the environment told each cell as it is computed;
817    /// or, where every answer is a number, `out` left empty and the numbers
818    /// in `numbers` (KERNEL.43).
819    #[allow(clippy::too_many_arguments)]
820    pub(crate) fn evaluate_run(
821        &self,
822        env: &dyn Env,
823        library: &Library,
824        shape: usize,
825        cells: &[CellId],
826        lanes: &mut Lanes,
827        out: &mut Vec<Computed>,
828        numbers: &mut Vec<f64>,
829    ) {
830        let s = &self.shapes[shape];
831        // The paths the run takes are noted against its shape, in a test
832        // (KERNEL.30's oracle: the census names each of them).
833        #[cfg(test)]
834        vector::taken::shape(shape);
835        match &s.expr {
836            Ok(e) => {
837                let held = s.holds;
838                vector::evaluate(
839                    env, library, e, held, &s.repeats, s.anchor, cells, lanes, out, numbers,
840                );
841            }
842            // A run holds a parsed shape; were it not, each cell alone.
843            Err(_) => out.extend(cells.iter().map(|&id| {
844                env.enter(id);
845                self.evaluate(env, library, id)
846            })),
847        }
848    }
849
850    /// The formula cells in the order of evaluation, each after everything
851    /// it reads; the cells of a cycle are not in it.
852    pub fn order(&self) -> &[CellId] {
853        &self.order
854    }
855
856    /// What each shape reads, and where the cells of the order stand
857    /// (KERNEL.20).
858    pub(crate) fn reach(&self) -> &Reach {
859        &self.reach
860    }
861
862    /// Every cycle, each its cells sorted, the cycles by their first cell.
863    pub fn cycles(&self) -> &[Vec<CellId>] {
864        &self.cycles
865    }
866
867    /// How many formula cells are placed.
868    pub fn formulas(&self) -> usize {
869        self.places.len()
870    }
871
872    /// How many shapes the formulas make.
873    pub fn shapes(&self) -> usize {
874        self.shapes.len()
875    }
876
877    /// Whether a cell holds a placed formula.
878    pub fn holds(&self, id: CellId) -> bool {
879        self.places.contains_key(&id)
880    }
881
882    /// A formula cell's place.
883    pub fn place(&self, id: CellId) -> Option<Place> {
884        self.places.get(&id).copied()
885    }
886
887    /// A shape, by its index.
888    pub fn shape(&self, index: usize) -> Option<&Shape> {
889        self.shapes.get(index)
890    }
891
892    /// The plan printed as rows, the census (KERNEL.30): a `(plan ...)` row
893    /// of totals, a `(readers ...)` row for each sheet whose changed cells
894    /// are checked against readers, then a `(shape ...)` row for each shape
895    /// a cell holds, each read named by the path an engine's machine takes
896    /// for it (`super::census`). Every sheet is read as a frame's own, as a
897    /// workbook's are: this is the census of a plan [`Shapes::of`] made over
898    /// a grid with no `.last` twin, each sheet kept as an array where a
899    /// machine of the same grid keeps it (KERNEL.31, [`Shapes::keeps_array`]).
900    /// A machine's, its twins read as it keeps them, is `Machine::census`.
901    /// Computed when asked from the plan as it stands, which keeps nothing
902    /// for it but where each level ends and which sheets a read takes as an
903    /// array.
904    pub fn census(&self, model: &Workbook, library: &Library) -> String {
905        super::census::rows(self, model, library, &|s| self.keeps_array(model, s))
906    }
907
908    /// Where each level of the order ends, by level (KERNEL.30).
909    pub(crate) fn level_ends(&self) -> &[u32] {
910        &self.level_ends
911    }
912
913    /// Each run of the order as its first position, the position after its
914    /// last cell and its shape, by first position (KERNEL.30).
915    pub(crate) fn run_spans(&self) -> impl Iterator<Item = (usize, usize, usize)> + '_ {
916        self.runs.iter().map(|r| (r.start, r.end, r.shape))
917    }
918
919    /// Every formula cell and its place, in no order (KERNEL.30).
920    pub(crate) fn places(&self) -> impl Iterator<Item = (CellId, Place)> + '_ {
921        self.places.iter().map(|(&id, &p)| (id, p))
922    }
923
924    /// Every shape the plan holds, a shape whose cells a write has since
925    /// replaced among them (KERNEL.30).
926    pub(crate) fn every_shape(&self) -> &[Shape] {
927        &self.shapes
928    }
929
930    /// One formula cell evaluated against the grid as `env` shows it, the
931    /// cell being computed being `env.here()`.
932    pub fn evaluate(&self, env: &dyn Env, library: &Library, id: CellId) -> Computed {
933        let Some(p) = self.places.get(&id) else {
934            return Computed::NotComputed(Reason::Construct(String::new()));
935        };
936        let shape = &self.shapes[p.shape];
937        if let Some(construct) = self.unread.get(&id) {
938            return Computed::NotComputed(Reason::Construct(construct.clone()));
939        }
940        match &shape.expr {
941            Err(construct) => Computed::NotComputed(Reason::Construct(construct.clone())),
942            Ok(e) => {
943                let ctx = Ctx::at(env, library, p.d_row, p.d_col);
944                match ctx.eval(e).and_then(|arg| ctx.one(&arg)) {
945                    Ok(v) => Computed::Value(v.settled()),
946                    Err(reason) => Computed::NotComputed(reason),
947                }
948            }
949        }
950    }
951
952    /// The first thing that stops the grid from being stepped, in the order
953    /// [`super::Calc::first_refusal`] asks it: a cycle (`calc-cycle`), the
954    /// first cell calling a function the library does not hold
955    /// (`calc-function-not-computed`), the first cell whose text this
956    /// version does not read (`calc-construct-not-read`); with the cell the
957    /// refusal stands on, for its line. `only` limits the second and third
958    /// to the cells a write placed.
959    pub fn first_refusal(
960        &self,
961        model: &Workbook,
962        only: Option<&[CellId]>,
963    ) -> Option<(CellId, Refusal)> {
964        if let Some(cycle) = self.cycles.first() {
965            let cells: Vec<String> = cycle.iter().map(|id| spell_cell(model, *id)).collect();
966            return Some((
967                cycle[0],
968                raise("calc-cycle", &[("cells", &cells.join(", "))]),
969            ));
970        }
971        let mut ids: Vec<CellId> = match only {
972            Some(cells) => cells
973                .iter()
974                .copied()
975                .filter(|id| self.places.contains_key(id))
976                .collect(),
977            None => self.places.keys().copied().collect(),
978        };
979        // A grid is refused nowhere far more often than somewhere: its cells
980        // are sorted, to find the first refused, only when one is (KERNEL.34).
981        let refused = |id: &CellId| {
982            let shape = &self.shapes[self.places[id].shape];
983            !shape.missing.is_empty() || shape.expr.is_err() || self.unread.contains_key(id)
984        };
985        if !ids.iter().any(refused) {
986            return None;
987        }
988        ids.sort_unstable();
989        for id in &ids {
990            let shape = &self.shapes[self.places[id].shape];
991            if let Some(name) = shape.missing.first() {
992                return Some((
993                    *id,
994                    raise(
995                        "calc-function-not-computed",
996                        &[("cell", &spell_cell(model, *id)), ("function", name)],
997                    ),
998                ));
999            }
1000        }
1001        for id in &ids {
1002            let p = self.places[id];
1003            let shape = &self.shapes[p.shape];
1004            let label = match (&shape.expr, self.unread.get(id).cloned()) {
1005                (_, Some(unread)) => Some(unread),
1006                // The construct as the cell's own text spells it.
1007                (Err(construct), None) if p.d_row == 0 && p.d_col == 0 => Some(construct.clone()),
1008                (Err(construct), None) => {
1009                    let moved = refers::shift_a1_references(&shape.text, p.d_row, p.d_col);
1010                    Some(
1011                        formula::parse(&moved)
1012                            .err()
1013                            .unwrap_or_else(|| construct.clone()),
1014                    )
1015                }
1016                (Ok(_), None) => None,
1017            };
1018            if let Some(label) = label {
1019                return Some((
1020                    *id,
1021                    formula::unread_refusal(&spell_cell(model, *id), &label),
1022                ));
1023            }
1024        }
1025        None
1026    }
1027}
1028
1029/// Cells ordered by a key below `keys`, cells of one key in the order they
1030/// came in: a counting sort, a pass over the cells and one over the keys,
1031/// for the order of a plan (KERNEL.20); and, by key, where its cells end in
1032/// that order, which the sort's own counts hold once it is done (KERNEL.30).
1033/// A plan's cells are positions of an order a `u32` holds, as its reach's
1034/// are.
1035fn by_key(cells: &[usize], keys: usize, key: impl Fn(usize) -> usize) -> (Vec<usize>, Vec<u32>) {
1036    let mut start = vec![0u32; keys + 1];
1037    for &v in cells {
1038        start[key(v) + 1] += 1;
1039    }
1040    for k in 0..keys {
1041        start[k + 1] += start[k];
1042    }
1043    let mut out = vec![0; cells.len()];
1044    for &v in cells {
1045        let k = key(v);
1046        out[start[k] as usize] = v;
1047        start[k] += 1;
1048    }
1049    start.truncate(keys);
1050    (out, start)
1051}
1052
1053/// By sheet of the grid, whether a read takes it as an array (KERNEL.31,
1054/// [`Shapes::read_as_array`]): a sheet a shape reads a range of, or a cell of
1055/// by a reference no `$` holds at both corners in a shape with runs. A
1056/// reference that names no sheet reads each sheet its shape's cells stand
1057/// on; a span of sheets is read through its cells, and so is a defined
1058/// name's text. One pass over each shape's reads, and one over the plan's
1059/// cells, `ids` beside `shape_of`, for the shapes that read their own
1060/// sheets: two arrays read in order, where a walk of the places would look
1061/// each cell up (`plan`).
1062fn read_as_arrays(
1063    model: &Workbook,
1064    shapes: &[Shape],
1065    (ids, shape_of): (&[CellId], &[usize]),
1066    runs: &[Run],
1067) -> Vec<bool> {
1068    let mut with_runs = vec![false; shapes.len()];
1069    for r in runs {
1070        with_runs[r.shape] = true;
1071    }
1072    let mut read = vec![false; model.sheets.len()];
1073    // By shape, whether it reads its own sheets so.
1074    let mut own = vec![false; shapes.len()];
1075    for (k, shape) in shapes.iter().enumerate() {
1076        let Ok(e) = &shape.expr else {
1077            continue;
1078        };
1079        e.each_read(&mut |r| {
1080            let Read::Ref(r) = r else {
1081                return;
1082            };
1083            if r.last_sheet.is_some() {
1084                return;
1085            }
1086            let held = r.corners.row_abs1 && r.corners.col_abs1;
1087            let range = !matches!(r.shape, refers::Shape::Cell);
1088            if !range && (held || !with_runs[k]) {
1089                return;
1090            }
1091            let mut mark = |s: usize| {
1092                if let Some(b) = read.get_mut(s) {
1093                    *b = true;
1094                }
1095            };
1096            match (r.found, &r.sheet) {
1097                (Some((first, _)), _) => mark(usize::from(first)),
1098                (None, None) => own[k] = true,
1099                (None, Some(name)) => {
1100                    if let Some(s) = model.find_sheet(name) {
1101                        mark(s);
1102                    }
1103                }
1104            }
1105        });
1106    }
1107    for (id, &k) in ids.iter().zip(shape_of) {
1108        if own[k] {
1109            read[id.0] = true;
1110        }
1111    }
1112    read
1113}
1114
1115/// A formula cell's vertex, its position among a plan's sorted cells, as an
1116/// edge holds it: a plan's cells are positions a `u32` holds, as its
1117/// order's are.
1118fn vertex(position: usize) -> u32 {
1119    position as u32
1120}
1121
1122/// A plan's formula cells as the vertices of its edges (KERNEL.33): each
1123/// cell's position among the plan's sorted cells, and whether each sheet
1124/// holds any formula.
1125struct Vertices<'a> {
1126    model: &'a Workbook,
1127    extents: &'a [Extent],
1128    index: &'a Positions,
1129    holds: &'a [bool],
1130}
1131
1132/// A shape's references as its plan reads them, once a shape (KERNEL.33):
1133/// the vertices those every `$` holds name, the same at every cell, sorted
1134/// and each once; the references each cell moves, each once; and whether
1135/// the parse reads a name, which each cell resolves from its own sheet.
1136struct ShapeReads<'e> {
1137    fixed: Vec<u32>,
1138    moving: Vec<&'e RefExpr>,
1139    named: bool,
1140}
1141
1142impl Vertices<'_> {
1143    /// The formula cells inside an area, each by its vertex, to `out`: by
1144    /// the array where the sheet's formulas stand close enough together to
1145    /// be held as one, else by the sheet's tree; none on a sheet holding no
1146    /// formula, from which no edge comes.
1147    fn formulas_in(&self, a: &Area, out: &mut Vec<u32>) {
1148        if !self.holds.get(a.sheet).copied().unwrap_or(false) {
1149            return;
1150        }
1151        let rect = (a.top, a.left, a.bottom, a.right);
1152        if self
1153            .index
1154            .each_in(a.sheet, rect, &mut |v| out.push(vertex(v)))
1155        {
1156            return;
1157        }
1158        let sheet = &self.model.sheets[a.sheet];
1159        let extent = self.extents.get(a.sheet).copied().flatten();
1160        for ((row, col), cell) in cells_in(sheet, extent, a) {
1161            if is_formula(&cell.content) {
1162                if let Some(v) = self.index.get(a.sheet, row, col) {
1163                    out.push(vertex(v));
1164                }
1165            }
1166        }
1167    }
1168
1169    /// A shape's references read off its parse once: each once, however
1170    /// often the parse repeats it; one to the single sheet its placement
1171    /// found, when that sheet holds no formula, left out; one every `$`
1172    /// holds there, found now, since it names the same cells at every
1173    /// offset and `graph::areas_at` reads a found sheet and not the cell's;
1174    /// every other kept for each cell to move.
1175    fn reads_of<'e>(&self, shape: &'e Shape) -> ShapeReads<'e> {
1176        let mut reads = ShapeReads {
1177            fixed: Vec::new(),
1178            moving: Vec::new(),
1179            named: false,
1180        };
1181        let Ok(e) = &shape.expr else {
1182            return reads;
1183        };
1184        let mut each: Vec<&RefExpr> = Vec::new();
1185        e.each_read(&mut |r| match r {
1186            Read::Ref(re) => {
1187                if !each.contains(&re) {
1188                    each.push(re);
1189                }
1190            }
1191            Read::Name { .. } => reads.named = true,
1192        });
1193        for re in each {
1194            let found_once = match (re.found, &re.last_sheet) {
1195                (Some((first, last)), None) if first == last => Some(usize::from(first)),
1196                _ => None,
1197            };
1198            if let Some(sheet) = found_once {
1199                if !self.holds.get(sheet).copied().unwrap_or(false) {
1200                    continue;
1201                }
1202                if every_dollar(re) {
1203                    if let Ok(areas) = areas_at(self.model, sheet, re, 0, 0) {
1204                        for a in &areas {
1205                            self.formulas_in(a, &mut reads.fixed);
1206                        }
1207                    }
1208                    continue;
1209                }
1210            }
1211            reads.moving.push(re);
1212        }
1213        reads.fixed.sort_unstable();
1214        reads.fixed.dedup();
1215        reads
1216    }
1217}
1218
1219/// Whether every `$` holds a reference, so that it names the same cells at
1220/// every offset (KERNEL.33): a cell's two, a range's four, a whole column's
1221/// columns, a whole row's rows.
1222fn every_dollar(r: &RefExpr) -> bool {
1223    let c = &r.corners;
1224    match r.shape {
1225        refers::Shape::Cell => c.row_abs1 && c.col_abs1,
1226        refers::Shape::Range => c.row_abs1 && c.col_abs1 && c.row_abs2 && c.col_abs2,
1227        refers::Shape::Column => c.col_abs1 && c.col_abs2,
1228        refers::Shape::Row => c.row_abs1 && c.row_abs2,
1229        refers::Shape::None => false,
1230    }
1231}
1232
1233/// Every reference of a parse that names its sheets, those sheets found once
1234/// against the grid it is placed in (KERNEL.20): a machine's sheets, its
1235/// twins among them, are fixed for its life, so the index a name finds at
1236/// placement is the one it would find at every evaluation. A reference to
1237/// the formula's own sheet, and one naming a sheet the grid does not hold,
1238/// stay unfound and are found by name at each evaluation, as before. The
1239/// walk keeps its own stack, so that a chain of operators costs it no depth.
1240fn find_sheets(expr: &mut Expr, model: &Workbook) {
1241    let mut stack = vec![expr];
1242    while let Some(e) = stack.pop() {
1243        match e {
1244            Expr::Ref(r) => r.found = sheets_of(model, r),
1245            Expr::Call(_, args) => stack.extend(args.iter_mut()),
1246            Expr::Neg(x) | Expr::Pos(x) | Expr::Percent(x) => stack.push(x),
1247            Expr::Binary(_, a, b) => {
1248                stack.push(a);
1249                stack.push(b);
1250            }
1251            Expr::Name { .. }
1252            | Expr::Number(_)
1253            | Expr::Text(_)
1254            | Expr::Bool(_)
1255            | Expr::Error(_)
1256            | Expr::Empty => {}
1257        }
1258    }
1259}
1260
1261/// A reference's named sheets as `graph::areas_at` finds them by name: the
1262/// first, and the last of a span or the first again; `None` when it names
1263/// none, one the grid does not hold, or one past the 256th, which a byte
1264/// cannot hold and the name finds as before.
1265fn sheets_of(model: &Workbook, r: &RefExpr) -> Option<(u8, u8)> {
1266    let first = model.find_sheet(r.sheet.as_deref()?)?;
1267    let last = match &r.last_sheet {
1268        None => first,
1269        Some(name) => model.find_sheet(name)?,
1270    };
1271    Some((u8::try_from(first).ok()?, u8::try_from(last).ok()?))
1272}
1273
1274/// `Sheet!A1` for a cell, the sheet quoted as a reference quotes it.
1275pub fn spell_cell(model: &Workbook, id: CellId) -> String {
1276    let sheet = model
1277        .sheets
1278        .get(id.0)
1279        .map(|s| s.name.as_str())
1280        .unwrap_or("");
1281    format!("{}!{}", quote_sheet(sheet), cell_ref(id.1, id.2))
1282}
1283
1284#[cfg(test)]
1285mod tests {
1286    use super::*;
1287
1288    #[test]
1289    fn every_dollar_holds_a_reference_whose_every_number_is_held() {
1290        // KERNEL.33: a plan finds a reference once for its whole shape only
1291        // when every `$` holds it, so that it names the same cells at every
1292        // offset: a cell's two numbers, a range's four, a whole column's
1293        // columns, a whole row's rows. One unheld number moves it.
1294        for (text, held) in [
1295            ("$C$2", true),
1296            ("$C2", false),
1297            ("C$2", false),
1298            ("C2", false),
1299            ("$C$2:$D$4", true),
1300            ("$C$2:D4", false),
1301            ("$C$2:$D4", false),
1302            ("$C$2:D$4", false),
1303            ("C2:$D$4", false),
1304            ("$C:$D", true),
1305            ("$C:D", false),
1306            ("C:D", false),
1307            ("$2:$4", true),
1308            ("$2:4", false),
1309            ("2:4", false),
1310            ("S!$C$2:$D$4", true),
1311            ("S!C2:$D$4", false),
1312        ] {
1313            let Ok(Expr::Ref(r)) = formula::parse(text) else {
1314                panic!("{text} reads as one reference")
1315            };
1316            assert_eq!(every_dollar(&r), held, "{text}");
1317        }
1318    }
1319}