use std::collections::btree_map::Range;
use std::collections::HashMap;
use crate::intrinsics::fold;
use crate::refers::{self, Kind};
use crate::sheet::{Cell, Content, Sheet, Workbook};
use super::formula::{ref_expr, RefExpr};
use super::value::ErrorKind;
pub type CellId = (usize, u32, u32);
pub type Extent = Option<((u32, u32), (u32, u32))>;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Area {
pub sheet: usize,
pub top: u32,
pub left: u32,
pub bottom: u32,
pub right: u32,
}
impl Area {
pub fn is_cell(&self) -> bool {
self.top == self.bottom && self.left == self.right
}
}
#[derive(Clone, Debug)]
pub struct Areas(Held);
#[derive(Clone, Debug)]
enum Held {
One(Area),
Span(Vec<Area>),
}
impl Areas {
pub fn one(area: Area) -> Areas {
Areas(Held::One(area))
}
}
impl From<Vec<Area>> for Areas {
fn from(areas: Vec<Area>) -> Areas {
match areas.as_slice() {
[area] => Areas::one(*area),
_ => Areas(Held::Span(areas)),
}
}
}
impl std::ops::Deref for Areas {
type Target = [Area];
fn deref(&self) -> &[Area] {
match &self.0 {
Held::One(area) => std::slice::from_ref(area),
Held::Span(areas) => areas,
}
}
}
impl<'a> IntoIterator for &'a Areas {
type Item = &'a Area;
type IntoIter = std::slice::Iter<'a, Area>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl PartialEq for Areas {
fn eq(&self, other: &Areas) -> bool {
**self == **other
}
}
impl Eq for Areas {}
pub struct Names {
entries: Vec<NameEntry>,
}
struct NameEntry {
key: String,
scope: Option<usize>,
text: String,
}
impl Names {
pub fn of(model: &Workbook) -> Names {
let entries = model
.defined_names
.iter()
.map(|(printed, text)| {
let (scope, bare) = split_scope(model, printed);
NameEntry {
key: fold(bare),
scope,
text: text.clone(),
}
})
.collect();
Names { entries }
}
pub fn resolve(&self, name: &str, scope: Option<usize>, home: usize) -> Option<&str> {
let key = fold(name);
let scoped = |s: usize| {
self.entries
.iter()
.find(|e| e.key == key && e.scope == Some(s))
};
let global = || {
self.entries
.iter()
.find(|e| e.key == key && e.scope.is_none())
};
let entry = match scope {
Some(s) => scoped(s).or_else(global),
None => scoped(home).or_else(global),
};
entry.map(|e| e.text.as_str())
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
fn split_scope<'a>(model: &Workbook, printed: &'a str) -> (Option<usize>, &'a str) {
let Some(bang) = printed.rfind('!') else {
return (None, printed);
};
let (sheet_part, bare) = (&printed[..bang], &printed[bang + 1..]);
let sheet =
if sheet_part.len() >= 2 && sheet_part.starts_with('\'') && sheet_part.ends_with('\'') {
sheet_part[1..sheet_part.len() - 1].replace("''", "'")
} else {
sheet_part.to_string()
};
(model.find_sheet(&sheet), bare)
}
pub fn areas_of(model: &Workbook, home: usize, r: &RefExpr) -> Result<Areas, ErrorKind> {
areas_at(model, home, r, 0, 0)
}
pub fn areas_at(
model: &Workbook,
home: usize,
r: &RefExpr,
d_row: i64,
d_col: i64,
) -> Result<Areas, ErrorKind> {
let (first, last) = match r.found {
Some((first, last)) => (usize::from(first), usize::from(last)),
None => {
let first = match &r.sheet {
None => home,
Some(name) => model.find_sheet(name).ok_or(ErrorKind::Ref)?,
};
let last = match &r.last_sheet {
None => first,
Some(name) => model.find_sheet(name).ok_or(ErrorKind::Ref)?,
};
(first, last)
}
};
let (top, left, bottom, right) = r.moved(d_row, d_col).ok_or(ErrorKind::Ref)?;
let area = |sheet| Area {
sheet,
top,
left,
bottom,
right,
};
if first == last {
return Ok(Areas::one(area(first)));
}
let (a, b) = (first.min(last), first.max(last));
Ok(Areas::from((a..=b).map(area).collect::<Vec<Area>>()))
}
pub fn cells_in<'a>(
sheet: &'a Sheet,
extent: Extent,
area: &Area,
) -> impl Iterator<Item = ((u32, u32), &'a Cell)> + 'a {
let Some(((e_top, e_left), (e_bottom, e_right))) = extent else {
return Walk::Done;
};
let top = area.top.max(e_top);
let bottom = area.bottom.min(e_bottom);
let left = area.left.max(e_left);
let right = area.right.min(e_right);
if top > bottom || left > right {
return Walk::Done;
}
let rows = usize::try_from(bottom - top + 1).unwrap_or(usize::MAX);
if rows > sheet.cells.len() {
return Walk::Sparse {
cells: sheet.cells.range((top, 0)..=(bottom, u32::MAX)),
left,
right,
};
}
Walk::Rows {
sheet,
row: top,
bottom,
left,
right,
cells: sheet.cells.range((top, left)..=(top, right)),
}
}
enum Walk<'a> {
Done,
Sparse {
cells: Range<'a, (u32, u32), Cell>,
left: u32,
right: u32,
},
Rows {
sheet: &'a Sheet,
row: u32,
bottom: u32,
left: u32,
right: u32,
cells: Range<'a, (u32, u32), Cell>,
},
}
impl<'a> Iterator for Walk<'a> {
type Item = ((u32, u32), &'a Cell);
fn next(&mut self) -> Option<Self::Item> {
match self {
Walk::Done => None,
Walk::Sparse { cells, left, right } => {
let (left, right) = (*left, *right);
cells
.find(|((_, col), _)| *col >= left && *col <= right)
.map(|(k, c)| (*k, c))
}
Walk::Rows {
sheet,
row,
bottom,
left,
right,
cells,
} => loop {
if let Some((k, c)) = cells.next() {
return Some((*k, c));
}
if *row >= *bottom {
return None;
}
*row += 1;
*cells = sheet.cells.range((*row, *left)..=(*row, *right));
},
}
}
}
pub fn is_formula(content: &Content) -> bool {
matches!(
content,
Content::Formula(_)
| Content::SharedMaster { .. }
| Content::SharedChild { .. }
| Content::DynamicFormula(_)
)
}
const NAME_DEPTH: u32 = 8;
pub fn dependencies(
model: &Workbook,
names: &Names,
extents: &[Extent],
home: usize,
text: &str,
out: &mut Vec<CellId>,
) {
collect(model, names, extents, home, text, 0, out);
}
fn collect(
model: &Workbook,
names: &Names,
extents: &[Extent],
home: usize,
text: &str,
depth: u32,
out: &mut Vec<CellId>,
) {
for r in refers::scan(text) {
match r.kind {
Kind::Cell | Kind::Range | Kind::Column | Kind::Row | Kind::ThreeD => {
let Some(re) = ref_expr(&r) else { continue };
let Ok(areas) = areas_of(model, home, &re) else {
continue;
};
for a in &areas {
let sheet = &model.sheets[a.sheet];
for ((row, col), cell) in cells_in(sheet, extents[a.sheet], a) {
if is_formula(&cell.content) {
out.push((a.sheet, row, col));
}
}
}
}
Kind::Name if depth < NAME_DEPTH => {
let scope = if r.sheet_name.is_empty() {
None
} else {
model.find_sheet(&r.sheet_name)
};
if let Some(t) = names.resolve(&r.part, scope, home) {
collect(
model,
names,
extents,
scope.unwrap_or(home),
t,
depth + 1,
out,
);
}
}
_ => {}
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Plan {
pub order: Vec<CellId>,
pub cycles: Vec<Vec<CellId>>,
}
impl Plan {
pub fn formulas(&self) -> usize {
self.order.len() + self.cycles.iter().map(Vec::len).sum::<usize>()
}
}
pub fn plan(
model: &Workbook,
names: &Names,
extents: &[Extent],
texts: &HashMap<CellId, String>,
) -> Plan {
let mut ids: Vec<CellId> = texts.keys().copied().collect();
ids.sort_unstable();
let index: HashMap<CellId, usize> = ids.iter().enumerate().map(|(i, id)| (*id, i)).collect();
let n = ids.len();
let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
let mut deps = Vec::new();
for (vi, id) in ids.iter().enumerate() {
deps.clear();
dependencies(model, names, extents, id.0, &texts[id], &mut deps);
let edges = &mut adj[vi];
for d in &deps {
if let Some(&di) = index.get(d) {
edges.push(di);
}
}
edges.sort_unstable();
edges.dedup();
}
let components = tarjan(&adj);
let mut plan = Plan::default();
for comp in components {
if comp.len() == 1 && !adj[comp[0]].contains(&comp[0]) {
plan.order.push(ids[comp[0]]);
} else {
let mut cells: Vec<CellId> = comp.iter().map(|&i| ids[i]).collect();
cells.sort_unstable();
plan.cycles.push(cells);
}
}
plan.cycles.sort();
plan
}
pub(crate) fn tarjan(adj: &[Vec<usize>]) -> Vec<Vec<usize>> {
let mut offsets = Vec::with_capacity(adj.len() + 1);
let mut targets = Vec::new();
offsets.push(0);
for edges in adj {
targets.extend(edges.iter().map(|&w| w as u32));
offsets.push(targets.len());
}
let (members, starts) = tarjan_flat(&offsets, &targets);
starts
.windows(2)
.map(|c| members[c[0]..c[1]].iter().map(|&v| v as usize).collect())
.collect()
}
pub(crate) fn tarjan_flat(offsets: &[usize], targets: &[u32]) -> (Vec<u32>, Vec<usize>) {
const NONE: u32 = u32::MAX;
let n = offsets.len().saturating_sub(1);
let mut index = vec![NONE; n];
let mut low = vec![0u32; n];
let mut on_stack = vec![false; n];
let mut stack: Vec<u32> = Vec::new();
let mut members: Vec<u32> = Vec::with_capacity(n);
let mut starts: Vec<usize> = Vec::with_capacity(n + 1);
starts.push(0);
let mut counter = 0u32;
let mut calls: Vec<(usize, usize)> = Vec::new();
for root in 0..n {
if index[root] != NONE {
continue;
}
calls.push((root, offsets[root]));
index[root] = counter;
low[root] = counter;
counter += 1;
stack.push(root as u32);
on_stack[root] = true;
while let Some(&mut (v, ref mut next)) = calls.last_mut() {
if *next < offsets[v + 1] {
let w = targets[*next] as usize;
*next += 1;
if index[w] == NONE {
index[w] = counter;
low[w] = counter;
counter += 1;
stack.push(w as u32);
on_stack[w] = true;
calls.push((w, offsets[w]));
} else if on_stack[w] {
low[v] = low[v].min(index[w]);
}
continue;
}
calls.pop();
if let Some(&(u, _)) = calls.last() {
low[u] = low[u].min(low[v]);
}
if low[v] == index[v] {
let from = members.len();
while let Some(w) = stack.pop() {
on_stack[w as usize] = false;
members.push(w);
if w as usize == v {
break;
}
}
members[from..].sort_unstable();
starts.push(members.len());
}
}
}
(members, starts)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tarjan_emits_dependencies_before_their_readers() {
let adj = vec![vec![1, 2], vec![2], vec![], vec![4], vec![3], vec![5]];
let comps = tarjan(&adj);
let pos = |x: usize| comps.iter().position(|c| c.contains(&x)).unwrap();
assert!(pos(2) < pos(1) && pos(1) < pos(0));
assert!(comps.contains(&vec![3, 4]));
assert!(comps.contains(&vec![5]));
assert_eq!(comps.len(), 5);
}
#[test]
fn the_flat_walk_finds_every_component_after_all_it_reads() {
let mut seed: u64 = 0x5EED_0033;
let mut below = |n: usize| {
seed = seed
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
((seed >> 33) % n as u64) as usize
};
for case in 0..400 {
let n = 1 + below(14);
let density = 1 + below(5);
let adj: Vec<Vec<usize>> = (0..n)
.map(|_| {
let mut edges: Vec<usize> = (0..below(density * 2)).map(|_| below(n)).collect();
edges.sort_unstable();
edges.dedup();
edges
})
.collect();
let mut reach = vec![vec![false; n]; n];
for (v, row) in reach.iter_mut().enumerate() {
let mut stack = vec![v];
while let Some(u) = stack.pop() {
for &w in &adj[u] {
if !row[w] {
row[w] = true;
stack.push(w);
}
}
}
}
let mut want: Vec<Vec<usize>> = Vec::new();
for (v, from_v) in reach.iter().enumerate() {
if want.iter().any(|c| c.contains(&v)) {
continue;
}
want.push(
(0..n)
.filter(|&u| u == v || (from_v[u] && reach[u][v]))
.collect(),
);
}
let comps = tarjan(&adj);
let mut got = comps.clone();
got.sort();
want.sort();
assert_eq!(got, want, "case {case}: {adj:?}");
let at = |v: usize| comps.iter().position(|c| c.contains(&v)).unwrap();
for (v, edges) in adj.iter().enumerate() {
for &w in edges {
assert!(at(w) <= at(v), "case {case}: {v} reads {w}: {adj:?}");
}
}
}
}
#[test]
fn a_reference_s_areas_are_one_held_in_place_or_a_span_s_in_order() {
use super::super::formula::{parse, Expr};
let mut wb = Workbook::new();
for name in ["Jan", "Feb", "Mar"] {
wb.sheets.push(Sheet::new(name));
}
let areas = |text: &str| {
let Ok(Expr::Ref(r)) = parse(text) else {
panic!("{text}")
};
areas_of(&wb, 0, &r)
};
let rect = |sheet, top, left, bottom, right| Area {
sheet,
top,
left,
bottom,
right,
};
let one = areas("=Feb!B2:C3").unwrap();
assert_eq!(&one[..], &[rect(1, 2, 2, 3, 3)]);
assert_eq!(one, Areas::one(rect(1, 2, 2, 3, 3)));
assert_eq!(one, Areas::from(vec![rect(1, 2, 2, 3, 3)]));
assert_eq!(
&areas("=B2").unwrap()[..],
&[rect(0, 2, 2, 2, 2)],
"the formula's own sheet"
);
let span = areas("=Jan:Mar!B2").unwrap();
let sheets: Vec<usize> = span.iter().map(|a| a.sheet).collect();
assert_eq!(sheets, vec![0, 1, 2]);
assert_eq!(areas("=Mar:Jan!B2").unwrap(), span);
assert_ne!(span, one);
assert_eq!(areas("=Apr!B2"), Err(ErrorKind::Ref));
}
#[test]
fn a_range_s_cells_are_walked_row_by_row_as_they_were_collected() {
use crate::sheet::{MAX_COLUMN, MAX_ROW};
let number = |n: u32| Cell {
content: Content::Number(f64::from(n)),
style: 0,
};
let mut dense = Sheet::new("Dense");
for row in 1..=5u32 {
for col in 1..=4u32 {
if (row + col) % 5 != 0 {
dense.set(row, col, number(row * 10 + col));
}
}
}
let mut sparse = Sheet::new("Sparse");
for (row, col) in [(1, 2), (40, 1), (40, 3), (700, 2), (1000, 4)] {
sparse.set(row, col, number(row));
}
let empty = Sheet::new("Empty");
let rect = |top, left, bottom, right| Area {
sheet: 0,
top,
left,
bottom,
right,
};
let areas = [
rect(2, 2, 4, 3),
rect(1, 1, MAX_ROW, 1),
rect(3, 1, 3, MAX_COLUMN),
rect(1, 1, MAX_ROW, MAX_COLUMN),
rect(40, 2, 700, 3),
rect(6, 1, 9, 9),
rect(2, 5, 4, 9),
rect(1, 2, 1, 2),
];
let mut walked_any = 0;
for sheet in [&dense, &sparse, &empty] {
let extent = sheet.extent();
for a in &areas {
let walked: Vec<(u32, u32)> = cells_in(sheet, extent, a).map(|(k, _)| k).collect();
let want: Vec<(u32, u32)> = match extent {
None => Vec::new(),
Some(((t, l), (b, r))) => sheet
.cells
.keys()
.copied()
.filter(|&(row, col)| {
row >= a.top.max(t)
&& row <= a.bottom.min(b)
&& col >= a.left.max(l)
&& col <= a.right.min(r)
})
.collect(),
};
assert_eq!(walked, want, "{} {a:?}", sheet.name);
walked_any += walked.len();
}
}
assert_eq!(
walked_any, 37,
"28 cells of the dense sheet's, 9 of the sparse one's"
);
}
#[test]
fn a_deep_chain_does_not_overflow() {
let n = 200_000;
let adj: Vec<Vec<usize>> = (0..n)
.map(|i| if i + 1 < n { vec![i + 1] } else { vec![] })
.collect();
let comps = tarjan(&adj);
assert_eq!(comps.len(), n);
assert_eq!(comps[0], vec![n - 1]);
assert_eq!(comps[n - 1], vec![0]);
}
}