use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use icu_casemap::CaseMapperBorrowed;
use truecalc_core::{CellAddr, Ref};
use crate::address::Address;
use crate::casefold::simple_fold;
use crate::named_ref;
use crate::value::Value;
use crate::workbook::Workbook;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CellRef {
pub sheet: String,
pub addr: Address,
}
impl CellRef {
fn new(sheet: String, addr: Address) -> Self {
Self { sheet, addr }
}
pub fn from_display_name(sheet: &str, addr: Address) -> Self {
let folder = CaseMapperBorrowed::new();
Self::new(simple_fold(&folder, sheet), addr)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RangeRef {
pub sheet: String,
pub start: Address,
pub end: Address,
}
impl RangeRef {
pub fn contains(&self, cell: &CellRef) -> bool {
cell.sheet == self.sheet
&& cell.addr.row >= self.start.row
&& cell.addr.row <= self.end.row
&& cell.addr.column >= self.start.column
&& cell.addr.column <= self.end.column
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Precedent {
Cell(CellRef),
Range(RangeRef),
Name(String),
Unresolved(String),
}
#[derive(Debug, Clone, PartialEq)]
pub struct DependencyGraph {
precedents: BTreeMap<CellRef, Vec<Precedent>>,
cell_dependents: HashMap<CellRef, BTreeSet<CellRef>>,
range_dependents: Vec<(RangeRef, BTreeSet<CellRef>)>,
name_dependents: HashMap<String, BTreeSet<CellRef>>,
name_targets: HashMap<String, NameTarget>,
formula_rows: HashMap<String, BTreeMap<u32, Vec<Address>>>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NameTarget {
Cell(CellRef),
Range(RangeRef),
}
impl DependencyGraph {
pub fn build(workbook: &Workbook) -> Self {
let folder = CaseMapperBorrowed::new();
let mut name_targets: HashMap<String, NameTarget> = HashMap::new();
for nr in workbook.names() {
let folded = simple_fold(&folder, &nr.name);
if let Some(target) = resolve_name_ref(&nr.r#ref, &folder, workbook) {
name_targets.insert(folded, target);
}
}
let mut graph = DependencyGraph {
precedents: BTreeMap::new(),
cell_dependents: HashMap::new(),
range_dependents: Vec::new(),
name_dependents: HashMap::new(),
name_targets,
formula_rows: HashMap::new(),
};
let mut range_slots: HashMap<RangeRef, usize> = HashMap::new();
for sheet in workbook.sheets() {
let sheet_folded = simple_fold(&folder, sheet.name());
for (addr, cell) in sheet.iter() {
let Some(formula) = cell.formula() else {
continue;
};
let from = CellRef::new(sheet_folded.clone(), addr);
let refs = match truecalc_core::parse_formula(formula) {
Ok(expr) => truecalc_core::extract_refs(&expr),
Err(_) => {
graph.precedents.insert(
from.clone(),
vec![Precedent::Unresolved(formula.to_owned())],
);
continue;
}
};
let mut seen: HashSet<Precedent> = HashSet::new();
let mut resolved: Vec<Precedent> = Vec::new();
for r in &refs {
let prec = resolve_ref(r, &from.sheet, from.addr, &folder, workbook);
if seen.insert(prec.clone()) {
resolved.push(prec);
}
}
for prec in &resolved {
match prec {
Precedent::Cell(target) => {
graph
.cell_dependents
.entry(target.clone())
.or_default()
.insert(from.clone());
}
Precedent::Range(range) => {
let slot = *range_slots.entry(range.clone()).or_insert_with(|| {
graph
.range_dependents
.push((range.clone(), BTreeSet::new()));
graph.range_dependents.len() - 1
});
graph.range_dependents[slot].1.insert(from.clone());
}
Precedent::Name(name) => {
graph
.name_dependents
.entry(name.clone())
.or_default()
.insert(from.clone());
}
Precedent::Unresolved(_) => {}
}
}
graph.precedents.insert(from, resolved);
}
}
for cell in graph.precedents.keys() {
graph
.formula_rows
.entry(cell.sheet.clone())
.or_default()
.entry(cell.addr.row)
.or_default()
.push(cell.addr);
}
graph
}
pub fn precedents_of(&self, cell: &CellRef) -> Option<&[Precedent]> {
self.precedents.get(cell).map(Vec::as_slice)
}
pub fn is_formula(&self, cell: &CellRef) -> bool {
self.precedents.contains_key(cell)
}
pub fn formula_cells(&self) -> impl Iterator<Item = &CellRef> {
self.precedents.keys()
}
pub fn direct_dependents_of(&self, cell: &CellRef) -> BTreeSet<CellRef> {
let mut out = BTreeSet::new();
if let Some(direct) = self.cell_dependents.get(cell) {
out.extend(direct.iter().cloned());
}
for (range, deps) in &self.range_dependents {
if range.contains(cell) {
out.extend(deps.iter().cloned());
}
}
for (name, target) in &self.name_targets {
let hit = match target {
NameTarget::Cell(c) => c == cell,
NameTarget::Range(r) => r.contains(cell),
};
if hit {
if let Some(deps) = self.name_dependents.get(name) {
out.extend(deps.iter().cloned());
}
}
}
out
}
pub fn name_dependents_of(&self, name: &str) -> BTreeSet<CellRef> {
let folder = CaseMapperBorrowed::new();
let folded = simple_fold(&folder, name);
self.name_dependents
.get(&folded)
.cloned()
.unwrap_or_default()
}
pub fn name_target_of(&self, name: &str) -> Option<NameTarget> {
let folder = CaseMapperBorrowed::new();
let folded = simple_fold(&folder, name);
self.name_targets.get(&folded).cloned()
}
pub fn topological_order(&self) -> Result<Vec<CellRef>, BTreeSet<CellRef>> {
let edges = self.formula_edges();
match edges.topological_order() {
Some(order) => Ok(order),
None => Err(edges.cycle_members()),
}
}
pub fn evaluation_order(&self) -> (Vec<CellRef>, BTreeSet<CellRef>) {
let edges = self.formula_edges();
match edges.topological_order() {
Some(order) => (order, BTreeSet::new()),
None => {
let cycle = edges.cycle_members();
let order = edges.order_excluding(&cycle);
(order, cycle)
}
}
}
pub fn acyclic_order_excluding(&self, cycle: &BTreeSet<CellRef>) -> Vec<CellRef> {
self.formula_edges().order_excluding(cycle)
}
pub fn cycle_cells(&self) -> BTreeSet<CellRef> {
self.formula_edges().cycle_members()
}
fn formula_edges(&self) -> FormulaEdges<'_> {
let nodes: Vec<&CellRef> = self.precedents.keys().collect();
let index_of: HashMap<&CellRef, usize> =
nodes.iter().enumerate().map(|(i, n)| (*n, i)).collect();
let mut succ: Vec<BTreeSet<usize>> = vec![BTreeSet::new(); nodes.len()];
for (i, cell) in nodes.iter().enumerate() {
for prec in &self.precedents[*cell] {
for fp in self.formula_precedent_cells(prec) {
if let Some(&j) = index_of.get(&fp) {
succ[j].insert(i);
}
}
}
}
FormulaEdges { nodes, succ }
}
pub fn formula_precedent_cells(&self, prec: &Precedent) -> Vec<CellRef> {
self.formula_precedent_cells_examined(prec).0
}
#[doc(hidden)]
pub fn formula_precedent_cells_examined(&self, prec: &Precedent) -> (Vec<CellRef>, usize) {
match prec {
Precedent::Cell(c) => {
if self.precedents.contains_key(c) {
(vec![c.clone()], 1)
} else {
(Vec::new(), 1)
}
}
Precedent::Range(r) => self.formula_cells_in_range(r),
Precedent::Name(name) => match self.name_targets.get(name) {
Some(NameTarget::Cell(c)) if self.precedents.contains_key(c) => {
(vec![c.clone()], 1)
}
Some(NameTarget::Cell(_)) => (Vec::new(), 1),
None => (Vec::new(), 0),
Some(NameTarget::Range(r)) => self.formula_cells_in_range(r),
},
Precedent::Unresolved(_) => (Vec::new(), 0),
}
}
fn formula_cells_in_range(&self, range: &RangeRef) -> (Vec<CellRef>, usize) {
let mut out = Vec::new();
let mut examined = 0usize;
if range.start.row > range.end.row || range.start.column > range.end.column {
return (out, examined);
}
let Some(rows) = self.formula_rows.get(&range.sheet) else {
return (out, examined);
};
for addrs in rows.range(range.start.row..=range.end.row).map(|(_, a)| a) {
examined += addrs.len();
out.extend(
addrs
.iter()
.filter(|a| a.column >= range.start.column && a.column <= range.end.column)
.map(|a| CellRef::new(range.sheet.clone(), *a)),
);
}
(out, examined)
}
}
struct FormulaEdges<'a> {
nodes: Vec<&'a CellRef>,
succ: Vec<BTreeSet<usize>>,
}
impl FormulaEdges<'_> {
fn topological_order(&self) -> Option<Vec<CellRef>> {
let mut indeg: Vec<usize> = vec![0; self.nodes.len()];
for deps in &self.succ {
for &i in deps {
indeg[i] += 1;
}
}
let mut ready: BTreeSet<usize> = (0..self.nodes.len()).filter(|&i| indeg[i] == 0).collect();
let mut order: Vec<CellRef> = Vec::with_capacity(self.nodes.len());
while let Some(&node) = ready.iter().next() {
ready.remove(&node);
order.push(self.nodes[node].clone());
for &dep in &self.succ[node] {
indeg[dep] -= 1;
if indeg[dep] == 0 {
ready.insert(dep);
}
}
}
(order.len() == self.nodes.len()).then_some(order)
}
fn order_excluding(&self, cycle: &BTreeSet<CellRef>) -> Vec<CellRef> {
let kept: Vec<usize> = (0..self.nodes.len())
.filter(|&i| !cycle.contains(self.nodes[i]))
.collect();
let mut slot: Vec<Option<usize>> = vec![None; self.nodes.len()];
for (k, &i) in kept.iter().enumerate() {
slot[i] = Some(k);
}
let mut indeg: Vec<usize> = vec![0; kept.len()];
let mut tainted: Vec<bool> = vec![false; kept.len()];
for (j, deps) in self.succ.iter().enumerate() {
let from_cycle = slot[j].is_none();
for &i in deps {
let Some(k) = slot[i] else { continue };
if from_cycle {
tainted[k] = true;
} else {
indeg[k] += 1;
}
}
}
let mut ready: BTreeSet<usize> = (0..kept.len())
.filter(|&k| indeg[k] == 0 && !tainted[k])
.collect();
let mut order: Vec<CellRef> = Vec::new();
while let Some(&k) = ready.iter().next() {
ready.remove(&k);
order.push(self.nodes[kept[k]].clone());
for &i in &self.succ[kept[k]] {
let Some(dep) = slot[i] else { continue };
indeg[dep] -= 1;
if indeg[dep] == 0 && !tainted[dep] {
ready.insert(dep);
}
}
}
order
}
fn cycle_members(&self) -> BTreeSet<CellRef> {
TarjanScc::new(&self.succ).cycle_members(&self.nodes)
}
}
fn resolve_ref(
r: &Ref,
own_sheet: &str,
own_addr: Address,
folder: &CaseMapperBorrowed<'static>,
workbook: &Workbook,
) -> Precedent {
match r {
Ref::Cell { sheet, addr } => {
let sheet_folded = match sheet {
None => own_sheet.to_owned(),
Some(name) => match workbook.sheet(name) {
Some(_) => simple_fold(folder, name),
None => return Precedent::Unresolved(r.relative_display()),
},
};
match to_address(addr) {
Some(a) => Precedent::Cell(CellRef::new(sheet_folded, a)),
None => Precedent::Unresolved(r.relative_display()),
}
}
Ref::Range { sheet, start, end } => {
let sheet_folded = match sheet {
None => own_sheet.to_owned(),
Some(name) => match workbook.sheet(name) {
Some(_) => simple_fold(folder, name),
None => return Precedent::Unresolved(r.relative_display()),
},
};
match normalize_range(start, end) {
Some((s, e)) => Precedent::Range(RangeRef {
sheet: sheet_folded,
start: s,
end: e,
}),
None => Precedent::Unresolved(r.relative_display()),
}
}
Ref::Name(name) => {
let folded = simple_fold(folder, name);
if workbook
.names()
.iter()
.any(|nr| simple_fold(folder, &nr.name) == folded)
{
Precedent::Name(folded)
} else {
Precedent::Unresolved(name.clone())
}
}
Ref::Table {
table,
column,
this_row,
} => resolve_table_precedent(
table.as_deref(),
column,
*this_row,
own_sheet,
own_addr,
folder,
workbook,
),
}
}
fn resolve_table_precedent(
table: Option<&str>,
column: &str,
this_row: bool,
own_sheet: &str,
own_addr: Address,
folder: &CaseMapperBorrowed<'static>,
workbook: &Workbook,
) -> Precedent {
let target = match table {
Some(name) => {
let folded_name = simple_fold(folder, name);
workbook
.tables()
.iter()
.find(|t| simple_fold(folder, &t.name) == folded_name)
}
None => workbook.tables().iter().find(|t| {
named_ref::parse_canonical_ref(&t.r#ref)
.ok()
.and_then(|parsed| crate::table_ref::parsed_range_bounds(&t.r#ref, &parsed))
.is_some_and(|b| {
simple_fold(folder, &b.sheet) == own_sheet
&& b.row_start < own_addr.row
&& own_addr.row <= b.row_end
&& b.col_start <= own_addr.column
&& own_addr.column <= b.col_end
})
}),
};
let Some(t) = target else {
return Precedent::Unresolved(format!(
"{}[{}{}]",
table.unwrap_or(""),
if this_row { "@" } else { "" },
column
));
};
let Ok(parsed) = named_ref::parse_canonical_ref(&t.r#ref) else {
return Precedent::Unresolved(t.r#ref.clone());
};
let Some(bounds) = crate::table_ref::parsed_range_bounds(&t.r#ref, &parsed) else {
return Precedent::Unresolved(t.r#ref.clone());
};
let sheet_folded = simple_fold(folder, &bounds.sheet);
let column_folded = simple_fold(folder, column);
let sheet = workbook.sheet(&bounds.sheet);
let mut found = None;
for c in bounds.col_start..=bounds.col_end {
let Some(header_addr) = Address::new(bounds.row_start, c) else {
continue;
};
if let Some(Value::Text(header)) = sheet.and_then(|s| s.get(header_addr)).map(|c| c.value())
{
if simple_fold(folder, header) == column_folded {
found = Some(c);
break;
}
}
}
let Some(col) = found else {
return Precedent::Unresolved(t.r#ref.clone());
};
if this_row {
if own_sheet != sheet_folded
|| own_addr.row <= bounds.row_start
|| own_addr.row > bounds.row_end
{
return Precedent::Unresolved(t.r#ref.clone());
}
return match Address::new(own_addr.row, col) {
Some(a) => Precedent::Cell(CellRef::new(sheet_folded, a)),
None => Precedent::Unresolved(t.r#ref.clone()),
};
}
Precedent::Range(RangeRef {
sheet: sheet_folded,
start: Address::new(bounds.row_start, col).unwrap(),
end: Address::new(bounds.row_end, col).unwrap(),
})
}
fn resolve_name_ref(
r: &str,
folder: &CaseMapperBorrowed<'static>,
workbook: &Workbook,
) -> Option<NameTarget> {
let parsed = named_ref::parse_canonical_ref(r).ok()?;
let sheet = workbook.sheet(&parsed.sheet)?;
let sheet_folded = simple_fold(folder, sheet.name());
let a1_part = r.rsplit_once('!').map(|(_, a)| a).unwrap_or(r);
match a1_part.split_once(':') {
None => {
let addr = Address::from_a1(a1_part)?;
Some(NameTarget::Cell(CellRef::new(sheet_folded, addr)))
}
Some((s, e)) => {
let start = Address::from_a1(s)?;
let end = Address::from_a1(e)?;
Some(NameTarget::Range(RangeRef {
sheet: sheet_folded,
start,
end,
}))
}
}
}
fn to_address(addr: &CellAddr) -> Option<Address> {
Address::new(addr.row, addr.col)
}
fn normalize_range(start: &CellAddr, end: &CellAddr) -> Option<(Address, Address)> {
let top = Address::new(start.row.min(end.row), start.col.min(end.col))?;
let bottom = Address::new(start.row.max(end.row), start.col.max(end.col))?;
Some((top, bottom))
}
struct TarjanScc<'a> {
adj: &'a [BTreeSet<usize>],
index: Vec<Option<usize>>,
lowlink: Vec<usize>,
on_stack: Vec<bool>,
stack: Vec<usize>,
next_index: usize,
components: Vec<Vec<usize>>,
}
impl<'a> TarjanScc<'a> {
fn new(adj: &'a [BTreeSet<usize>]) -> Self {
let n = adj.len();
Self {
adj,
index: vec![None; n],
lowlink: vec![0; n],
on_stack: vec![false; n],
stack: Vec::new(),
next_index: 0,
components: Vec::new(),
}
}
fn cycle_members(mut self, nodes: &[&CellRef]) -> BTreeSet<CellRef> {
for v in 0..self.adj.len() {
if self.index[v].is_none() {
self.strongconnect(v);
}
}
let mut out = BTreeSet::new();
for comp in &self.components {
let on_cycle = comp.len() > 1
|| (comp.len() == 1 && self.adj[comp[0]].contains(&comp[0]));
if on_cycle {
for &i in comp {
out.insert(nodes[i].clone());
}
}
}
out
}
fn strongconnect(&mut self, v: usize) {
let mut call_stack: Vec<(usize, Vec<usize>)> =
vec![(v, self.adj[v].iter().copied().collect())];
self.index[v] = Some(self.next_index);
self.lowlink[v] = self.next_index;
self.next_index += 1;
self.stack.push(v);
self.on_stack[v] = true;
while let Some((node, successors)) = call_stack.last_mut() {
let node = *node;
if let Some(w) = successors.pop() {
if self.index[w].is_none() {
self.index[w] = Some(self.next_index);
self.lowlink[w] = self.next_index;
self.next_index += 1;
self.stack.push(w);
self.on_stack[w] = true;
call_stack.push((w, self.adj[w].iter().copied().collect()));
} else if self.on_stack[w] {
self.lowlink[node] = self.lowlink[node].min(self.index[w].unwrap());
}
} else {
if self.lowlink[node] == self.index[node].unwrap() {
let mut component = Vec::new();
loop {
let w = self.stack.pop().unwrap();
self.on_stack[w] = false;
component.push(w);
if w == node {
break;
}
}
self.components.push(component);
}
call_stack.pop();
if let Some((parent, _)) = call_stack.last() {
let parent = *parent;
self.lowlink[parent] = self.lowlink[parent].min(self.lowlink[node]);
}
}
}
}
}