use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use icu_casemap::CaseMapperBorrowed;
use truecalc_core::{CellAddr, Engine, EngineFlavor, Ref};
use crate::address::Address;
use crate::casefold::simple_fold;
use crate::named_ref;
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 }
}
}
#[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>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum NameTarget {
Cell(CellRef),
Range(RangeRef),
}
impl DependencyGraph {
pub fn build(workbook: &Workbook) -> Self {
let folder = CaseMapperBorrowed::new();
let engine = match workbook.engine() {
EngineFlavor::Sheets => Engine::sheets(),
EngineFlavor::Excel => Engine::excel(),
};
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,
};
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 engine.parse(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, &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);
}
}
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 topological_order(&self) -> Result<Vec<CellRef>, BTreeSet<CellRef>> {
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()];
let mut indeg: Vec<usize> = vec![0; 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) {
if succ[j].insert(i) {
indeg[i] += 1;
}
}
}
}
}
let mut ready: BTreeSet<usize> = (0..nodes.len()).filter(|&i| indeg[i] == 0).collect();
let mut order: Vec<CellRef> = Vec::with_capacity(nodes.len());
while let Some(&node) = ready.iter().next() {
ready.remove(&node);
order.push(nodes[node].clone());
for &dep in &succ[node] {
indeg[dep] -= 1;
if indeg[dep] == 0 {
ready.insert(dep);
}
}
}
if order.len() == nodes.len() {
Ok(order)
} else {
Err(self.cycle_cells())
}
}
pub fn acyclic_order_excluding(&self, cycle: &BTreeSet<CellRef>) -> Vec<CellRef> {
let nodes: Vec<&CellRef> = self
.precedents
.keys()
.filter(|c| !cycle.contains(*c))
.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()];
let mut indeg: Vec<usize> = vec![0; nodes.len()];
for (i, cell) in nodes.iter().enumerate() {
for prec in &self.precedents[*cell] {
for fp in self.formula_precedent_cells(prec) {
if cycle.contains(&fp) {
continue;
}
if let Some(&j) = index_of.get(&fp) {
if succ[j].insert(i) {
indeg[i] += 1;
}
}
}
}
}
let mut tainted = vec![false; nodes.len()];
for (i, cell) in nodes.iter().enumerate() {
for prec in &self.precedents[*cell] {
for fp in self.formula_precedent_cells(prec) {
if cycle.contains(&fp) {
tainted[i] = true;
}
}
}
}
let mut ready: BTreeSet<usize> = (0..nodes.len())
.filter(|&i| indeg[i] == 0 && !tainted[i])
.collect();
let mut order: Vec<CellRef> = Vec::new();
while let Some(&node) = ready.iter().next() {
ready.remove(&node);
order.push(nodes[node].clone());
for &dep in &succ[node] {
indeg[dep] -= 1;
if indeg[dep] == 0 && !tainted[dep] {
ready.insert(dep);
}
}
}
order
}
pub fn cycle_cells(&self) -> BTreeSet<CellRef> {
let nodes: Vec<CellRef> = self.precedents.keys().cloned().collect();
let index_of: HashMap<&CellRef, usize> =
nodes.iter().enumerate().map(|(i, n)| (n, i)).collect();
let mut adj: 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) {
adj[j].insert(i);
}
}
}
}
TarjanScc::new(&adj).cycle_members(&nodes)
}
fn formula_precedent_cells(&self, prec: &Precedent) -> Vec<CellRef> {
match prec {
Precedent::Cell(c) => {
if self.precedents.contains_key(c) {
vec![c.clone()]
} else {
Vec::new()
}
}
Precedent::Range(r) => self
.precedents
.keys()
.filter(|c| r.contains(c))
.cloned()
.collect(),
Precedent::Name(name) => match self.name_targets.get(name) {
Some(NameTarget::Cell(c)) if self.precedents.contains_key(c) => vec![c.clone()],
Some(NameTarget::Cell(_)) | None => Vec::new(),
Some(NameTarget::Range(r)) => self
.precedents
.keys()
.filter(|c| r.contains(c))
.cloned()
.collect(),
},
Precedent::Unresolved(_) => Vec::new(),
}
}
}
fn resolve_ref(
r: &Ref,
own_sheet: &str,
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())
}
}
}
}
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]);
}
}
}
}
}