use std::collections::HashMap;
use crate::fx::FxHashMap;
use crate::messages::{raise, Refusal};
use crate::refers::{self, quote_sheet};
use crate::sheet::{cell_ref, shared_masters, strip_future_prefixes, Content, Workbook};
use super::eval::{Computed, Ctx, Env, Library, Reason};
use super::formula::{self, Expr, Read, RefExpr};
use super::graph::{
self, areas_at, cells_in, dependencies, is_formula, Area, CellId, Extent, Names,
};
use super::program::{Program, Why};
use super::reach::{Positions, Reach};
use super::vector::{self, Lanes, Repeats};
#[derive(Clone, Debug)]
pub struct Shape {
pub text: String,
pub expr: Result<Expr, String>,
pub anchor: CellId,
missing: Vec<String>,
pub(crate) risky: bool,
holds: (usize, usize),
repeats: Repeats,
fill: Option<u32>,
lowered: Option<Result<Program, Why>>,
}
impl Shape {
#[cfg(test)]
pub(crate) fn holds(&self) -> (usize, usize) {
self.holds
}
pub(crate) fn missing(&self) -> &[String] {
&self.missing
}
pub(crate) fn unlowered(&self) -> Option<Why> {
match &self.lowered {
Some(Err(why)) => Some(*why),
_ => None,
}
}
pub fn unread_at(&self, d_row: i64, d_col: i64) -> Option<String> {
if !self.risky || (d_row == 0 && d_col == 0) || self.expr.is_err() {
return None;
}
formula::parse(&refers::shift_a1_references(&self.text, d_row, d_col)).err()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Place {
pub shape: usize,
pub d_row: i64,
pub d_col: i64,
}
#[derive(Clone, Debug, Default)]
pub struct Shapes {
shapes: Vec<Shape>,
by_r1c1: HashMap<String, usize>,
by_shared: HashMap<(usize, u32), usize>,
places: FxHashMap<CellId, Place>,
unread: FxHashMap<CellId, String>,
order: Vec<CellId>,
cycles: Vec<Vec<CellId>>,
runs: Vec<Run>,
reach: Reach,
level_ends: Vec<u32>,
arrays: Vec<bool>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Run {
start: usize,
end: usize,
shape: usize,
}
pub(crate) const RUN: usize = 4;
pub(crate) const SHORT: usize = 16;
impl Shapes {
pub fn new() -> Shapes {
Shapes::default()
}
pub fn of(
model: &Workbook,
sheets: usize,
library: &Library,
names: &Names,
extents: &[Extent],
) -> Shapes {
let mut s: Box<Shapes> = Box::default();
for (si, sheet) in model.sheets.iter().enumerate().take(sheets) {
let masters = shared_masters(sheet);
for (&(row, col), cell) in &sheet.cells {
s.place_with(model, (si, row, col), &cell.content, &masters, library);
}
}
s.plan(model, names, extents);
*s
}
pub fn replace(&mut self, model: &Workbook, id: CellId, library: &Library) {
self.places.remove(&id);
self.unread.remove(&id);
let Some(sheet) = model.sheets.get(id.0) else {
return;
};
let Some(cell) = sheet.cells.get(&(id.1, id.2)) else {
return;
};
if !is_formula(&cell.content) {
return;
}
let masters = shared_masters(sheet);
self.place_with(model, id, &cell.content, &masters, library);
}
pub(crate) fn replace_many(&mut self, model: &Workbook, ids: &[CellId], library: &Library) {
for id in ids {
self.places.remove(id);
self.unread.remove(id);
}
let mut sheets: Vec<usize> = ids.iter().map(|id| id.0).collect();
sheets.sort_unstable();
sheets.dedup();
for s in sheets {
let Some(sheet) = model.sheets.get(s) else {
continue;
};
let masters = shared_masters(sheet);
for &id in ids.iter().filter(|id| id.0 == s) {
let Some(cell) = sheet.cells.get(&(id.1, id.2)) else {
continue;
};
if is_formula(&cell.content) {
self.place_with(model, id, &cell.content, &masters, library);
}
}
}
self.as_loaded(model, library);
}
fn as_loaded(&mut self, model: &Workbook, library: &Library) {
let mut first: Vec<Option<CellId>> = vec![None; self.shapes.len()];
for (&id, p) in &self.places {
match first[p.shape] {
Some(f) if f <= id => {}
_ => first[p.shape] = Some(id),
}
}
let mut order: Vec<usize> = (0..self.shapes.len())
.filter(|&s| first[s].is_some())
.collect();
order.sort_unstable_by_key(|&s| first[s]);
let mut masters: HashMap<usize, HashMap<u32, (&str, u32, u32)>> = HashMap::new();
let mut again: Vec<(usize, CellId, String)> = Vec::new();
for &s in &order {
let f = first[s].expect("a shape in the order has a first cell");
let shape = &self.shapes[s];
let (anchor, raw) = match shape.fill {
Some(index) => {
let sheet = shape.anchor.0;
let found = masters
.entry(sheet)
.or_insert_with(|| shared_masters(&model.sheets[sheet]))
.get(&index);
match found {
Some(&(text, row, col)) => ((sheet, row, col), text),
None => continue,
}
}
None => match model.sheets[f.0].cells.get(&(f.1, f.2)).map(|c| &c.content) {
Some(Content::Formula(t) | Content::DynamicFormula(t)) => (f, t.as_str()),
_ => continue,
},
};
if anchor != shape.anchor
|| (raw != shape.text && strip_future_prefixes(raw) != shape.text)
{
again.push((s, anchor, strip_future_prefixes(raw)));
}
}
let numbered = order.iter().enumerate().all(|(k, &s)| k == s);
if again.is_empty() && numbered && order.len() == self.shapes.len() {
return;
}
let mut renewed: HashMap<usize, Shape> = HashMap::new();
for (s, anchor, text) in again {
let fill = self.shapes[s].fill;
self.add(model, text, anchor, library);
let mut shape = self.shapes.pop().expect("the shape just made");
shape.fill = fill;
renewed.insert(s, shape);
}
let mut number: Vec<Option<usize>> = vec![None; self.shapes.len()];
let mut moved: Vec<bool> = Vec::with_capacity(order.len());
let mut old: Vec<Option<Shape>> = std::mem::take(&mut self.shapes)
.into_iter()
.map(Some)
.collect();
for (k, &s) in order.iter().enumerate() {
number[s] = Some(k);
moved.push(renewed.contains_key(&s));
let shape = renewed
.remove(&s)
.or_else(|| old[s].take())
.expect("each shape once");
self.shapes.push(shape);
}
let shapes = &self.shapes;
for (id, p) in self.places.iter_mut() {
p.shape = number[p.shape].expect("a placed cell's shape is kept");
if !moved[p.shape] {
continue;
}
let shape = &shapes[p.shape];
p.d_row = i64::from(id.1) - i64::from(shape.anchor.1);
p.d_col = i64::from(id.2) - i64::from(shape.anchor.2);
match shape.unread_at(p.d_row, p.d_col) {
Some(construct) => self.unread.insert(*id, construct),
None => self.unread.remove(id),
};
}
self.by_shared.retain(|_, s| number[*s].is_some());
self.by_r1c1.retain(|_, s| number[*s].is_some());
for s in self.by_shared.values_mut().chain(self.by_r1c1.values_mut()) {
*s = number[*s].expect("kept");
}
}
fn place_with(
&mut self,
model: &Workbook,
id: CellId,
content: &Content,
masters: &HashMap<u32, (&str, u32, u32)>,
library: &Library,
) {
let (si, row, col) = id;
let (shape, anchor) = match content {
Content::SharedMaster { si: index, .. } | Content::SharedChild { si: index } => {
let Some(&(text, m_row, m_col)) = masters.get(index) else {
return;
};
let anchor = (si, m_row, m_col);
let key = (si, *index);
let shape = match self.by_shared.get(&key) {
Some(&s) => s,
None => {
let s = self.add(model, strip_future_prefixes(text), anchor, library);
self.shapes[s].fill = Some(*index);
self.by_shared.insert(key, s);
s
}
};
(shape, anchor)
}
Content::Formula(t) | Content::DynamicFormula(t) => {
let text = strip_future_prefixes(t);
let key = refers::r1c1(&text, row, col);
match self.by_r1c1.get(&key) {
Some(&s) => (s, self.shapes[s].anchor),
None => {
let s = self.add(model, text, id, library);
self.by_r1c1.insert(key, s);
(s, id)
}
}
}
_ => return,
};
let (d_row, d_col) = (
i64::from(row) - i64::from(anchor.1),
i64::from(col) - i64::from(anchor.2),
);
self.places.insert(
id,
Place {
shape,
d_row,
d_col,
},
);
match self.shapes[shape].unread_at(d_row, d_col) {
Some(construct) => self.unread.insert(id, construct),
None => self.unread.remove(&id),
};
}
fn add(&mut self, model: &Workbook, text: String, anchor: CellId, library: &Library) -> usize {
let mut expr = formula::parse(&text);
if let Ok(e) = expr.as_mut() {
find_sheets(e, model);
}
let mut missing: Vec<String> = formula::calls(&text)
.into_iter()
.filter(|name| !library.has(name))
.collect();
let risky = refers::scan(&text).iter().any(|r| {
r.kind == refers::Kind::ThreeD
|| (r.kind == refers::Kind::Broken && r.shape != refers::Shape::None)
});
missing.sort_unstable();
missing.dedup();
self.push(text, expr, anchor, missing, risky, library)
}
#[inline(never)]
fn push(
&mut self,
text: String,
expr: Result<Expr, String>,
anchor: CellId,
missing: Vec<String>,
risky: bool,
library: &Library,
) -> usize {
let (holds, repeats) = match &expr {
Ok(e) => {
let repeats = Repeats::of(library, e);
let (values, positions) = vector::holds(library, e);
((values + repeats.parts(), positions), repeats)
}
Err(_) => ((0, 0), Repeats::default()),
};
let lowered = match &expr {
Ok(e) if !risky => Some(Program::lower(library, e)),
_ => None,
};
self.shapes.push(Shape {
text,
expr,
anchor,
missing,
risky,
holds,
repeats,
fill: None,
lowered,
});
self.shapes.len() - 1
}
pub fn plan(&mut self, model: &Workbook, names: &Names, extents: &[Extent]) {
let mut ids: Vec<CellId> = self.places.keys().copied().collect();
ids.sort_unstable();
let index = Positions::of(&ids, model.sheets.len());
let mut holds = vec![false; model.sheets.len()];
for id in &ids {
holds[id.0] = true;
}
let vertices = Vertices {
model,
extents,
index: &index,
holds: &holds,
};
let reads: Vec<ShapeReads> = self
.shapes
.iter()
.map(|shape| vertices.reads_of(shape))
.collect();
let n = ids.len();
let mut offsets: Vec<usize> = Vec::with_capacity(n + 1);
let mut targets: Vec<u32> = Vec::with_capacity(n * 8);
offsets.push(0);
let mut shape_of: Vec<usize> = Vec::with_capacity(n);
let mut deps: Vec<CellId> = Vec::new();
let mut edges: Vec<u32> = Vec::new();
for id in &ids {
edges.clear();
deps.clear();
let p = self.places[id];
shape_of.push(p.shape);
let shape = &self.shapes[p.shape];
let read = !self.unread.contains_key(id);
match &shape.expr {
Ok(e) if read => {
let r = &reads[p.shape];
edges.extend_from_slice(&r.fixed);
for re in &r.moving {
let Ok(areas) = areas_at(model, id.0, re, p.d_row, p.d_col) else {
continue;
};
for a in &areas {
vertices.formulas_in(a, &mut edges);
}
}
if r.named {
e.each_read(&mut |r| {
if let Read::Name { name, sheet } = r {
let scope = sheet.and_then(|s| model.find_sheet(s));
if let Some(t) = names.resolve(name, scope, id.0) {
dependencies(
model,
names,
extents,
scope.unwrap_or(id.0),
t,
&mut deps,
);
}
}
});
}
}
_ => {
let moved = refers::shift_a1_references(&shape.text, p.d_row, p.d_col);
dependencies(model, names, extents, id.0, &moved, &mut deps);
}
}
for d in &deps {
if let Some(v) = index.get(d.0, d.1, d.2) {
edges.push(vertex(v));
}
}
edges.sort_unstable();
edges.dedup();
targets.extend_from_slice(&edges);
offsets.push(targets.len());
}
let (members, starts) = graph::tarjan_flat(&offsets, &targets);
let shape_at =
self.order_and_runs(&ids, (&offsets, &targets), &shape_of, (&members, &starts));
self.arrays = read_as_arrays(model, &self.shapes, (&ids, &shape_of), &self.runs);
let places = &self.places;
let formula = |id: CellId| places.contains_key(&id);
self.reach = Reach::of(model, &self.shapes, &self.order, shape_at, &formula);
}
fn order_and_runs(
&mut self,
ids: &[CellId],
(offsets, targets): (&[usize], &[u32]),
shape_of: &[usize],
(members, starts): (&[u32], &[usize]),
) -> Vec<u32> {
self.order.clear();
self.cycles.clear();
self.runs.clear();
let adj = |v: usize| &targets[offsets[v]..offsets[v + 1]];
let mut component = vec![0; ids.len()];
let mut level = vec![0; ids.len()];
let mut alone = vec![false; ids.len()];
for (c, ends) in starts.windows(2).enumerate() {
let comp = &members[ends[0]..ends[1]];
for &v in comp {
component[v as usize] = c;
}
let deepest = comp
.iter()
.flat_map(|&v| adj(v as usize))
.filter(|&&u| component[u as usize] != c)
.map(|&u| level[u as usize] + 1)
.max()
.unwrap_or(0);
for &v in comp {
level[v as usize] = deepest;
}
if comp.len() == 1 && !adj(comp[0] as usize).contains(&comp[0]) {
alone[comp[0] as usize] = true;
} else {
let mut cells: Vec<CellId> = comp.iter().map(|&i| ids[i as usize]).collect();
cells.sort_unstable();
self.cycles.push(cells);
}
}
self.cycles.sort();
let cells: Vec<usize> = (0..ids.len()).filter(|&v| alone[v]).collect();
let (cells, _) = by_key(&cells, self.shapes.len(), |v| shape_of[v]);
let levels = level.iter().max().map_or(0, |deepest| deepest + 1);
let (order, ends) = by_key(&cells, levels, |v| level[v]);
self.level_ends = ends;
let mut joined = vec![usize::MAX; ids.len()];
let mut run = Run {
start: 0,
end: 0,
shape: usize::MAX,
};
let mut shape_at = Vec::with_capacity(order.len());
for v in order {
let (id, shape) = (ids[v], shape_of[v]);
let pos = self.order.len();
let joins = shape == run.shape
&& id.0 == self.order[run.start].0
&& adj(v).iter().all(|&u| joined[u as usize] != run.start);
if !joins {
self.keep(run);
run = Run {
start: pos,
end: pos,
shape,
};
}
joined[v] = run.start;
run.end = pos + 1;
self.order.push(id);
shape_at.push(shape as u32);
}
self.keep(run);
shape_at
}
fn keep(&mut self, run: Run) {
let Some(shape) = self.shapes.get(run.shape) else {
return;
};
if run.end - run.start >= RUN && !shape.risky && shape.expr.is_ok() {
self.runs.push(run);
}
}
#[cfg(test)]
pub(crate) fn cells_in_runs(&self) -> usize {
self.runs.iter().map(|r| r.end - r.start).sum()
}
#[cfg(test)]
pub(crate) fn spelled(&self) -> String {
use std::fmt::Write;
let mut out = String::new();
for (k, s) in self.shapes.iter().enumerate() {
let _ = writeln!(
out,
"shape {k} {:?} {:?} {:?} {:?} {:?} {:?} {:?} {:?} {:?}",
s.anchor, s.fill, s.text, s.expr, s.missing, s.risky, s.holds, s.repeats, s.lowered
);
}
let mut places: Vec<(&CellId, &Place)> = self.places.iter().collect();
places.sort_by_key(|(id, _)| **id);
let mut unread: Vec<(&CellId, &String)> = self.unread.iter().collect();
unread.sort();
let mut shared: Vec<(&(usize, u32), &usize)> = self.by_shared.iter().collect();
shared.sort();
let mut r1c1: Vec<(&String, &usize)> = self.by_r1c1.iter().collect();
r1c1.sort();
let _ = writeln!(out, "places {places:?}\nunread {unread:?}");
let _ = writeln!(out, "by shared {shared:?}\nby r1c1 {r1c1:?}");
let _ = writeln!(
out,
"order {:?}\ncycles {:?}\nruns {:?}\nlevels {:?}\narrays {:?}",
self.order, self.cycles, self.runs, self.level_ends, self.arrays
);
out.push_str(&self.reach.spelled());
out
}
pub(crate) fn read_as_array(&self, sheet: usize) -> bool {
self.arrays.get(sheet).is_some_and(|&b| b)
}
pub(crate) fn keeps_array(&self, model: &Workbook, sheet: usize) -> bool {
self.read_as_array(sheet)
&& model.sheets.get(sheet).is_some_and(|s| {
let most = crate::machine::CELLS as u64;
super::dense::fits(s.extent(), super::dense::limit(s.cells.len(), most))
})
}
pub(crate) fn run_after(&self, pos: usize) -> usize {
self.runs.partition_point(|r| r.end <= pos)
}
#[inline]
pub(crate) fn run_span(&self, k: usize) -> Option<(usize, usize, usize)> {
self.runs.get(k).map(|r| (r.start, r.end, r.shape))
}
#[inline]
pub(crate) fn program(&self, index: usize) -> Option<(&Program, CellId)> {
let s = self.shapes.get(index)?;
match &s.lowered {
Some(Ok(p)) => Some((p, s.anchor)),
_ => None,
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn evaluate_run(
&self,
env: &dyn Env,
library: &Library,
shape: usize,
cells: &[CellId],
lanes: &mut Lanes,
out: &mut Vec<Computed>,
numbers: &mut Vec<f64>,
) {
let s = &self.shapes[shape];
#[cfg(test)]
vector::taken::shape(shape);
match &s.expr {
Ok(e) => {
let held = s.holds;
vector::evaluate(
env, library, e, held, &s.repeats, s.anchor, cells, lanes, out, numbers,
);
}
Err(_) => out.extend(cells.iter().map(|&id| {
env.enter(id);
self.evaluate(env, library, id)
})),
}
}
pub fn order(&self) -> &[CellId] {
&self.order
}
pub(crate) fn reach(&self) -> &Reach {
&self.reach
}
pub fn cycles(&self) -> &[Vec<CellId>] {
&self.cycles
}
pub fn formulas(&self) -> usize {
self.places.len()
}
pub fn shapes(&self) -> usize {
self.shapes.len()
}
pub fn holds(&self, id: CellId) -> bool {
self.places.contains_key(&id)
}
pub fn place(&self, id: CellId) -> Option<Place> {
self.places.get(&id).copied()
}
pub fn shape(&self, index: usize) -> Option<&Shape> {
self.shapes.get(index)
}
pub fn census(&self, model: &Workbook, library: &Library) -> String {
super::census::rows(self, model, library, &|s| self.keeps_array(model, s))
}
pub(crate) fn level_ends(&self) -> &[u32] {
&self.level_ends
}
pub(crate) fn run_spans(&self) -> impl Iterator<Item = (usize, usize, usize)> + '_ {
self.runs.iter().map(|r| (r.start, r.end, r.shape))
}
pub(crate) fn places(&self) -> impl Iterator<Item = (CellId, Place)> + '_ {
self.places.iter().map(|(&id, &p)| (id, p))
}
pub(crate) fn every_shape(&self) -> &[Shape] {
&self.shapes
}
pub fn evaluate(&self, env: &dyn Env, library: &Library, id: CellId) -> Computed {
let Some(p) = self.places.get(&id) else {
return Computed::NotComputed(Reason::Construct(String::new()));
};
let shape = &self.shapes[p.shape];
if let Some(construct) = self.unread.get(&id) {
return Computed::NotComputed(Reason::Construct(construct.clone()));
}
match &shape.expr {
Err(construct) => Computed::NotComputed(Reason::Construct(construct.clone())),
Ok(e) => {
let ctx = Ctx::at(env, library, p.d_row, p.d_col);
match ctx.eval(e).and_then(|arg| ctx.one(&arg)) {
Ok(v) => Computed::Value(v.settled()),
Err(reason) => Computed::NotComputed(reason),
}
}
}
}
pub fn first_refusal(
&self,
model: &Workbook,
only: Option<&[CellId]>,
) -> Option<(CellId, Refusal)> {
if let Some(cycle) = self.cycles.first() {
let cells: Vec<String> = cycle.iter().map(|id| spell_cell(model, *id)).collect();
return Some((
cycle[0],
raise("calc-cycle", &[("cells", &cells.join(", "))]),
));
}
let mut ids: Vec<CellId> = match only {
Some(cells) => cells
.iter()
.copied()
.filter(|id| self.places.contains_key(id))
.collect(),
None => self.places.keys().copied().collect(),
};
let refused = |id: &CellId| {
let shape = &self.shapes[self.places[id].shape];
!shape.missing.is_empty() || shape.expr.is_err() || self.unread.contains_key(id)
};
if !ids.iter().any(refused) {
return None;
}
ids.sort_unstable();
for id in &ids {
let shape = &self.shapes[self.places[id].shape];
if let Some(name) = shape.missing.first() {
return Some((
*id,
raise(
"calc-function-not-computed",
&[("cell", &spell_cell(model, *id)), ("function", name)],
),
));
}
}
for id in &ids {
let p = self.places[id];
let shape = &self.shapes[p.shape];
let label = match (&shape.expr, self.unread.get(id).cloned()) {
(_, Some(unread)) => Some(unread),
(Err(construct), None) if p.d_row == 0 && p.d_col == 0 => Some(construct.clone()),
(Err(construct), None) => {
let moved = refers::shift_a1_references(&shape.text, p.d_row, p.d_col);
Some(
formula::parse(&moved)
.err()
.unwrap_or_else(|| construct.clone()),
)
}
(Ok(_), None) => None,
};
if let Some(label) = label {
return Some((
*id,
formula::unread_refusal(&spell_cell(model, *id), &label),
));
}
}
None
}
}
fn by_key(cells: &[usize], keys: usize, key: impl Fn(usize) -> usize) -> (Vec<usize>, Vec<u32>) {
let mut start = vec![0u32; keys + 1];
for &v in cells {
start[key(v) + 1] += 1;
}
for k in 0..keys {
start[k + 1] += start[k];
}
let mut out = vec![0; cells.len()];
for &v in cells {
let k = key(v);
out[start[k] as usize] = v;
start[k] += 1;
}
start.truncate(keys);
(out, start)
}
fn read_as_arrays(
model: &Workbook,
shapes: &[Shape],
(ids, shape_of): (&[CellId], &[usize]),
runs: &[Run],
) -> Vec<bool> {
let mut with_runs = vec![false; shapes.len()];
for r in runs {
with_runs[r.shape] = true;
}
let mut read = vec![false; model.sheets.len()];
let mut own = vec![false; shapes.len()];
for (k, shape) in shapes.iter().enumerate() {
let Ok(e) = &shape.expr else {
continue;
};
e.each_read(&mut |r| {
let Read::Ref(r) = r else {
return;
};
if r.last_sheet.is_some() {
return;
}
let held = r.corners.row_abs1 && r.corners.col_abs1;
let range = !matches!(r.shape, refers::Shape::Cell);
if !range && (held || !with_runs[k]) {
return;
}
let mut mark = |s: usize| {
if let Some(b) = read.get_mut(s) {
*b = true;
}
};
match (r.found, &r.sheet) {
(Some((first, _)), _) => mark(usize::from(first)),
(None, None) => own[k] = true,
(None, Some(name)) => {
if let Some(s) = model.find_sheet(name) {
mark(s);
}
}
}
});
}
for (id, &k) in ids.iter().zip(shape_of) {
if own[k] {
read[id.0] = true;
}
}
read
}
fn vertex(position: usize) -> u32 {
position as u32
}
struct Vertices<'a> {
model: &'a Workbook,
extents: &'a [Extent],
index: &'a Positions,
holds: &'a [bool],
}
struct ShapeReads<'e> {
fixed: Vec<u32>,
moving: Vec<&'e RefExpr>,
named: bool,
}
impl Vertices<'_> {
fn formulas_in(&self, a: &Area, out: &mut Vec<u32>) {
if !self.holds.get(a.sheet).copied().unwrap_or(false) {
return;
}
let rect = (a.top, a.left, a.bottom, a.right);
if self
.index
.each_in(a.sheet, rect, &mut |v| out.push(vertex(v)))
{
return;
}
let sheet = &self.model.sheets[a.sheet];
let extent = self.extents.get(a.sheet).copied().flatten();
for ((row, col), cell) in cells_in(sheet, extent, a) {
if is_formula(&cell.content) {
if let Some(v) = self.index.get(a.sheet, row, col) {
out.push(vertex(v));
}
}
}
}
fn reads_of<'e>(&self, shape: &'e Shape) -> ShapeReads<'e> {
let mut reads = ShapeReads {
fixed: Vec::new(),
moving: Vec::new(),
named: false,
};
let Ok(e) = &shape.expr else {
return reads;
};
let mut each: Vec<&RefExpr> = Vec::new();
e.each_read(&mut |r| match r {
Read::Ref(re) => {
if !each.contains(&re) {
each.push(re);
}
}
Read::Name { .. } => reads.named = true,
});
for re in each {
let found_once = match (re.found, &re.last_sheet) {
(Some((first, last)), None) if first == last => Some(usize::from(first)),
_ => None,
};
if let Some(sheet) = found_once {
if !self.holds.get(sheet).copied().unwrap_or(false) {
continue;
}
if every_dollar(re) {
if let Ok(areas) = areas_at(self.model, sheet, re, 0, 0) {
for a in &areas {
self.formulas_in(a, &mut reads.fixed);
}
}
continue;
}
}
reads.moving.push(re);
}
reads.fixed.sort_unstable();
reads.fixed.dedup();
reads
}
}
fn every_dollar(r: &RefExpr) -> bool {
let c = &r.corners;
match r.shape {
refers::Shape::Cell => c.row_abs1 && c.col_abs1,
refers::Shape::Range => c.row_abs1 && c.col_abs1 && c.row_abs2 && c.col_abs2,
refers::Shape::Column => c.col_abs1 && c.col_abs2,
refers::Shape::Row => c.row_abs1 && c.row_abs2,
refers::Shape::None => false,
}
}
fn find_sheets(expr: &mut Expr, model: &Workbook) {
let mut stack = vec![expr];
while let Some(e) = stack.pop() {
match e {
Expr::Ref(r) => r.found = sheets_of(model, r),
Expr::Call(_, args) => stack.extend(args.iter_mut()),
Expr::Neg(x) | Expr::Pos(x) | Expr::Percent(x) => stack.push(x),
Expr::Binary(_, a, b) => {
stack.push(a);
stack.push(b);
}
Expr::Name { .. }
| Expr::Number(_)
| Expr::Text(_)
| Expr::Bool(_)
| Expr::Error(_)
| Expr::Empty => {}
}
}
}
fn sheets_of(model: &Workbook, r: &RefExpr) -> Option<(u8, u8)> {
let first = model.find_sheet(r.sheet.as_deref()?)?;
let last = match &r.last_sheet {
None => first,
Some(name) => model.find_sheet(name)?,
};
Some((u8::try_from(first).ok()?, u8::try_from(last).ok()?))
}
pub fn spell_cell(model: &Workbook, id: CellId) -> String {
let sheet = model
.sheets
.get(id.0)
.map(|s| s.name.as_str())
.unwrap_or("");
format!("{}!{}", quote_sheet(sheet), cell_ref(id.1, id.2))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_dollar_holds_a_reference_whose_every_number_is_held() {
for (text, held) in [
("$C$2", true),
("$C2", false),
("C$2", false),
("C2", false),
("$C$2:$D$4", true),
("$C$2:D4", false),
("$C$2:$D4", false),
("$C$2:D$4", false),
("C2:$D$4", false),
("$C:$D", true),
("$C:D", false),
("C:D", false),
("$2:$4", true),
("$2:4", false),
("2:4", false),
("S!$C$2:$D$4", true),
("S!C2:$D$4", false),
] {
let Ok(Expr::Ref(r)) = formula::parse(text) else {
panic!("{text} reads as one reference")
};
assert_eq!(every_dollar(&r), held, "{text}");
}
}
}