use std::fmt; use erg_common::consts::DEBUG_MODE; use erg_common::dict::Dict; use erg_common::pathutil::NormalizedPathBuf; use erg_common::set; use erg_common::set::Set; use erg_common::shared::{MappedRwLockReadGuard, RwLockReadGuard, Shared}; use erg_common::tsort::{tsort, Graph, Node, TopoSortError}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum IncRefError { CycleDetected, } impl IncRefError { pub const fn is_cycle_detected(&self) -> bool { matches!(self, Self::CycleDetected) } } #[derive(Debug, Clone, Default)] pub struct ModuleGraph { graph: Graph, index: Dict, } impl fmt::Display for ModuleGraph { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { writeln!(f, "ModuleGraph {{")?; for node in self.graph.iter() { writeln!(f, "{} depends on {{", node.id.display())?; for dep in node.depends_on.iter() { writeln!(f, "{}, ", dep.display())?; } writeln!(f, "}}, ")?; } write!(f, "}}") } } impl IntoIterator for ModuleGraph { type Item = Node; type IntoIter = std::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { self.graph.into_iter() } } impl ModuleGraph { pub fn new() -> Self { Self { graph: Graph::new(), index: Dict::new(), } } pub fn get_node(&self, path: &NormalizedPathBuf) -> Option<&Node> { self.index.get(path).map(|&i| &self.graph[i]) } pub fn get_mut_node( &mut self, path: &NormalizedPathBuf, ) -> Option<&mut Node> { self.index.get(path).map(|&i| &mut self.graph[i]) } /// if `path` directly depends on `target`, returns `true`, else `false`. /// if `path` not found, returns `false`. /// O(1) pub fn depends_on(&self, path: &NormalizedPathBuf, target: &NormalizedPathBuf) -> bool { let path = NormalizedPathBuf::new(path.to_path_buf()); let target = NormalizedPathBuf::new(target.to_path_buf()); self.get_node(&path) .map(|n| n.depends_on.contains(&target)) .unwrap_or(false) } /// if `path` depends on `target`, returns `true`, else `false`. /// if `path` not found, returns `false`. pub fn deep_depends_on(&self, path: &NormalizedPathBuf, target: &NormalizedPathBuf) -> bool { let path = NormalizedPathBuf::new(path.to_path_buf()); let target = NormalizedPathBuf::new(target.to_path_buf()); let mut visited = set! {}; self.deep_depends_on_(&path, &target, &mut visited) } fn deep_depends_on_<'p>( &'p self, path: &'p NormalizedPathBuf, target: &NormalizedPathBuf, visited: &mut Set<&'p NormalizedPathBuf>, ) -> bool { if !visited.insert(path) { return false; } self.get_node(path) .map(|n| { n.depends_on.contains(target) || n.depends_on .iter() .any(|p| self.deep_depends_on_(p, target, visited)) }) .unwrap_or(false) } /// (usually) `path` is not contained. /// O(N) pub fn children<'p>( &'p self, path: &'p NormalizedPathBuf, ) -> impl Iterator + 'p { self.graph .iter() .filter(|n| n.depends_on.contains(path)) .map(|n| n.id.clone()) } /// (usually) `path` is not contained. /// O(1) pub fn parents(&self, path: &NormalizedPathBuf) -> Option<&Set> { self.get_node(path).map(|n| &n.depends_on) } /// ```erg /// # a.er /// b = import "b" /// # -> a: child, b: parent /// # b.er /// c = import "c" /// # -> ancestors(a) == {b, c} /// ``` /// O(N) pub fn ancestors<'p>(&'p self, path: &'p NormalizedPathBuf) -> Set<&'p NormalizedPathBuf> { let mut ancestors = set! {}; let mut visited = set! {}; self.ancestors_(path, &mut ancestors, &mut visited); ancestors } fn ancestors_<'p>( &'p self, path: &'p NormalizedPathBuf, ancestors: &mut Set<&'p NormalizedPathBuf>, visited: &mut Set<&'p NormalizedPathBuf>, ) { if !visited.insert(path) { return; } if let Some(parents) = self.parents(path) { for parent in parents.iter() { if ancestors.insert(parent) { self.ancestors_(parent, ancestors, visited); } } } } pub fn add_node_if_none(&mut self, path: &NormalizedPathBuf) { if path.is_dir() { if DEBUG_MODE { panic!("path is a directory: {path}"); } return; } if self.index.get(path).is_none() { let node = Node::new(path.clone(), (), set! {}); self.graph.push(node); self.index.insert(path.clone(), self.graph.len() - 1); } } /// returns Err (and do nothing) if this operation makes a cycle pub fn inc_ref( &mut self, referrer: &NormalizedPathBuf, depends_on: NormalizedPathBuf, ) -> Result<(), IncRefError> { if depends_on.is_dir() { if DEBUG_MODE { panic!("path is a directory: {depends_on}"); } return Ok(()); } self.add_node_if_none(referrer); if referrer == &depends_on { return Ok(()); } if self.deep_depends_on(&depends_on, referrer) { return Err(IncRefError::CycleDetected); } if let Some(node) = self.get_mut_node(referrer) { node.push_dep(depends_on); } else { unreachable!("node not found: {}", referrer.display()); } Ok(()) } pub fn iter(&self) -> impl Iterator> { self.graph.iter() } pub fn sorted(self) -> Result { tsort(self.graph).map(|graph| { let index = graph .iter() .map(|n| n.id.clone()) .enumerate() .map(|(i, path)| (path, i)) .collect(); Self { graph, index } }) } #[allow(clippy::result_unit_err)] pub fn sort(&mut self) -> Result<(), TopoSortError> { *self = std::mem::take(self).sorted()?; Ok(()) } /// Do not erase relationships with modules that depend on `path`. /// O(N) pub fn remove(&mut self, path: &NormalizedPathBuf) { if let Some(&i) = self.index.get(path) { self.graph.remove(i); self.index.remove(path); // shift indices for index in self.index.values_mut() { if *index > i { *index -= 1; } } } for node in self.graph.iter_mut() { node.depends_on.retain(|p| p != path); } } /// O(N) pub fn rename_path(&mut self, old: &NormalizedPathBuf, new: NormalizedPathBuf) { for node in self.graph.iter_mut() { if &node.id == old { node.id = new.clone(); } if node.depends_on.contains(old) { node.depends_on.insert(new.clone()); } node.depends_on.retain(|p| p != old); } } pub fn initialize(&mut self) { self.graph.clear(); self.index.clear(); } pub fn display_parents( &self, lev: usize, id: &NormalizedPathBuf, appeared: &mut Set, ) -> String { let mut s = String::new(); let Some(parents) = self.parents(id) else { return s; }; for parent in parents.iter() { s.push_str(&format!("{}-> {}\n", " ".repeat(lev), parent.display())); if appeared.contains(parent) { continue; } s.push_str(&self.display_parents(lev + 1, parent, appeared)); appeared.insert(parent.clone()); } s } pub fn display(&self) -> String { let mut s = String::new(); let mut appeared = set! {}; for node in self.graph.iter() { let mut children = self.children(&node.id); if children.next().is_some() || appeared.contains(&node.id) { continue; } s.push_str(&format!("{}\n", node.id.display())); s.push_str(&self.display_parents(1, &node.id, &mut appeared)); appeared.insert(node.id.clone()); } s } } #[derive(Debug, Clone, Default)] pub struct SharedModuleGraph(Shared); impl fmt::Display for SharedModuleGraph { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0.borrow()) } } impl IntoIterator for SharedModuleGraph { type Item = Node; type IntoIter = std::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { self.0.into_inner().into_iter() } } impl SharedModuleGraph { pub fn new() -> Self { Self(Shared::new(ModuleGraph::new())) } pub fn get_node( &self, path: &NormalizedPathBuf, ) -> Option>> { RwLockReadGuard::try_map(self.0.borrow(), |graph| graph.get_node(path)).ok() } /// if `path` directly depends on `target`, returns `true`, else `false`. /// if `path` not found, returns `false`. /// O(1) pub fn depends_on(&self, path: &NormalizedPathBuf, target: &NormalizedPathBuf) -> bool { self.0.borrow().depends_on(path, target) } /// (usually) `path` is not contained. /// O(N) pub fn children(&self, path: &NormalizedPathBuf) -> Set { self.0.borrow().children(path).collect() } /// (usually) `path` is not contained. /// O(N) pub fn ancestors(&self, path: &NormalizedPathBuf) -> Set { self.0.borrow().ancestors(path).cloned() } pub fn add_node_if_none(&self, path: &NormalizedPathBuf) { self.0.borrow_mut().add_node_if_none(path); } pub fn inc_ref( &self, referrer: &NormalizedPathBuf, depends_on: NormalizedPathBuf, ) -> Result<(), IncRefError> { self.0.borrow_mut().inc_ref(referrer, depends_on) } pub fn ref_inner(&self) -> RwLockReadGuard { self.0.borrow() } pub fn remove(&self, path: &NormalizedPathBuf) { self.0.borrow_mut().remove(path); } pub fn rename_path(&self, old: &NormalizedPathBuf, new: NormalizedPathBuf) { self.0.borrow_mut().rename_path(old, new); } #[allow(clippy::result_unit_err)] pub fn sort(&self) -> Result<(), TopoSortError> { self.0.borrow_mut().sort() } pub fn entries(&self) -> Set { self.0.borrow().iter().map(|n| n.id.clone()).collect() } pub fn initialize(&self) { self.0.borrow_mut().initialize(); } pub fn clone_inner(&self) -> ModuleGraph { self.0.borrow().clone() } pub fn display(&self) -> String { self.0.borrow().display() } }