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). What each
33//! shape reads is found with the order, so that an engine follows a changed
34//! cell to the cells of the order that read it and computes those alone
35//! (`super::reach`, KERNEL.20).
36//!
37//! The plan is printed as rows on request ([`Shapes::census`], KERNEL.30,
38//! `super::census`): each shape's cells, the levels of the order it spans,
39//! its runs and the paths its reads take, so that a grid's cost is read
40//! before a frame runs. Of all that, the plan keeps for it only where each
41//! level of the order ends, the array its counting sort already makes.
42
43use std::collections::HashMap;
44
45use crate::fx::FxHashMap;
46use crate::messages::{raise, Refusal};
47use crate::refers::{self, quote_sheet};
48use crate::sheet::{cell_ref, shared_masters, strip_future_prefixes, Content, Workbook};
49
50use super::eval::{Computed, Ctx, Env, Library, Reason};
51use super::formula::{self, Expr, Read, RefExpr};
52use super::graph::{
53 self, areas_at, cells_in, dependencies, is_formula, Area, CellId, Extent, Names,
54};
55use super::reach::{Positions, Reach};
56use super::vector::{self, Lanes, Repeats};
57
58/// One formula, parsed once, and the cell it was read at.
59#[derive(Clone, Debug)]
60pub struct Shape {
61 /// The text at the shape's first cell, without its `=`, as the formula
62 /// bar shows it.
63 pub text: String,
64 /// The parse, or the construct it stopped at.
65 pub expr: Result<Expr, String>,
66 /// The cell the text was read at; every other cell of the shape is at
67 /// an offset from it.
68 pub anchor: CellId,
69 /// The functions the parse calls that the library does not hold,
70 /// sorted, each once: what a load refuses, read once a shape.
71 missing: Vec<String>,
72 /// Whether a cell of the shape may stand for a text the parse does not
73 /// read: the text holds a 3D span (`Jan:Dec!A1`) or a broken reference
74 /// with a cell after it (`#REF!A1`), each of which the mover writes as
75 /// a construct when it moves off the sheet (`Jan:Dec!#REF!`,
76 /// `#REF!#REF!`). Found 2026-10-09 by holding the offset to the moved
77 /// text's parse over the refers fixture.
78 pub(crate) risky: bool,
79 /// The most buffers an evaluation of the parse at many cells holds at
80 /// once, found once (KERNEL.20, `vector::holds`), a lane for each of its
81 /// repeated parts with them (KERNEL.27).
82 holds: (usize, usize),
83 /// The parts the parse writes more than once that a run computes once
84 /// (KERNEL.27, `vector::Repeats`).
85 repeats: Repeats,
86}
87
88impl Shape {
89 /// The most buffers of each kind a run of the shape holds at once.
90 #[cfg(test)]
91 pub(crate) fn holds(&self) -> (usize, usize) {
92 self.holds
93 }
94
95 /// The functions the parse calls that the library does not hold, sorted,
96 /// each once (KERNEL.30's census prints them).
97 pub(crate) fn missing(&self) -> &[String] {
98 &self.missing
99 }
100
101 /// The construct a cell at this offset stands for that the shape's
102 /// first cell does not, as the moved text's parse names it; `None` when
103 /// the moved text reads as the shape does, which for a shape that is not
104 /// risky it always does (the refers fixture moved to every edge of the
105 /// sheet holds that).
106 pub fn unread_at(&self, d_row: i64, d_col: i64) -> Option<String> {
107 if !self.risky || (d_row == 0 && d_col == 0) || self.expr.is_err() {
108 return None;
109 }
110 formula::parse(&refers::shift_a1_references(&self.text, d_row, d_col)).err()
111 }
112}
113
114/// Where a formula cell's formula comes from: its shape, and the cell's
115/// offset from the shape's first cell.
116#[derive(Clone, Copy, Debug, PartialEq, Eq)]
117pub struct Place {
118 pub shape: usize,
119 pub d_row: i64,
120 pub d_col: i64,
121}
122
123/// The plan: every formula cell placed on a shape, the order, the cycles.
124#[derive(Clone, Debug, Default)]
125pub struct Shapes {
126 shapes: Vec<Shape>,
127 /// A plain formula's shape, by its R1C1 text.
128 by_r1c1: HashMap<String, usize>,
129 /// A shared formula's shape, by its sheet and its index.
130 by_shared: HashMap<(usize, u32), usize>,
131 /// Every formula cell's place, looked up at each of its evaluations,
132 /// under the crate's own hash (KERNEL.25).
133 places: FxHashMap<CellId, Place>,
134 /// A cell of a risky shape whose own text the parse does not read: the
135 /// construct it names, found once when the cell is placed.
136 unread: FxHashMap<CellId, String>,
137 order: Vec<CellId>,
138 cycles: Vec<Vec<CellId>>,
139 /// The runs of the order an engine evaluates at once (KERNEL.20), by
140 /// their first position: each of at least [`RUN`] cells of one parsed
141 /// shape that is not risky, on one sheet, consecutive in the order, none
142 /// reading another.
143 runs: Vec<Run>,
144 /// What each shape reads, found with the order (KERNEL.20).
145 reach: Reach,
146 /// Where each level of the order ends, by level (KERNEL.30): the
147 /// position after its last cell, the order's own counting sort kept
148 /// rather than dropped, so that the census finds each cell's level
149 /// without planning again.
150 level_ends: Vec<u32>,
151}
152
153/// A run of the order: its cells are `order[start..end]`, of one shape.
154#[derive(Clone, Copy, Debug, PartialEq, Eq)]
155struct Run {
156 start: usize,
157 end: usize,
158 shape: usize,
159}
160
161/// The fewest cells a run is evaluated at once for; fewer are evaluated one
162/// by one, as every cell was before (KERNEL.20).
163pub(crate) const RUN: usize = 4;
164
165impl Shapes {
166 /// A plan with nothing in it.
167 pub fn new() -> Shapes {
168 Shapes::default()
169 }
170
171 /// Every formula cell of the sheets `0..sheets` placed, the shapes
172 /// parsed, and the order found. The plan is made in a box: every formula
173 /// is parsed beneath this frame, the deepest a load reaches, which
174 /// `VLA.1`'s limits pin on a stack of 1 MiB, and the plan held here by
175 /// value would stand on it, 480 bytes since it kept its level ends
176 /// (KERNEL.30).
177 pub fn of(
178 model: &Workbook,
179 sheets: usize,
180 library: &Library,
181 names: &Names,
182 extents: &[Extent],
183 ) -> Shapes {
184 let mut s: Box<Shapes> = Box::default();
185 for (si, sheet) in model.sheets.iter().enumerate().take(sheets) {
186 let masters = shared_masters(sheet);
187 for (&(row, col), cell) in &sheet.cells {
188 s.place_with(model, (si, row, col), &cell.content, &masters, library);
189 }
190 }
191 s.plan(model, names, extents);
192 *s
193 }
194
195 /// Place one cell after a write: its old place forgotten, and a new one
196 /// found when it holds a formula. The order waits for [`Shapes::plan`].
197 pub fn replace(&mut self, model: &Workbook, id: CellId, library: &Library) {
198 self.places.remove(&id);
199 self.unread.remove(&id);
200 let Some(sheet) = model.sheets.get(id.0) else {
201 return;
202 };
203 let Some(cell) = sheet.cells.get(&(id.1, id.2)) else {
204 return;
205 };
206 if !is_formula(&cell.content) {
207 return;
208 }
209 let masters = shared_masters(sheet);
210 self.place_with(model, id, &cell.content, &masters, library);
211 }
212
213 fn place_with(
214 &mut self,
215 model: &Workbook,
216 id: CellId,
217 content: &Content,
218 masters: &HashMap<u32, (&str, u32, u32)>,
219 library: &Library,
220 ) {
221 let (si, row, col) = id;
222 let (shape, anchor) = match content {
223 Content::SharedMaster { si: index, .. } | Content::SharedChild { si: index } => {
224 let Some(&(text, m_row, m_col)) = masters.get(index) else {
225 // A child whose master is gone has no text; the machine
226 // never leaves one (a write over a master gives its
227 // other cells their own text first).
228 return;
229 };
230 let anchor = (si, m_row, m_col);
231 let key = (si, *index);
232 let shape = match self.by_shared.get(&key) {
233 Some(&s) => s,
234 None => {
235 let s = self.add(model, strip_future_prefixes(text), anchor, library);
236 self.by_shared.insert(key, s);
237 s
238 }
239 };
240 (shape, anchor)
241 }
242 Content::Formula(t) | Content::DynamicFormula(t) => {
243 let text = strip_future_prefixes(t);
244 let key = refers::r1c1(&text, row, col);
245 match self.by_r1c1.get(&key) {
246 Some(&s) => (s, self.shapes[s].anchor),
247 None => {
248 let s = self.add(model, text, id, library);
249 self.by_r1c1.insert(key, s);
250 (s, id)
251 }
252 }
253 }
254 _ => return,
255 };
256 let (d_row, d_col) = (
257 i64::from(row) - i64::from(anchor.1),
258 i64::from(col) - i64::from(anchor.2),
259 );
260 self.places.insert(
261 id,
262 Place {
263 shape,
264 d_row,
265 d_col,
266 },
267 );
268 match self.shapes[shape].unread_at(d_row, d_col) {
269 Some(construct) => self.unread.insert(id, construct),
270 None => self.unread.remove(&id),
271 };
272 }
273
274 fn add(&mut self, model: &Workbook, text: String, anchor: CellId, library: &Library) -> usize {
275 let mut expr = formula::parse(&text);
276 if let Ok(e) = expr.as_mut() {
277 find_sheets(e, model);
278 }
279 // The calls as the text's tokens hold them, parsed or not, as
280 // `Calc::missing_functions` reads them.
281 let mut missing: Vec<String> = formula::calls(&text)
282 .into_iter()
283 .filter(|name| !library.has(name))
284 .collect();
285 let risky = refers::scan(&text).iter().any(|r| {
286 r.kind == refers::Kind::ThreeD
287 || (r.kind == refers::Kind::Broken && r.shape != refers::Shape::None)
288 });
289 missing.sort_unstable();
290 missing.dedup();
291 self.push(text, expr, anchor, missing, risky, library)
292 }
293
294 /// A shape kept, with what an evaluation of it at many cells holds found
295 /// once (KERNEL.20, `vector::holds`) and the parts it writes more than
296 /// once (KERNEL.27, `vector::Repeats`), in a frame of its own: `add`'s
297 /// frame stands beneath the parse, the deepest a load reaches, which
298 /// `VLA.1`'s limits pin on a stack of 1 MiB.
299 #[inline(never)]
300 fn push(
301 &mut self,
302 text: String,
303 expr: Result<Expr, String>,
304 anchor: CellId,
305 missing: Vec<String>,
306 risky: bool,
307 library: &Library,
308 ) -> usize {
309 let (holds, repeats) = match &expr {
310 Ok(e) => {
311 let repeats = Repeats::of(library, e);
312 let (values, positions) = vector::holds(library, e);
313 ((values + repeats.parts(), positions), repeats)
314 }
315 Err(_) => ((0, 0), Repeats::default()),
316 };
317 self.shapes.push(Shape {
318 text,
319 expr,
320 anchor,
321 missing,
322 risky,
323 holds,
324 repeats,
325 });
326 self.shapes.len() - 1
327 }
328
329 /// The order found again from the places: every formula cell's edges
330 /// read off its shape's parse at its offset (a defined name's text read
331 /// as `graph::dependencies` reads it), then Tarjan's components, as
332 /// `graph::plan` finds them for a plan of texts.
333 ///
334 /// Each shape's references are read off its parse once (KERNEL.33,
335 /// asked by Alonzo's playtest pass of Sand, whose load spent 341 ms of
336 /// 491 finding 2.37 million edges a cell at a time): each reference once,
337 /// one to a sheet holding no formula left out, one every `$` holds found
338 /// once for the whole shape; each cell then moves only the rest. A cell's
339 /// vertex is found as reach finds a position, by an array over a sheet
340 /// whose formulas stand close together, and the edges are one flat array.
341 /// Every cell's edges are the ones a walk of its parse at its offset
342 /// finds, sorted and each once, so the order, its runs and its cycles
343 /// are the same.
344 pub fn plan(&mut self, model: &Workbook, names: &Names, extents: &[Extent]) {
345 let mut ids: Vec<CellId> = self.places.keys().copied().collect();
346 ids.sort_unstable();
347 let index = Positions::of(&ids, model.sheets.len());
348 let mut holds = vec![false; model.sheets.len()];
349 for id in &ids {
350 holds[id.0] = true;
351 }
352 let vertices = Vertices {
353 model,
354 extents,
355 index: &index,
356 holds: &holds,
357 };
358 let reads: Vec<ShapeReads> = self
359 .shapes
360 .iter()
361 .map(|shape| vertices.reads_of(shape))
362 .collect();
363 // Every cell's edges in one array, cell `v`'s at
364 // `targets[offsets[v]..offsets[v + 1]]`, where a vector of edges was
365 // made for each cell.
366 let n = ids.len();
367 let mut offsets: Vec<usize> = Vec::with_capacity(n + 1);
368 let mut targets: Vec<u32> = Vec::with_capacity(n * 8);
369 offsets.push(0);
370 let mut shape_of: Vec<usize> = Vec::with_capacity(n);
371 let mut deps: Vec<CellId> = Vec::new();
372 let mut edges: Vec<u32> = Vec::new();
373 for id in &ids {
374 edges.clear();
375 deps.clear();
376 let p = self.places[id];
377 shape_of.push(p.shape);
378 let shape = &self.shapes[p.shape];
379 let read = !self.unread.contains_key(id);
380 match &shape.expr {
381 Ok(e) if read => {
382 let r = &reads[p.shape];
383 edges.extend_from_slice(&r.fixed);
384 for re in &r.moving {
385 let Ok(areas) = areas_at(model, id.0, re, p.d_row, p.d_col) else {
386 continue;
387 };
388 for a in &areas {
389 vertices.formulas_in(a, &mut edges);
390 }
391 }
392 if r.named {
393 e.each_read(&mut |r| {
394 if let Read::Name { name, sheet } = r {
395 let scope = sheet.and_then(|s| model.find_sheet(s));
396 if let Some(t) = names.resolve(name, scope, id.0) {
397 dependencies(
398 model,
399 names,
400 extents,
401 scope.unwrap_or(id.0),
402 t,
403 &mut deps,
404 );
405 }
406 }
407 });
408 }
409 }
410 // A text this version does not parse, here or at this
411 // offset: its edges as the text scan reads them, so that a
412 // cycle through it is still found.
413 _ => {
414 let moved = refers::shift_a1_references(&shape.text, p.d_row, p.d_col);
415 dependencies(model, names, extents, id.0, &moved, &mut deps);
416 }
417 }
418 for d in &deps {
419 if let Some(v) = index.get(d.0, d.1, d.2) {
420 edges.push(vertex(v));
421 }
422 }
423 edges.sort_unstable();
424 edges.dedup();
425 targets.extend_from_slice(&edges);
426 offsets.push(targets.len());
427 }
428 let (members, starts) = graph::tarjan_flat(&offsets, &targets);
429 let shape_at =
430 self.order_and_runs(&ids, (&offsets, &targets), &shape_of, (&members, &starts));
431 let places = &self.places;
432 let formula = |id: CellId| places.contains_key(&id);
433 self.reach = Reach::of(model, &self.shapes, &self.order, shape_at, &formula);
434 }
435
436 /// The order, the cycles and the runs, from the cells, each one's edges
437 /// (one flat array, as [`graph::tarjan_flat`] reads them) and shape, and
438 /// the components, which come each after everything it reads; the shape
439 /// of each cell of the order answered.
440 ///
441 /// Each cell's level is 0 when it reads no formula of the plan, else one
442 /// past the deepest it reads, its component's. The order is by level,
443 /// then shape, then cell, sheet first: any order that puts a cell after
444 /// what it reads computes the same frame, and in this one the cells of a
445 /// shape that read none of one another stand together, whether the shape
446 /// fills rows or a column beside others (KERNEL.20).
447 fn order_and_runs(
448 &mut self,
449 ids: &[CellId],
450 (offsets, targets): (&[usize], &[u32]),
451 shape_of: &[usize],
452 (members, starts): (&[u32], &[usize]),
453 ) -> Vec<u32> {
454 self.order.clear();
455 self.cycles.clear();
456 self.runs.clear();
457 let adj = |v: usize| &targets[offsets[v]..offsets[v + 1]];
458 let mut component = vec![0; ids.len()];
459 let mut level = vec![0; ids.len()];
460 let mut alone = vec![false; ids.len()];
461 for (c, ends) in starts.windows(2).enumerate() {
462 let comp = &members[ends[0]..ends[1]];
463 for &v in comp {
464 component[v as usize] = c;
465 }
466 let deepest = comp
467 .iter()
468 .flat_map(|&v| adj(v as usize))
469 .filter(|&&u| component[u as usize] != c)
470 .map(|&u| level[u as usize] + 1)
471 .max()
472 .unwrap_or(0);
473 for &v in comp {
474 level[v as usize] = deepest;
475 }
476 if comp.len() == 1 && !adj(comp[0] as usize).contains(&comp[0]) {
477 alone[comp[0] as usize] = true;
478 } else {
479 let mut cells: Vec<CellId> = comp.iter().map(|&i| ids[i as usize]).collect();
480 cells.sort_unstable();
481 self.cycles.push(cells);
482 }
483 }
484 self.cycles.sort();
485 // By shape and then by level, each pass keeping the order before it,
486 // so by level, shape and cell, a cell's sheet first among its own;
487 // the second pass's counts are where each level ends (KERNEL.30).
488 let cells: Vec<usize> = (0..ids.len()).filter(|&v| alone[v]).collect();
489 let (cells, _) = by_key(&cells, self.shapes.len(), |v| shape_of[v]);
490 let levels = level.iter().max().map_or(0, |deepest| deepest + 1);
491 let (order, ends) = by_key(&cells, levels, |v| level[v]);
492 self.level_ends = ends;
493 // The run each cell of the order joined, by the run's first position:
494 // a cell joins the run before it when it is of the run's shape and
495 // sheet and reads no cell of the run, so that the run's cells may be
496 // computed in any order, all at once among them (KERNEL.20).
497 let mut joined = vec![usize::MAX; ids.len()];
498 let mut run = Run {
499 start: 0,
500 end: 0,
501 shape: usize::MAX,
502 };
503 let mut shape_at = Vec::with_capacity(order.len());
504 for v in order {
505 let (id, shape) = (ids[v], shape_of[v]);
506 let pos = self.order.len();
507 let joins = shape == run.shape
508 && id.0 == self.order[run.start].0
509 && adj(v).iter().all(|&u| joined[u as usize] != run.start);
510 if !joins {
511 self.keep(run);
512 run = Run {
513 start: pos,
514 end: pos,
515 shape,
516 };
517 }
518 joined[v] = run.start;
519 run.end = pos + 1;
520 self.order.push(id);
521 shape_at.push(shape as u32);
522 }
523 self.keep(run);
524 shape_at
525 }
526
527 /// A run of the order kept to be evaluated at once when it is long
528 /// enough and its shape parsed and not risky, so that every cell of it
529 /// reads as the shape does (KERNEL.20). (A risky shape's cells all read
530 /// as it does in a grid that loaded, every other being refused; the
531 /// shape is evaluated cell by cell all the same.)
532 fn keep(&mut self, run: Run) {
533 let Some(shape) = self.shapes.get(run.shape) else {
534 return;
535 };
536 if run.end - run.start >= RUN && !shape.risky && shape.expr.is_ok() {
537 self.runs.push(run);
538 }
539 }
540
541 /// How many cells of the order are in runs.
542 #[cfg(test)]
543 pub(crate) fn cells_in_runs(&self) -> usize {
544 self.runs.iter().map(|r| r.end - r.start).sum()
545 }
546
547 /// The run a position of the order is in, as its shape and the position
548 /// after its last cell; `None` for a cell evaluated alone.
549 pub(crate) fn run_at(&self, pos: usize) -> Option<(usize, usize)> {
550 let after = self.runs.partition_point(|r| r.start <= pos);
551 let run = self.runs.get(after.checked_sub(1)?)?;
552 (pos < run.end).then_some((run.shape, run.end))
553 }
554
555 /// Cells of one run, a part of it or the whole, evaluated at once
556 /// against the grid as `env` shows it (KERNEL.20, `super::vector`), each
557 /// cell's answer pushed to `out` in order: what [`Shapes::evaluate`]
558 /// answers for each, the environment told each cell as it is computed.
559 pub(crate) fn evaluate_run(
560 &self,
561 env: &dyn Env,
562 library: &Library,
563 shape: usize,
564 cells: &[CellId],
565 lanes: &mut Lanes,
566 out: &mut Vec<Computed>,
567 ) {
568 let s = &self.shapes[shape];
569 // The paths the run takes are noted against its shape, in a test
570 // (KERNEL.30's oracle: the census names each of them).
571 #[cfg(test)]
572 vector::taken::shape(shape);
573 match &s.expr {
574 Ok(e) => {
575 let held = s.holds;
576 vector::evaluate(
577 env, library, e, held, &s.repeats, s.anchor, cells, lanes, out,
578 );
579 }
580 // A run holds a parsed shape; were it not, each cell alone.
581 Err(_) => out.extend(cells.iter().map(|&id| {
582 env.enter(id);
583 self.evaluate(env, library, id)
584 })),
585 }
586 }
587
588 /// The formula cells in the order of evaluation, each after everything
589 /// it reads; the cells of a cycle are not in it.
590 pub fn order(&self) -> &[CellId] {
591 &self.order
592 }
593
594 /// What each shape reads, and where the cells of the order stand
595 /// (KERNEL.20).
596 pub(crate) fn reach(&self) -> &Reach {
597 &self.reach
598 }
599
600 /// Every cycle, each its cells sorted, the cycles by their first cell.
601 pub fn cycles(&self) -> &[Vec<CellId>] {
602 &self.cycles
603 }
604
605 /// How many formula cells are placed.
606 pub fn formulas(&self) -> usize {
607 self.places.len()
608 }
609
610 /// How many shapes the formulas make.
611 pub fn shapes(&self) -> usize {
612 self.shapes.len()
613 }
614
615 /// Whether a cell holds a placed formula.
616 pub fn holds(&self, id: CellId) -> bool {
617 self.places.contains_key(&id)
618 }
619
620 /// A formula cell's place.
621 pub fn place(&self, id: CellId) -> Option<Place> {
622 self.places.get(&id).copied()
623 }
624
625 /// A shape, by its index.
626 pub fn shape(&self, index: usize) -> Option<&Shape> {
627 self.shapes.get(index)
628 }
629
630 /// The plan printed as rows, the census (KERNEL.30): a `(plan ...)` row
631 /// of totals, a `(readers ...)` row for each sheet whose changed cells
632 /// are checked against readers, then a `(shape ...)` row for each shape
633 /// a cell holds, each read named by the path an engine's machine takes
634 /// for it (`super::census`). Every sheet is read as a frame's own, as a
635 /// workbook's are: this is the census of a plan [`Shapes::of`] made over
636 /// a grid with no `.last` twin. A machine's, its twins read as it keeps
637 /// them, is `Machine::census`. Computed when asked from the plan as it
638 /// stands, which keeps nothing for it but where each level ends.
639 pub fn census(&self, model: &Workbook, library: &Library) -> String {
640 super::census::rows(self, model, library, &|_| false)
641 }
642
643 /// Where each level of the order ends, by level (KERNEL.30).
644 pub(crate) fn level_ends(&self) -> &[u32] {
645 &self.level_ends
646 }
647
648 /// Each run of the order as its first position, the position after its
649 /// last cell and its shape, by first position (KERNEL.30).
650 pub(crate) fn run_spans(&self) -> impl Iterator<Item = (usize, usize, usize)> + '_ {
651 self.runs.iter().map(|r| (r.start, r.end, r.shape))
652 }
653
654 /// Every formula cell and its place, in no order (KERNEL.30).
655 pub(crate) fn places(&self) -> impl Iterator<Item = (CellId, Place)> + '_ {
656 self.places.iter().map(|(&id, &p)| (id, p))
657 }
658
659 /// Every shape the plan holds, a shape whose cells a write has since
660 /// replaced among them (KERNEL.30).
661 pub(crate) fn every_shape(&self) -> &[Shape] {
662 &self.shapes
663 }
664
665 /// One formula cell evaluated against the grid as `env` shows it, the
666 /// cell being computed being `env.here()`.
667 pub fn evaluate(&self, env: &dyn Env, library: &Library, id: CellId) -> Computed {
668 let Some(p) = self.places.get(&id) else {
669 return Computed::NotComputed(Reason::Construct(String::new()));
670 };
671 let shape = &self.shapes[p.shape];
672 if let Some(construct) = self.unread.get(&id) {
673 return Computed::NotComputed(Reason::Construct(construct.clone()));
674 }
675 match &shape.expr {
676 Err(construct) => Computed::NotComputed(Reason::Construct(construct.clone())),
677 Ok(e) => {
678 let ctx = Ctx::at(env, library, p.d_row, p.d_col);
679 match ctx.eval(e).and_then(|arg| ctx.one(&arg)) {
680 Ok(v) => Computed::Value(v.settled()),
681 Err(reason) => Computed::NotComputed(reason),
682 }
683 }
684 }
685 }
686
687 /// The first thing that stops the grid from being stepped, in the order
688 /// [`super::Calc::first_refusal`] asks it: a cycle (`calc-cycle`), the
689 /// first cell calling a function the library does not hold
690 /// (`calc-function-not-computed`), the first cell whose text this
691 /// version does not read (`calc-construct-not-read`); with the cell the
692 /// refusal stands on, for its line. `only` limits the second and third
693 /// to the cells a write placed.
694 pub fn first_refusal(
695 &self,
696 model: &Workbook,
697 only: Option<&[CellId]>,
698 ) -> Option<(CellId, Refusal)> {
699 if let Some(cycle) = self.cycles.first() {
700 let cells: Vec<String> = cycle.iter().map(|id| spell_cell(model, *id)).collect();
701 return Some((
702 cycle[0],
703 raise("calc-cycle", &[("cells", &cells.join(", "))]),
704 ));
705 }
706 let mut ids: Vec<CellId> = match only {
707 Some(cells) => cells
708 .iter()
709 .copied()
710 .filter(|id| self.places.contains_key(id))
711 .collect(),
712 None => self.places.keys().copied().collect(),
713 };
714 ids.sort_unstable();
715 for id in &ids {
716 let shape = &self.shapes[self.places[id].shape];
717 if let Some(name) = shape.missing.first() {
718 return Some((
719 *id,
720 raise(
721 "calc-function-not-computed",
722 &[("cell", &spell_cell(model, *id)), ("function", name)],
723 ),
724 ));
725 }
726 }
727 for id in &ids {
728 let p = self.places[id];
729 let shape = &self.shapes[p.shape];
730 let label = match (&shape.expr, self.unread.get(id).cloned()) {
731 (_, Some(unread)) => Some(unread),
732 // The construct as the cell's own text spells it.
733 (Err(construct), None) if p.d_row == 0 && p.d_col == 0 => Some(construct.clone()),
734 (Err(construct), None) => {
735 let moved = refers::shift_a1_references(&shape.text, p.d_row, p.d_col);
736 Some(
737 formula::parse(&moved)
738 .err()
739 .unwrap_or_else(|| construct.clone()),
740 )
741 }
742 (Ok(_), None) => None,
743 };
744 if let Some(label) = label {
745 return Some((
746 *id,
747 formula::unread_refusal(&spell_cell(model, *id), &label),
748 ));
749 }
750 }
751 None
752 }
753}
754
755/// Cells ordered by a key below `keys`, cells of one key in the order they
756/// came in: a counting sort, a pass over the cells and one over the keys,
757/// for the order of a plan (KERNEL.20); and, by key, where its cells end in
758/// that order, which the sort's own counts hold once it is done (KERNEL.30).
759/// A plan's cells are positions of an order a `u32` holds, as its reach's
760/// are.
761fn by_key(cells: &[usize], keys: usize, key: impl Fn(usize) -> usize) -> (Vec<usize>, Vec<u32>) {
762 let mut start = vec![0u32; keys + 1];
763 for &v in cells {
764 start[key(v) + 1] += 1;
765 }
766 for k in 0..keys {
767 start[k + 1] += start[k];
768 }
769 let mut out = vec![0; cells.len()];
770 for &v in cells {
771 let k = key(v);
772 out[start[k] as usize] = v;
773 start[k] += 1;
774 }
775 start.truncate(keys);
776 (out, start)
777}
778
779/// A formula cell's vertex, its position among a plan's sorted cells, as an
780/// edge holds it: a plan's cells are positions a `u32` holds, as its
781/// order's are.
782fn vertex(position: usize) -> u32 {
783 position as u32
784}
785
786/// A plan's formula cells as the vertices of its edges (KERNEL.33): each
787/// cell's position among the plan's sorted cells, and whether each sheet
788/// holds any formula.
789struct Vertices<'a> {
790 model: &'a Workbook,
791 extents: &'a [Extent],
792 index: &'a Positions,
793 holds: &'a [bool],
794}
795
796/// A shape's references as its plan reads them, once a shape (KERNEL.33):
797/// the vertices those every `$` holds name, the same at every cell, sorted
798/// and each once; the references each cell moves, each once; and whether
799/// the parse reads a name, which each cell resolves from its own sheet.
800struct ShapeReads<'e> {
801 fixed: Vec<u32>,
802 moving: Vec<&'e RefExpr>,
803 named: bool,
804}
805
806impl Vertices<'_> {
807 /// The formula cells inside an area, each by its vertex, to `out`: by
808 /// the array where the sheet's formulas stand close enough together to
809 /// be held as one, else by the sheet's tree; none on a sheet holding no
810 /// formula, from which no edge comes.
811 fn formulas_in(&self, a: &Area, out: &mut Vec<u32>) {
812 if !self.holds.get(a.sheet).copied().unwrap_or(false) {
813 return;
814 }
815 let rect = (a.top, a.left, a.bottom, a.right);
816 if self
817 .index
818 .each_in(a.sheet, rect, &mut |v| out.push(vertex(v)))
819 {
820 return;
821 }
822 let sheet = &self.model.sheets[a.sheet];
823 let extent = self.extents.get(a.sheet).copied().flatten();
824 for ((row, col), cell) in cells_in(sheet, extent, a) {
825 if is_formula(&cell.content) {
826 if let Some(v) = self.index.get(a.sheet, row, col) {
827 out.push(vertex(v));
828 }
829 }
830 }
831 }
832
833 /// A shape's references read off its parse once: each once, however
834 /// often the parse repeats it; one to the single sheet its placement
835 /// found, when that sheet holds no formula, left out; one every `$`
836 /// holds there, found now, since it names the same cells at every
837 /// offset and `graph::areas_at` reads a found sheet and not the cell's;
838 /// every other kept for each cell to move.
839 fn reads_of<'e>(&self, shape: &'e Shape) -> ShapeReads<'e> {
840 let mut reads = ShapeReads {
841 fixed: Vec::new(),
842 moving: Vec::new(),
843 named: false,
844 };
845 let Ok(e) = &shape.expr else {
846 return reads;
847 };
848 let mut each: Vec<&RefExpr> = Vec::new();
849 e.each_read(&mut |r| match r {
850 Read::Ref(re) => {
851 if !each.contains(&re) {
852 each.push(re);
853 }
854 }
855 Read::Name { .. } => reads.named = true,
856 });
857 for re in each {
858 let found_once = match (re.found, &re.last_sheet) {
859 (Some((first, last)), None) if first == last => Some(usize::from(first)),
860 _ => None,
861 };
862 if let Some(sheet) = found_once {
863 if !self.holds.get(sheet).copied().unwrap_or(false) {
864 continue;
865 }
866 if every_dollar(re) {
867 if let Ok(areas) = areas_at(self.model, sheet, re, 0, 0) {
868 for a in &areas {
869 self.formulas_in(a, &mut reads.fixed);
870 }
871 }
872 continue;
873 }
874 }
875 reads.moving.push(re);
876 }
877 reads.fixed.sort_unstable();
878 reads.fixed.dedup();
879 reads
880 }
881}
882
883/// Whether every `$` holds a reference, so that it names the same cells at
884/// every offset (KERNEL.33): a cell's two, a range's four, a whole column's
885/// columns, a whole row's rows.
886fn every_dollar(r: &RefExpr) -> bool {
887 let c = &r.corners;
888 match r.shape {
889 refers::Shape::Cell => c.row_abs1 && c.col_abs1,
890 refers::Shape::Range => c.row_abs1 && c.col_abs1 && c.row_abs2 && c.col_abs2,
891 refers::Shape::Column => c.col_abs1 && c.col_abs2,
892 refers::Shape::Row => c.row_abs1 && c.row_abs2,
893 refers::Shape::None => false,
894 }
895}
896
897/// Every reference of a parse that names its sheets, those sheets found once
898/// against the grid it is placed in (KERNEL.20): a machine's sheets, its
899/// twins among them, are fixed for its life, so the index a name finds at
900/// placement is the one it would find at every evaluation. A reference to
901/// the formula's own sheet, and one naming a sheet the grid does not hold,
902/// stay unfound and are found by name at each evaluation, as before. The
903/// walk keeps its own stack, so that a chain of operators costs it no depth.
904fn find_sheets(expr: &mut Expr, model: &Workbook) {
905 let mut stack = vec![expr];
906 while let Some(e) = stack.pop() {
907 match e {
908 Expr::Ref(r) => r.found = sheets_of(model, r),
909 Expr::Call(_, args) => stack.extend(args.iter_mut()),
910 Expr::Neg(x) | Expr::Pos(x) | Expr::Percent(x) => stack.push(x),
911 Expr::Binary(_, a, b) => {
912 stack.push(a);
913 stack.push(b);
914 }
915 Expr::Name { .. }
916 | Expr::Number(_)
917 | Expr::Text(_)
918 | Expr::Bool(_)
919 | Expr::Error(_)
920 | Expr::Empty => {}
921 }
922 }
923}
924
925/// A reference's named sheets as `graph::areas_at` finds them by name: the
926/// first, and the last of a span or the first again; `None` when it names
927/// none, one the grid does not hold, or one past the 256th, which a byte
928/// cannot hold and the name finds as before.
929fn sheets_of(model: &Workbook, r: &RefExpr) -> Option<(u8, u8)> {
930 let first = model.find_sheet(r.sheet.as_deref()?)?;
931 let last = match &r.last_sheet {
932 None => first,
933 Some(name) => model.find_sheet(name)?,
934 };
935 Some((u8::try_from(first).ok()?, u8::try_from(last).ok()?))
936}
937
938/// `Sheet!A1` for a cell, the sheet quoted as a reference quotes it.
939pub fn spell_cell(model: &Workbook, id: CellId) -> String {
940 let sheet = model
941 .sheets
942 .get(id.0)
943 .map(|s| s.name.as_str())
944 .unwrap_or("");
945 format!("{}!{}", quote_sheet(sheet), cell_ref(id.1, id.2))
946}
947
948#[cfg(test)]
949mod tests {
950 use super::*;
951
952 #[test]
953 fn every_dollar_holds_a_reference_whose_every_number_is_held() {
954 // KERNEL.33: a plan finds a reference once for its whole shape only
955 // when every `$` holds it, so that it names the same cells at every
956 // offset: a cell's two numbers, a range's four, a whole column's
957 // columns, a whole row's rows. One unheld number moves it.
958 for (text, held) in [
959 ("$C$2", true),
960 ("$C2", false),
961 ("C$2", false),
962 ("C2", false),
963 ("$C$2:$D$4", true),
964 ("$C$2:D4", false),
965 ("$C$2:$D4", false),
966 ("$C$2:D$4", false),
967 ("C2:$D$4", false),
968 ("$C:$D", true),
969 ("$C:D", false),
970 ("C:D", false),
971 ("$2:$4", true),
972 ("$2:4", false),
973 ("2:4", false),
974 ("S!$C$2:$D$4", true),
975 ("S!C2:$D$4", false),
976 ] {
977 let Ok(Expr::Ref(r)) = formula::parse(text) else {
978 panic!("{text} reads as one reference")
979 };
980 assert_eq!(every_dollar(&r), held, "{text}");
981 }
982 }
983}