Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-core / src / dep_tree.rs
5.0 kB 174 lines
1// SPDX-License-Identifier: Apache-2.0 2// SPDX-FileCopyrightText: 2020 The Gleam contributors 3 4use ecow::EcoString; 5use petgraph::{Direction, algo::Cycle, graph::NodeIndex}; 6use std::collections::{HashMap, HashSet}; 7 8#[cfg(test)] 9use pretty_assertions::assert_eq; 10 11/// Take a sequence of values and their deps, and return the values in 12/// order so that deps come before the dependents. 13/// 14/// Any deps that are not nodes are ignored and presumed to be nodes 15/// that do not need processing. 16/// 17/// Errors if there are duplicate values, unknown deps, or cycles. 18/// 19pub fn toposort_deps(inputs: Vec<(EcoString, Vec<EcoString>)>) -> Result<Vec<EcoString>, Error> { 20 let mut graph = petgraph::Graph::<(), ()>::with_capacity(inputs.len(), inputs.len() * 5); 21 let mut values = HashMap::with_capacity(inputs.len()); 22 let mut indexes = HashMap::with_capacity(inputs.len()); 23 24 for (value, _deps) in &inputs { 25 let index = graph.add_node(()); 26 let _ = indexes.insert(value.clone(), index); 27 let _ = values.insert(index, value.clone()); 28 } 29 30 for (value, deps) in inputs { 31 let &from_index = indexes.get(&value).expect("Finding index for value"); 32 for &to_index in deps.into_iter().filter_map(|dep| indexes.get(&dep)) { 33 let _ = graph.add_edge(from_index, to_index, ()); 34 } 35 } 36 37 match petgraph::algo::toposort(&graph, None) { 38 Err(e) => Err(Error::Cycle(import_cycle(e, &graph, values))), 39 40 Ok(seq) => Ok(seq 41 .into_iter() 42 .map(|i| values.remove(&i).expect("Finding value for index")) 43 .rev() 44 .collect()), 45 } 46} 47 48fn import_cycle( 49 cycle: Cycle<NodeIndex>, 50 graph: &petgraph::Graph<(), ()>, 51 mut values: HashMap<NodeIndex, EcoString>, 52) -> Vec<EcoString> { 53 let origin = cycle.node_id(); 54 let mut path = vec![]; 55 let _ = find_cycle(origin, origin, graph, &mut path, &mut HashSet::new()); 56 path.iter() 57 .map(|index| { 58 values 59 .remove(index) 60 .expect("dep_tree::import_cycle(): cannot find values for index") 61 }) 62 .collect() 63} 64 65fn find_cycle( 66 origin: NodeIndex, 67 parent: NodeIndex, 68 graph: &petgraph::Graph<(), ()>, 69 path: &mut Vec<NodeIndex>, 70 seen: &mut HashSet<NodeIndex>, 71) -> bool { 72 let _ = seen.insert(parent); 73 for node in graph.neighbors_directed(parent, Direction::Outgoing) { 74 if node == origin { 75 path.push(node); 76 return true; 77 } 78 if seen.contains(&node) { 79 continue; 80 } 81 if find_cycle(origin, node, graph, path, seen) { 82 path.push(node); 83 return true; 84 } 85 } 86 false 87} 88 89#[derive(Debug, PartialEq)] 90pub enum Error { 91 Cycle(Vec<EcoString>), 92} 93 94#[cfg(test)] 95mod tests { 96 use super::{assert_eq, *}; 97 98 #[test] 99 fn toposort_deps_test() { 100 // All deps are nodes 101 assert_eq!( 102 toposort_deps(vec![ 103 ("a".into(), vec!["b".into()]), 104 ("c".into(), vec![]), 105 ("b".into(), vec!["c".into()]) 106 ]), 107 Ok(vec!["c".into(), "b".into(), "a".into()]) 108 ); 109 110 // No deps 111 assert_eq!( 112 toposort_deps(vec![ 113 ("no-deps-1".into(), vec![]), 114 ("no-deps-2".into(), vec![]) 115 ]), 116 Ok(vec!["no-deps-1".into(), "no-deps-2".into(),]) 117 ); 118 119 // Some deps are not nodes (and thus are ignored) 120 assert_eq!( 121 toposort_deps(vec![ 122 ("a".into(), vec!["b".into(), "z".into()]), 123 ("b".into(), vec!["x".into()]) 124 ]), 125 Ok(vec!["b".into(), "a".into()]) 126 ); 127 } 128 129 #[test] 130 fn cycle_detection() { 131 // a ---+ 132 // ^ | 133 // | v 134 // +----+ 135 assert_eq!( 136 toposort_deps(vec![("a".into(), vec!["a".into()])]), 137 Err(Error::Cycle(vec!["a".into()])) 138 ); 139 140 // a -> b -> c 141 // ^ v 142 // | | 143 // +---------+ 144 assert_eq!( 145 toposort_deps(vec![ 146 ("a".into(), vec!["b".into()]), 147 ("b".into(), vec!["c".into()]), 148 ("c".into(), vec!["a".into()]), 149 ]), 150 Err(Error::Cycle(vec!["c".into(), "b".into(), "a".into()])) 151 ); 152 153 // a -> b <- e 154 // | | ^ 155 // v v | 156 // f c -> d 157 assert_eq!( 158 toposort_deps(vec![ 159 ("a".into(), vec!["b".into()]), 160 ("b".into(), vec!["c".into()]), 161 ("c".into(), vec!["d".into()]), 162 ("d".into(), vec!["e".into()]), 163 ("e".into(), vec!["b".into()]), 164 ("a".into(), vec!["f".into()]), 165 ]), 166 Err(Error::Cycle(vec![ 167 "e".into(), 168 "d".into(), 169 "c".into(), 170 "b".into(), 171 ])) 172 ); 173 } 174}