use crate::{Context, DebugWithContext, Function, Symbol};
use rustc_hash::{FxHashMap, FxHashSet};
#[derive(Debug)]
pub enum Cycle {
Simple { nodes: Vec<Symbol> },
RhoShape {
tail: Vec<Symbol>,
cycle: Vec<Symbol>,
},
}
impl DebugWithContext for (Function, &Cycle) {
fn fmt_with_context(
&self,
formatter: &mut std::fmt::Formatter,
context: &Context,
) -> std::fmt::Result {
let names = |syms: &[Symbol]| -> Vec<String> {
syms.iter().map(|n| n.get_name(context, self.0)).collect()
};
match self.1 {
Cycle::Simple { nodes } => formatter
.debug_struct("Cycle::Simple")
.field("nodes", &names(nodes))
.finish(),
Cycle::RhoShape { tail, cycle } => formatter
.debug_struct("Cycle::RhoShape")
.field("tail", &names(tail))
.field("cycle", &names(cycle))
.finish(),
}
}
}
impl Cycle {
pub fn new(edges: &FxHashMap<Symbol, Symbol>, start: Symbol) -> Self {
let cycle = std::iter::successors(Some(start), |&node| {
let next = edges[&node];
if next == start {
None
} else {
Some(next)
}
})
.collect::<Vec<_>>();
let cycle_set = cycle.iter().copied().collect::<FxHashSet<_>>();
let tail = edges
.keys()
.filter(|node| {
if cycle_set.contains(node) {
return false;
}
std::iter::successors(Some(*node), |current| edges.get(current))
.take(edges.len() + 1)
.any(|node| cycle_set.contains(node))
})
.copied()
.collect::<Vec<_>>();
if tail.is_empty() {
Cycle::Simple { nodes: cycle }
} else {
Cycle::RhoShape { tail, cycle }
}
}
}
pub fn find_node_in_cycle(edges: &FxHashMap<Symbol, Symbol>) -> Option<Symbol> {
let mut not_in_cycle: FxHashSet<Symbol> = FxHashSet::default();
#[allow(clippy::iter_over_hash_type)]
for candidate in edges.keys() {
if not_in_cycle.contains(candidate) {
continue;
}
let mut path = FxHashSet::default();
let mut candidate = *candidate;
loop {
if not_in_cycle.contains(&candidate) {
not_in_cycle.extend(path.drain());
break;
}
if !path.insert(candidate) {
return Some(candidate);
}
match edges.get(&candidate) {
Some(next) => candidate = *next,
None => {
not_in_cycle.extend(path.drain());
break;
}
}
}
}
None
}