use crate::ast::{Id, Span};
use anyhow::Result;
use indexmap::IndexMap;
use std::collections::BinaryHeap;
use std::fmt;
use std::mem;
#[derive(Default, Clone)]
struct State {
outbound_remaining: usize,
reverse_deps: Vec<usize>,
}
pub fn toposort<'a>(
kind: &str,
deps: &IndexMap<&'a str, Vec<Id<'a>>>,
) -> Result<Vec<&'a str>, Error> {
let mut states = vec![State::default(); deps.len()];
for (i, (_, edges)) in deps.iter().enumerate() {
states[i].outbound_remaining = edges.len();
for edge in edges {
let (j, _, _) = deps
.get_full(edge.name)
.ok_or_else(|| Error::NonexistentDep {
span: edge.span,
name: edge.name.to_string(),
kind: kind.to_string(),
highlighted: None,
})?;
states[j].reverse_deps.push(i);
}
}
let mut order = Vec::new();
let mut heap = BinaryHeap::new();
for (i, dep) in deps.keys().enumerate() {
if states[i].outbound_remaining == 0 {
heap.push((deps.len() - i, *dep, i));
}
}
while let Some((_order, node, i)) = heap.pop() {
order.push(node);
for i in mem::take(&mut states[i].reverse_deps) {
states[i].outbound_remaining -= 1;
if states[i].outbound_remaining == 0 {
let (dep, _) = deps.get_index(i).unwrap();
heap.push((deps.len() - i, *dep, i));
}
}
}
if order.len() == deps.len() {
return Ok(order);
}
for (i, state) in states.iter().enumerate() {
if state.outbound_remaining == 0 {
continue;
}
let (_, edges) = deps.get_index(i).unwrap();
for dep in edges {
let (j, _, _) = deps.get_full(dep.name).unwrap();
if states[j].outbound_remaining == 0 {
continue;
}
return Err(Error::Cycle {
span: dep.span,
name: dep.name.to_string(),
kind: kind.to_string(),
highlighted: None,
});
}
}
unreachable!()
}
#[derive(Debug)]
pub enum Error {
NonexistentDep {
span: Span,
name: String,
kind: String,
highlighted: Option<String>,
},
Cycle {
span: Span,
name: String,
kind: String,
highlighted: Option<String>,
},
}
impl Error {
pub(crate) fn highlighted(&self) -> Option<&str> {
match self {
Error::NonexistentDep { highlighted, .. } | Error::Cycle { highlighted, .. } => {
highlighted.as_deref()
}
}
}
pub(crate) fn set_highlighted(&mut self, string: String) {
match self {
Error::NonexistentDep { highlighted, .. } | Error::Cycle { highlighted, .. } => {
*highlighted = Some(string);
}
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(s) = self.highlighted() {
return f.write_str(s);
}
match self {
Error::NonexistentDep { kind, name, .. } => {
write!(f, "{kind} `{name}` does not exist")
}
Error::Cycle { kind, name, .. } => {
write!(f, "{kind} `{name}` depends on itself")
}
}
}
}
impl std::error::Error for Error {}
#[cfg(test)]
mod tests {
use super::*;
fn id(name: &str) -> Id<'_> {
Id {
name,
span: Span { start: 0, end: 0 },
}
}
#[test]
fn smoke() {
let empty: Vec<&str> = Vec::new();
assert_eq!(toposort("", &IndexMap::new()).unwrap(), empty);
let mut nonexistent = IndexMap::new();
nonexistent.insert("a", vec![id("b")]);
assert!(matches!(
toposort("", &nonexistent),
Err(Error::NonexistentDep { .. })
));
let mut one = IndexMap::new();
one.insert("a", vec![]);
assert_eq!(toposort("", &one).unwrap(), ["a"]);
let mut two = IndexMap::new();
two.insert("a", vec![]);
two.insert("b", vec![id("a")]);
assert_eq!(toposort("", &two).unwrap(), ["a", "b"]);
let mut two = IndexMap::new();
two.insert("a", vec![id("b")]);
two.insert("b", vec![]);
assert_eq!(toposort("", &two).unwrap(), ["b", "a"]);
}
#[test]
fn cycles() {
let mut cycle = IndexMap::new();
cycle.insert("a", vec![id("a")]);
assert!(matches!(toposort("", &cycle), Err(Error::Cycle { .. })));
let mut cycle = IndexMap::new();
cycle.insert("a", vec![id("b")]);
cycle.insert("b", vec![id("c")]);
cycle.insert("c", vec![id("a")]);
assert!(matches!(toposort("", &cycle), Err(Error::Cycle { .. })));
}
#[test]
fn depend_twice() {
let mut two = IndexMap::new();
two.insert("b", vec![id("a"), id("a")]);
two.insert("a", vec![]);
assert_eq!(toposort("", &two).unwrap(), ["a", "b"]);
}
#[test]
fn preserve_order() {
let mut order = IndexMap::new();
order.insert("a", vec![]);
order.insert("b", vec![]);
assert_eq!(toposort("", &order).unwrap(), ["a", "b"]);
let mut order = IndexMap::new();
order.insert("b", vec![]);
order.insert("a", vec![]);
assert_eq!(toposort("", &order).unwrap(), ["b", "a"]);
}
}