use crate::containment::ContainmentEdge;
use crate::error::GraphBuildError;
use crate::id::{GraphNodeId, index_of, position};
use super::state::ProjectionState;
#[derive(Clone, Copy, PartialEq, Eq)]
enum Visit {
Pending,
Active,
Done,
}
pub(crate) fn check_containment_forest(
state: &ProjectionState,
roots: &[GraphNodeId],
) -> Result<(), GraphBuildError> {
let parents = stated_parents(state)?;
let rooted = stated_roots(roots, parents.len())?;
check_every_node_is_parented(&parents, &rooted)?;
check_no_unit_root_is_contained(&parents, &rooted)?;
check_acyclic(&parents)
}
fn stated_roots(roots: &[GraphNodeId], nodes: usize) -> Result<Box<[bool]>, GraphBuildError> {
let mut rooted = vec![false; nodes];
for root in roots {
let index = index_of(root.index());
let slot = rooted
.get_mut(index)
.ok_or(GraphBuildError::UnknownContainmentNode { node: root.index() })?;
match slot {
true => return Err(GraphBuildError::SharedUnitRoot { root: root.index() }),
false => *slot = true,
}
}
Ok(rooted.into_boxed_slice())
}
fn stated_parents(state: &ProjectionState) -> Result<Box<[Option<GraphNodeId>]>, GraphBuildError> {
let mut parents: Vec<Option<GraphNodeId>> = vec![None; state.node_count()];
for edge in state.containment() {
let slot = parent_slot(&mut parents, edge)?;
match slot {
Some(existing) => {
return Err(GraphBuildError::MultiplyContained {
parent: existing.index(),
child: edge.child().index(),
});
}
None => *slot = Some(edge.parent()),
}
}
Ok(parents.into_boxed_slice())
}
fn parent_slot<'a>(
parents: &'a mut [Option<GraphNodeId>],
edge: &ContainmentEdge,
) -> Result<&'a mut Option<GraphNodeId>, GraphBuildError> {
let child = edge.child();
parents
.get_mut(index_of(child.index()))
.ok_or(GraphBuildError::UnknownContainmentNode {
node: child.index(),
})
}
fn check_every_node_is_parented(
parents: &[Option<GraphNodeId>],
rooted: &[bool],
) -> Result<(), GraphBuildError> {
parents
.iter()
.zip(rooted)
.enumerate()
.find(|(_, (parent, root))| parent.is_none() && !**root)
.map_or(Ok(()), |(index, _)| {
Err(GraphBuildError::UnparentedNode {
node: position(index),
})
})
}
fn check_no_unit_root_is_contained(
parents: &[Option<GraphNodeId>],
rooted: &[bool],
) -> Result<(), GraphBuildError> {
parents
.iter()
.zip(rooted)
.enumerate()
.find_map(|(index, (parent, root))| match root {
true => parent.map(|held| (index, held)),
false => None,
})
.map_or(Ok(()), |(index, parent)| {
Err(GraphBuildError::RootHasParent {
root: position(index),
parent: parent.index(),
})
})
}
fn check_acyclic(parents: &[Option<GraphNodeId>]) -> Result<(), GraphBuildError> {
let mut visits = vec![Visit::Pending; parents.len()];
let mut chain: Vec<usize> = Vec::new();
for start in 0..parents.len() {
walk_ancestors(parents, &mut visits, &mut chain, start)?;
}
Ok(())
}
fn walk_ancestors(
parents: &[Option<GraphNodeId>],
visits: &mut [Visit],
chain: &mut Vec<usize>,
start: usize,
) -> Result<(), GraphBuildError> {
match visit_of(visits, start)? {
Visit::Pending => (),
Visit::Active | Visit::Done => return Ok(()),
}
chain.clear();
let mut current = Some(start);
let mut cycle = None;
while let Some(index) = current {
let visit = visit_of(visits, index)?;
cycle = (visit == Visit::Active).then_some(index);
match visit {
Visit::Pending => (),
Visit::Active | Visit::Done => break,
}
mark(visits, index, Visit::Active)?;
chain.push(index);
current = parents
.get(index)
.and_then(|parent| *parent)
.map(|parent| index_of(parent.index()));
}
for index in chain.drain(..) {
mark(visits, index, Visit::Done)?;
}
match cycle {
Some(index) => Err(GraphBuildError::ContainmentCycle {
node: position(index),
}),
None => Ok(()),
}
}
fn visit_of(visits: &[Visit], index: usize) -> Result<Visit, GraphBuildError> {
visits
.get(index)
.copied()
.ok_or(GraphBuildError::UnknownContainmentNode {
node: position(index),
})
}
fn mark(visits: &mut [Visit], index: usize, visit: Visit) -> Result<(), GraphBuildError> {
match visits.get_mut(index) {
Some(slot) => {
*slot = visit;
Ok(())
}
None => Err(GraphBuildError::UnknownContainmentNode {
node: position(index),
}),
}
}