use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone, Default)]
pub struct DependencyGraph {
pub(crate) deps: HashMap<String, Vec<String>>,
pub(crate) spans: HashMap<String, std::ops::Range<usize>>,
}
impl DependencyGraph {
pub fn add_node(&mut self, name: impl Into<String>, span: Option<std::ops::Range<usize>>) {
let name = name.into();
self.deps.entry(name.clone()).or_default();
if let Some(span) = span {
self.spans.insert(name, span);
}
}
#[cfg_attr(debug_assertions, track_caller)]
pub fn add_edge(&mut self, from: &str, to: impl Into<String>) {
let to_str = to.into();
#[cfg(debug_assertions)]
{
eprintln!("DEBUG [DependencyGraph]: Adding edge '{}' -> '{}' (called from: {:?})",
from, to_str, std::panic::Location::caller());
}
if let Some(deps) = self.deps.get_mut(from) {
deps.push(to_str);
} else {
#[cfg(debug_assertions)]
eprintln!("DEBUG [DependencyGraph]: Warning - node '{}' not found in graph", from);
}
}
pub fn find_all_cycles(&self) -> Vec<Vec<String>> {
#[cfg(debug_assertions)]
eprintln!("DEBUG [DependencyGraph]: Searching for cycles in graph with {} nodes", self.deps.len());
let mut cycles = Vec::new();
let mut visited = HashSet::new();
let mut rec_stack = HashSet::new();
let mut path = Vec::new();
for node in self.deps.keys() {
if !visited.contains(node.as_str()) {
self.dfs_cycles(
node,
&mut visited,
&mut rec_stack,
&mut path,
&mut cycles,
);
}
}
cycles
}
fn extract_cycle(&self, path: &[String], cycle_start: &str) -> Option<Vec<String>> {
path.iter()
.position(|n| n == cycle_start)
.map(|start| {
let mut cycle = path[start..].to_vec();
cycle.push(cycle_start.to_string());
#[cfg(debug_assertions)]
eprintln!("DEBUG [DependencyGraph]: Found cycle: {}", cycle.join(" -> "));
cycle
})
}
fn process_neighbor(
&self,
neighbor: &str,
visited: &mut HashSet<String>,
rec_stack: &mut HashSet<String>,
path: &mut Vec<String>,
cycles: &mut Vec<Vec<String>>,
) {
if rec_stack.contains(neighbor) {
if let Some(cycle) = self.extract_cycle(path, neighbor) {
cycles.push(cycle);
}
} else if !visited.contains(neighbor) {
self.dfs_cycles(neighbor, visited, rec_stack, path, cycles);
}
}
fn dfs_cycles(
&self,
node: &str,
visited: &mut HashSet<String>,
rec_stack: &mut HashSet<String>,
path: &mut Vec<String>,
cycles: &mut Vec<Vec<String>>,
) {
visited.insert(node.to_owned());
rec_stack.insert(node.to_owned());
path.push(node.to_owned());
if let Some(neighbors) = self.deps.get(node) {
for neighbor in neighbors {
self.process_neighbor(neighbor, visited, rec_stack, path, cycles);
}
}
rec_stack.remove(node);
path.pop();
}
}