use crate::visit::IntoNeighbors;
use crate::visit::{VisitMap, Visitable};
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Default, Hash)]
pub struct Time(pub usize);
#[derive(Copy, Clone, Debug)]
pub enum DfsEvent<N> {
Discover(N, Time),
TreeEdge(N, N),
BackEdge(N, N),
CrossForwardEdge(N, N),
Finish(N, Time),
}
macro_rules! try_control {
($e:expr, $p:stmt) => {
try_control!($e, $p, ());
};
($e:expr, $p:stmt, $q:stmt) => {
match $e {
x => {
if x.should_break() {
return x;
} else if x.should_prune() {
$p
} else {
$q
}
}
}
};
}
#[derive(Copy, Clone, Debug)]
pub enum Control<B> {
Continue,
Prune,
Break(B),
}
impl<B> Control<B> {
pub fn breaking() -> Control<()> {
Control::Break(())
}
pub fn break_value(self) -> Option<B> {
match self {
Control::Continue | Control::Prune => None,
Control::Break(b) => Some(b),
}
}
}
pub trait ControlFlow {
fn continuing() -> Self;
fn should_break(&self) -> bool;
fn should_prune(&self) -> bool;
}
impl ControlFlow for () {
fn continuing() {}
#[inline]
fn should_break(&self) -> bool {
false
}
#[inline]
fn should_prune(&self) -> bool {
false
}
}
impl<B> ControlFlow for Control<B> {
fn continuing() -> Self {
Control::Continue
}
fn should_break(&self) -> bool {
matches!(*self, Control::Break(_))
}
fn should_prune(&self) -> bool {
matches!(*self, Control::Prune)
}
}
impl<C: ControlFlow, E> ControlFlow for Result<C, E> {
fn continuing() -> Self {
Ok(C::continuing())
}
fn should_break(&self) -> bool {
if let Ok(ref c) = *self {
c.should_break()
} else {
true
}
}
fn should_prune(&self) -> bool {
if let Ok(ref c) = *self {
c.should_prune()
} else {
false
}
}
}
impl<B> Default for Control<B> {
fn default() -> Self {
Control::Continue
}
}
#[track_caller]
pub fn depth_first_search<G, I, F, C>(graph: G, starts: I, mut visitor: F) -> C
where
G: IntoNeighbors + Visitable,
I: IntoIterator<Item = G::NodeId>,
F: FnMut(DfsEvent<G::NodeId>) -> C,
C: ControlFlow,
{
let time = &mut Time(0);
let discovered = &mut graph.visit_map();
let finished = &mut graph.visit_map();
for start in starts {
try_control!(
dfs_visitor(graph, start, &mut visitor, discovered, finished, time),
unreachable!()
);
}
C::continuing()
}
pub(crate) fn dfs_visitor<G, F, C>(
graph: G,
u: G::NodeId,
visitor: &mut F,
discovered: &mut impl VisitMap<G::NodeId>,
finished: &mut impl VisitMap<G::NodeId>,
time: &mut Time,
) -> C
where
G: IntoNeighbors + Visitable,
F: FnMut(DfsEvent<G::NodeId>) -> C,
C: ControlFlow,
{
if !discovered.visit(u) {
return C::continuing();
}
try_control!(
visitor(DfsEvent::Discover(u, time_post_inc(time))),
{},
for v in graph.neighbors(u) {
if !discovered.is_visited(&v) {
try_control!(visitor(DfsEvent::TreeEdge(u, v)), continue);
try_control!(
dfs_visitor(graph, v, visitor, discovered, finished, time),
unreachable!()
);
} else if !finished.is_visited(&v) {
try_control!(visitor(DfsEvent::BackEdge(u, v)), continue);
} else {
try_control!(visitor(DfsEvent::CrossForwardEdge(u, v)), continue);
}
}
);
let first_finish = finished.visit(u);
debug_assert!(first_finish);
try_control!(
visitor(DfsEvent::Finish(u, time_post_inc(time))),
panic!("Pruning on the `DfsEvent::Finish` is not supported!")
);
C::continuing()
}
fn time_post_inc(x: &mut Time) -> Time {
let v = *x;
x.0 += 1;
v
}