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}