use crate::Decl;
use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone)]
pub struct Module {
pub name: String,
pub path: Vec<String>,
pub decls: Vec<Decl>,
pub imports: Vec<String>,
pub exports: Vec<String>,
pub import_specs: Vec<ImportSpec>,
pub export_spec: Option<ExportSpec>,
pub namespaces: Vec<NamespaceScope>,
pub opens: Vec<OpenDirective>,
pub visibility_map: HashMap<String, Visibility>,
pub config: ModuleConfig,
}
impl Module {
pub fn new(name: String) -> Self {
Self {
name,
path: Vec::new(),
decls: Vec::new(),
imports: Vec::new(),
exports: Vec::new(),
import_specs: Vec::new(),
export_spec: None,
namespaces: Vec::new(),
opens: Vec::new(),
visibility_map: HashMap::new(),
config: ModuleConfig {
allow_circular: false,
private_by_default: false,
allow_shadowing: false,
},
}
}
pub fn with_config(name: String, config: ModuleConfig) -> Self {
let mut module = Self::new(name);
module.config = config;
module
}
pub fn add_decl(&mut self, decl: Decl) {
self.decls.push(decl);
}
pub fn add_import(&mut self, module: String) {
self.imports.push(module);
}
pub fn add_export(&mut self, name: String) {
self.exports.push(name);
}
pub fn full_path(&self) -> String {
if self.path.is_empty() {
self.name.clone()
} else {
format!("{}.{}", self.path.join("."), self.name)
}
}
#[allow(dead_code)]
pub fn resolve_name(&self, name: &str) -> ResolvedName {
let local_names = self.declared_names();
if local_names.contains(&name.to_string()) {
return ResolvedName::Local(name.to_string());
}
for ns in &self.namespaces {
if let Some(full) = ns.aliases.get(name) {
return ResolvedName::Local(full.clone());
}
}
let mut found_sources: Vec<String> = Vec::new();
for spec in &self.import_specs {
match spec {
ImportSpec::All(module_name) => {
found_sources.push(module_name.clone());
}
ImportSpec::Selective(module_name, selected) => {
if selected.contains(&name.to_string()) {
found_sources.push(module_name.clone());
}
}
ImportSpec::Hiding(module_name, hidden) => {
if !hidden.contains(&name.to_string()) {
found_sources.push(module_name.clone());
}
}
ImportSpec::Renaming(module_name, renamings) => {
for (from, to) in renamings {
if to == name {
return ResolvedName::Imported {
module: module_name.clone(),
name: from.clone(),
};
}
}
}
}
}
if !found_sources.is_empty() {
if found_sources.len() == 1 {
return ResolvedName::Imported {
module: found_sources[0].clone(),
name: name.to_string(),
};
}
return ResolvedName::Ambiguous(found_sources);
}
if !self.imports.is_empty() {
let mut import_sources: Vec<String> = Vec::new();
for imp in &self.imports {
import_sources.push(imp.clone());
}
if import_sources.len() == 1 {
return ResolvedName::Imported {
module: import_sources[0].clone(),
name: name.to_string(),
};
}
if import_sources.len() > 1 {
return ResolvedName::Ambiguous(import_sources);
}
}
ResolvedName::NotFound
}
fn declared_names(&self) -> Vec<String> {
let mut names = Vec::new();
for decl in &self.decls {
if let Some(name) = Self::decl_name(decl) {
names.push(name);
}
}
names
}
fn decl_name(decl: &Decl) -> Option<String> {
match decl {
Decl::Axiom { name, .. } => Some(name.clone()),
Decl::Definition { name, .. } => Some(name.clone()),
Decl::Theorem { name, .. } => Some(name.clone()),
Decl::Inductive { name, .. } => Some(name.clone()),
Decl::Namespace { name, .. } => Some(name.clone()),
Decl::Structure { name, .. } => Some(name.clone()),
Decl::ClassDecl { name, .. } => Some(name.clone()),
Decl::InstanceDecl {
name, class_name, ..
} => name.clone().or_else(|| Some(class_name.clone())),
Decl::SectionDecl { name, .. } => Some(name.clone()),
Decl::Mutual { .. } => None,
Decl::Derive { type_name, .. } => Some(type_name.clone()),
Decl::NotationDecl { name, .. } => Some(name.clone()),
Decl::Universe { .. } => None,
Decl::Import { .. }
| Decl::Variable { .. }
| Decl::Open { .. }
| Decl::Attribute { .. }
| Decl::HashCmd { .. } => None,
}
}
#[allow(dead_code)]
pub fn visible_names(&self) -> Vec<String> {
let mut names = self.declared_names();
for ns in &self.namespaces {
for alias in ns.aliases.keys() {
if !names.contains(alias) {
names.push(alias.clone());
}
}
}
names
}
#[allow(dead_code)]
pub fn exported_names(&self) -> Vec<String> {
match &self.export_spec {
Some(ExportSpec::All) => self.declared_names(),
Some(ExportSpec::Selective(selected)) => {
let declared = self.declared_names();
selected
.iter()
.filter(|n| declared.contains(n))
.cloned()
.collect()
}
None => {
if self.exports.is_empty() {
self.declared_names()
} else {
self.exports.clone()
}
}
}
}
#[allow(dead_code)]
pub fn add_namespace(&mut self, ns: NamespaceScope) {
self.namespaces.push(ns);
}
#[allow(dead_code)]
pub fn with_import_spec(&mut self, spec: ImportSpec) {
let module_name = match &spec {
ImportSpec::All(m) => m.clone(),
ImportSpec::Selective(m, _) => m.clone(),
ImportSpec::Hiding(m, _) => m.clone(),
ImportSpec::Renaming(m, _) => m.clone(),
};
if !self.imports.contains(&module_name) {
self.imports.push(module_name);
}
self.import_specs.push(spec);
}
#[allow(dead_code)]
pub fn set_visibility(&mut self, name: String, visibility: Visibility) {
self.visibility_map.insert(name, visibility);
}
#[allow(dead_code)]
pub fn get_visibility(&self, name: &str) -> Visibility {
self.visibility_map
.get(name)
.copied()
.unwrap_or(if self.config.private_by_default {
Visibility::Private
} else {
Visibility::Public
})
}
#[allow(dead_code)]
pub fn add_open(&mut self, module: String, scoped: bool) {
self.opens.push(OpenDirective { module, scoped });
}
#[allow(dead_code)]
pub fn get_opens(&self) -> Vec<String> {
self.opens.iter().map(|o| o.module.clone()).collect()
}
#[allow(dead_code)]
pub fn resolve_with_opens(&self, name: &str) -> ResolvedName {
if let ResolvedName::Local(n) = self.resolve_name(name) {
return ResolvedName::Local(n);
}
let mut found_sources = Vec::new();
for open in &self.opens {
found_sources.push(open.module.clone());
}
if !found_sources.is_empty() {
if found_sources.len() == 1 {
return ResolvedName::Imported {
module: found_sources[0].clone(),
name: name.to_string(),
};
}
return ResolvedName::Ambiguous(found_sources);
}
self.resolve_name(name)
}
#[allow(dead_code)]
pub fn all_visible_names(&self) -> Vec<NameVisibility> {
let mut result = Vec::new();
let declared = self.declared_names();
for name in declared {
let visibility = self.get_visibility(&name);
result.push(NameVisibility {
name,
visibility,
from_module: None,
});
}
for open in &self.opens {
result.push(NameVisibility {
name: format!("{}::*", open.module),
visibility: Visibility::Public,
from_module: Some(open.module.clone()),
});
}
result
}
#[allow(dead_code)]
pub fn is_accessible(&self, name: &str, from_same_module: bool) -> bool {
let visibility = self.get_visibility(name);
match visibility {
Visibility::Public => true,
Visibility::Private => from_same_module,
Visibility::Protected => true,
}
}
#[allow(dead_code)]
pub fn get_dependencies(&self) -> HashSet<String> {
let mut deps = HashSet::new();
deps.extend(self.imports.clone());
for open in &self.opens {
deps.insert(open.module.clone());
}
for spec in &self.import_specs {
let mod_name = match spec {
ImportSpec::All(m) => m.clone(),
ImportSpec::Selective(m, _) => m.clone(),
ImportSpec::Hiding(m, _) => m.clone(),
ImportSpec::Renaming(m, _) => m.clone(),
};
deps.insert(mod_name);
}
deps
}
#[allow(dead_code)]
pub fn imports_module(&self, other: &str) -> bool {
self.get_dependencies().contains(other)
}
#[allow(dead_code)]
pub fn resolve_selective_import(&self, _module: &str, selected: &[String]) -> Vec<String> {
selected.to_vec()
}
#[allow(dead_code)]
pub fn get_hidden_names(&self, module: &str) -> Vec<String> {
for spec in &self.import_specs {
if let ImportSpec::Hiding(m, hidden) = spec {
if m == module {
return hidden.clone();
}
}
}
Vec::new()
}
#[allow(dead_code)]
pub fn create_nested_namespace(&mut self, name: String, parent: NamespaceScope) {
let mut nested = NamespaceScope {
name,
opened: Vec::new(),
aliases: HashMap::new(),
visibility: HashMap::new(),
parent: Some(Box::new(parent)),
};
if let Some(ref p) = nested.parent {
nested.aliases.extend(p.aliases.clone());
}
self.namespaces.push(nested);
}
#[allow(dead_code)]
pub fn lookup_in_namespaces(&self, name: &str) -> Option<String> {
for ns in self.namespaces.iter().rev() {
if let Some(full) = ns.aliases.get(name) {
return Some(full.clone());
}
let mut current_parent = &ns.parent;
while let Some(ref parent) = current_parent {
if let Some(full) = parent.aliases.get(name) {
return Some(full.clone());
}
current_parent = &parent.parent;
}
}
None
}
#[allow(dead_code)]
pub fn namespace_names(&self, ns_name: &str) -> Vec<String> {
for ns in &self.namespaces {
if ns.name == ns_name {
return ns.aliases.keys().cloned().collect();
}
}
Vec::new()
}
#[allow(dead_code)]
pub fn set_default_visibility(&mut self, visibility: Visibility) {
match visibility {
Visibility::Private => self.config.private_by_default = true,
_ => self.config.private_by_default = false,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ResolvedName {
Local(String),
Imported {
module: String,
name: String,
},
Ambiguous(Vec<String>),
NotFound,
}
#[allow(dead_code)]
#[allow(missing_docs)]
#[derive(Debug, Clone)]
pub struct ModuleAttribute {
pub name: String,
pub arg: Option<String>,
}
impl ModuleAttribute {
#[allow(dead_code)]
pub fn new(name: &str) -> Self {
ModuleAttribute {
name: name.to_string(),
arg: None,
}
}
#[allow(dead_code)]
pub fn with_arg(mut self, arg: &str) -> Self {
self.arg = Some(arg.to_string());
self
}
#[allow(dead_code)]
pub fn format(&self) -> String {
if let Some(ref arg) = self.arg {
format!("@[{} {}]", self.name, arg)
} else {
format!("@[{}]", self.name)
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ExportSpec {
All,
Selective(Vec<String>),
}
#[derive(Debug, Clone)]
pub struct NameVisibility {
pub name: String,
pub visibility: Visibility,
pub from_module: Option<String>,
}
#[derive(Debug, Clone)]
pub struct ModuleDep {
pub name: String,
pub deps: Vec<String>,
}
#[allow(dead_code)]
#[allow(missing_docs)]
#[derive(Debug, Clone)]
pub struct ExportEntry {
pub module: String,
pub all: bool,
pub names: Vec<String>,
}
impl ExportEntry {
#[allow(dead_code)]
pub fn all(module: &str) -> Self {
ExportEntry {
module: module.to_string(),
all: true,
names: Vec::new(),
}
}
#[allow(dead_code)]
pub fn names(module: &str, names: Vec<&str>) -> Self {
ExportEntry {
module: module.to_string(),
all: false,
names: names.into_iter().map(|s| s.to_string()).collect(),
}
}
}
#[allow(dead_code)]
#[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ModuleVersion {
pub major: u32,
pub minor: u32,
pub patch: u32,
}
impl ModuleVersion {
#[allow(dead_code)]
pub fn new(major: u32, minor: u32, patch: u32) -> Self {
ModuleVersion {
major,
minor,
patch,
}
}
#[allow(dead_code)]
pub fn format(&self) -> String {
format!("{}.{}.{}", self.major, self.minor, self.patch)
}
}
#[derive(Debug, Clone)]
pub struct ModuleConfig {
pub allow_circular: bool,
pub private_by_default: bool,
pub allow_shadowing: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ImportSpec {
All(String),
Selective(String, Vec<String>),
Hiding(String, Vec<String>),
Renaming(String, Vec<(String, String)>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Visibility {
Public,
Private,
Protected,
}
#[derive(Debug, Clone)]
pub struct ScopedOpen {
pub module: String,
pub scope_var: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct OpenDirective {
pub module: String,
pub scoped: bool,
}
#[allow(dead_code)]
#[allow(missing_docs)]
pub struct DepGraphExt {
pub edges: Vec<ModuleDepExt>,
}
impl DepGraphExt {
#[allow(dead_code)]
pub fn new() -> Self {
DepGraphExt { edges: Vec::new() }
}
#[allow(dead_code)]
pub fn add(&mut self, dep: ModuleDepExt) {
self.edges.push(dep);
}
#[allow(dead_code)]
pub fn direct_deps(&self, module: &str) -> Vec<&str> {
self.edges
.iter()
.filter(|e| e.from == module && e.direct)
.map(|e| e.to.as_str())
.collect()
}
#[allow(dead_code)]
pub fn dependents(&self, module: &str) -> Vec<&str> {
self.edges
.iter()
.filter(|e| e.to == module)
.map(|e| e.from.as_str())
.collect()
}
#[allow(dead_code)]
pub fn has_self_import(&self) -> bool {
self.edges.iter().any(|e| e.from == e.to)
}
}
#[allow(dead_code)]
#[allow(missing_docs)]
#[derive(Debug, Clone)]
pub struct ModuleMetadata {
pub name: String,
pub version: Option<ModuleVersion>,
pub author: Option<String>,
}
impl ModuleMetadata {
#[allow(dead_code)]
pub fn new(name: &str) -> Self {
ModuleMetadata {
name: name.to_string(),
version: None,
author: None,
}
}
#[allow(dead_code)]
pub fn with_version(mut self, v: ModuleVersion) -> Self {
self.version = Some(v);
self
}
#[allow(dead_code)]
pub fn with_author(mut self, a: &str) -> Self {
self.author = Some(a.to_string());
self
}
}
#[allow(dead_code)]
#[allow(missing_docs)]
#[derive(Debug, Clone)]
pub struct ModuleDepExt {
pub from: String,
pub to: String,
pub direct: bool,
}
impl ModuleDepExt {
#[allow(dead_code)]
pub fn direct(from: &str, to: &str) -> Self {
ModuleDepExt {
from: from.to_string(),
to: to.to_string(),
direct: true,
}
}
#[allow(dead_code)]
pub fn transitive(from: &str, to: &str) -> Self {
ModuleDepExt {
from: from.to_string(),
to: to.to_string(),
direct: false,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum CycleDetectionResult {
NoCycles,
CycleFound(Vec<String>),
MultipleCycles(Vec<Vec<String>>),
}
pub struct ModuleRegistry {
modules: HashMap<String, Module>,
}
impl ModuleRegistry {
pub fn new() -> Self {
Self {
modules: HashMap::new(),
}
}
pub fn register(&mut self, module: Module) -> Result<(), String> {
let path = module.full_path();
match self.modules.entry(path.clone()) {
std::collections::hash_map::Entry::Occupied(_) => {
Err(format!("Module {} already registered", path))
}
std::collections::hash_map::Entry::Vacant(entry) => {
entry.insert(module);
Ok(())
}
}
}
pub fn get(&self, path: &str) -> Option<&Module> {
self.modules.get(path)
}
pub fn all_modules(&self) -> Vec<&Module> {
self.modules.values().collect()
}
pub fn contains(&self, path: &str) -> bool {
self.modules.contains_key(path)
}
#[allow(dead_code)]
pub fn get_mut(&mut self, path: &str) -> Option<&mut Module> {
self.modules.get_mut(path)
}
#[allow(dead_code)]
pub fn unregister(&mut self, path: &str) -> Option<Module> {
self.modules.remove(path)
}
#[allow(dead_code)]
pub fn resolve(&self, from: &str, name: &str) -> ResolvedName {
let module = match self.modules.get(from) {
Some(m) => m,
None => return ResolvedName::NotFound,
};
let local = module.resolve_name(name);
if matches!(local, ResolvedName::Local(_)) {
return local;
}
for spec in &module.import_specs {
match spec {
ImportSpec::All(mod_name) => {
if let Some(target) = self.modules.get(mod_name) {
let exported = target.exported_names();
if exported.contains(&name.to_string()) {
return ResolvedName::Imported {
module: mod_name.clone(),
name: name.to_string(),
};
}
}
}
ImportSpec::Selective(mod_name, selected) => {
if selected.contains(&name.to_string()) {
if let Some(target) = self.modules.get(mod_name) {
let exported = target.exported_names();
if exported.contains(&name.to_string()) {
return ResolvedName::Imported {
module: mod_name.clone(),
name: name.to_string(),
};
}
}
}
}
ImportSpec::Hiding(mod_name, hidden) => {
if !hidden.contains(&name.to_string()) {
if let Some(target) = self.modules.get(mod_name) {
let exported = target.exported_names();
if exported.contains(&name.to_string()) {
return ResolvedName::Imported {
module: mod_name.clone(),
name: name.to_string(),
};
}
}
}
}
ImportSpec::Renaming(mod_name, renamings) => {
for (from_name, to_name) in renamings {
if to_name == name {
return ResolvedName::Imported {
module: mod_name.clone(),
name: from_name.clone(),
};
}
}
}
}
}
for imp in &module.imports {
if let Some(target) = self.modules.get(imp) {
let exported = target.exported_names();
if exported.contains(&name.to_string()) {
return ResolvedName::Imported {
module: imp.clone(),
name: name.to_string(),
};
}
}
}
ResolvedName::NotFound
}
#[allow(dead_code)]
pub fn dependency_order(&self) -> Result<Vec<String>, Vec<String>> {
let deps = self.build_dep_graph();
let mut in_degree: HashMap<String, usize> = HashMap::new();
let mut adj: HashMap<String, Vec<String>> = HashMap::new();
for dep in &deps {
in_degree.entry(dep.name.clone()).or_insert(0);
adj.entry(dep.name.clone()).or_default();
for d in &dep.deps {
adj.entry(d.clone()).or_default();
in_degree.entry(d.clone()).or_insert(0);
*in_degree.entry(dep.name.clone()).or_insert(0) += 1;
}
}
let mut queue: Vec<String> = in_degree
.iter()
.filter(|(_, °)| deg == 0)
.map(|(name, _)| name.clone())
.collect();
queue.sort();
let mut result = Vec::new();
while let Some(node) = queue.pop() {
result.push(node.clone());
if let Some(neighbors) = adj.get(&node) {
for _neighbor in neighbors {}
let _ = neighbors;
}
}
let mut adj2: HashMap<String, Vec<String>> = HashMap::new();
let mut in_deg2: HashMap<String, usize> = HashMap::new();
for dep in &deps {
in_deg2.entry(dep.name.clone()).or_insert(0);
adj2.entry(dep.name.clone()).or_default();
for d in &dep.deps {
adj2.entry(d.clone()).or_default();
in_deg2.entry(d.clone()).or_insert(0);
adj2.entry(d.clone()).or_default().push(dep.name.clone());
*in_deg2.entry(dep.name.clone()).or_insert(0) += 1;
}
}
let mut queue2: Vec<String> = in_deg2
.iter()
.filter(|(_, °)| deg == 0)
.map(|(name, _)| name.clone())
.collect();
queue2.sort();
let mut sorted = Vec::new();
while let Some(node) = queue2.pop() {
sorted.push(node.clone());
if let Some(neighbors) = adj2.get(&node) {
for neighbor in neighbors.clone() {
if let Some(deg) = in_deg2.get_mut(&neighbor) {
*deg -= 1;
if *deg == 0 {
queue2.push(neighbor);
queue2.sort();
}
}
}
}
}
let total_nodes = in_deg2.len();
if sorted.len() == total_nodes {
Ok(sorted)
} else {
let cycle: Vec<String> = in_deg2
.iter()
.filter(|(_, °)| deg > 0)
.map(|(name, _)| name.clone())
.collect();
Err(cycle)
}
}
#[allow(dead_code)]
pub fn detect_cycles(&self) -> Vec<Vec<String>> {
let deps = self.build_dep_graph();
let mut visited: HashMap<String, u8> = HashMap::new();
let mut path: Vec<String> = Vec::new();
let mut cycles: Vec<Vec<String>> = Vec::new();
let dep_map: HashMap<String, Vec<String>> = deps
.iter()
.map(|d| (d.name.clone(), d.deps.clone()))
.collect();
for dep in &deps {
if visited.get(&dep.name).copied().unwrap_or(0) == 0 {
Self::dfs_cycle(&dep.name, &dep_map, &mut visited, &mut path, &mut cycles);
}
}
cycles
}
fn dfs_cycle(
node: &str,
adj: &HashMap<String, Vec<String>>,
visited: &mut HashMap<String, u8>,
path: &mut Vec<String>,
cycles: &mut Vec<Vec<String>>,
) {
visited.insert(node.to_string(), 1);
path.push(node.to_string());
if let Some(neighbors) = adj.get(node) {
for neighbor in neighbors {
let state = visited.get(neighbor).copied().unwrap_or(0);
if state == 1 {
if let Some(pos) = path.iter().position(|n| n == neighbor) {
let cycle: Vec<String> = path[pos..].to_vec();
cycles.push(cycle);
}
} else if state == 0 {
Self::dfs_cycle(neighbor, adj, visited, path, cycles);
}
}
}
path.pop();
visited.insert(node.to_string(), 2);
}
#[allow(dead_code)]
pub fn transitive_deps(&self, module: &str) -> Vec<String> {
let dep_map: HashMap<String, Vec<String>> = self
.build_dep_graph()
.into_iter()
.map(|d| (d.name, d.deps))
.collect();
let mut visited = Vec::new();
let mut stack = Vec::new();
if let Some(direct) = dep_map.get(module) {
stack.extend(direct.clone());
}
while let Some(dep) = stack.pop() {
if visited.contains(&dep) {
continue;
}
visited.push(dep.clone());
if let Some(transitive) = dep_map.get(&dep) {
for t in transitive {
if !visited.contains(t) {
stack.push(t.clone());
}
}
}
}
visited.sort();
visited
}
fn build_dep_graph(&self) -> Vec<ModuleDep> {
self.modules
.values()
.map(|m| {
let mut deps = m.imports.clone();
for spec in &m.import_specs {
let mod_name = match spec {
ImportSpec::All(n) => n.clone(),
ImportSpec::Selective(n, _) => n.clone(),
ImportSpec::Hiding(n, _) => n.clone(),
ImportSpec::Renaming(n, _) => n.clone(),
};
if !deps.contains(&mod_name) {
deps.push(mod_name);
}
}
ModuleDep {
name: m.full_path(),
deps,
}
})
.collect()
}
#[allow(dead_code)]
pub fn detect_mutual_imports(&self) -> Vec<(String, String)> {
let mut mutual = Vec::new();
for module in self.modules.values() {
for dep in &module.imports {
if let Some(dep_module) = self.modules.get(dep) {
if dep_module.imports.contains(&module.full_path()) {
let pair = (module.full_path(), dep.clone());
if !mutual.iter().any(|(a, b)| {
(a == &pair.0 && b == &pair.1) || (a == &pair.1 && b == &pair.0)
}) {
mutual.push(pair);
}
}
}
}
}
mutual
}
#[allow(dead_code)]
pub fn are_mutually_dependent(&self, a: &str, b: &str) -> bool {
if let (Some(mod_a), Some(mod_b)) = (self.modules.get(a), self.modules.get(b)) {
(mod_a.imports.contains(&b.to_string()) && mod_b.imports.contains(&a.to_string()))
|| (mod_a.imports_module(b) && mod_b.imports_module(a))
} else {
false
}
}
#[allow(dead_code)]
pub fn get_dependents(&self, module_name: &str) -> Vec<String> {
let mut dependents = Vec::new();
for module in self.modules.values() {
if module.imports_module(module_name) {
dependents.push(module.full_path());
}
}
dependents.sort();
dependents
}
#[allow(dead_code)]
pub fn get_all_dependencies(&self, module_name: &str) -> Vec<String> {
let mut all_deps = HashSet::new();
let mut stack = vec![module_name.to_string()];
let mut visited = HashSet::new();
while let Some(current) = stack.pop() {
if visited.contains(¤t) {
continue;
}
visited.insert(current.clone());
if let Some(module) = self.modules.get(¤t) {
for dep in module.get_dependencies() {
if !visited.contains(&dep) {
all_deps.insert(dep.clone());
stack.push(dep);
}
}
}
}
let mut result: Vec<_> = all_deps.into_iter().collect();
result.sort();
result
}
#[allow(dead_code)]
pub fn verify_consistency(&self) -> Result<(), Vec<String>> {
let mut errors = Vec::new();
for module in self.modules.values() {
for imported in &module.imports {
if !self.modules.contains_key(imported) {
errors.push(format!(
"Module {} imports non-existent module {}",
module.full_path(),
imported
));
}
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
#[allow(dead_code)]
pub fn get_reverse_dependencies(&self, module_name: &str) -> Vec<String> {
let mut affected = HashSet::new();
let mut stack = vec![module_name.to_string()];
let mut visited = HashSet::new();
while let Some(current) = stack.pop() {
if visited.contains(¤t) {
continue;
}
visited.insert(current.clone());
for dependent in self.get_dependents(¤t) {
if !visited.contains(&dependent) {
affected.insert(dependent.clone());
stack.push(dependent);
}
}
}
let mut result: Vec<_> = affected.into_iter().collect();
result.sort();
result
}
#[allow(dead_code)]
pub fn reachable_from(&self, module_name: &str) -> Vec<String> {
let mut reachable = Vec::new();
let mut visited = HashSet::new();
let mut stack = vec![module_name.to_string()];
while let Some(current) = stack.pop() {
if visited.contains(¤t) {
continue;
}
visited.insert(current.clone());
if let Some(module) = self.modules.get(¤t) {
for dep in module.get_dependencies() {
if !visited.contains(&dep) {
reachable.push(dep.clone());
stack.push(dep);
}
}
}
}
reachable.sort();
reachable
}
#[allow(dead_code)]
pub fn get_sccs(&self) -> Vec<Vec<String>> {
let mut sccs = Vec::new();
let deps = self.build_dep_graph();
let mut visited: HashMap<String, bool> = HashMap::new();
let mut rec_stack: HashMap<String, bool> = HashMap::new();
let mut current_scc = Vec::new();
for dep in &deps {
if !visited.contains_key(&dep.name) {
self.tarjan_visit(
&dep.name,
&deps,
&mut visited,
&mut rec_stack,
&mut current_scc,
&mut sccs,
);
}
}
sccs
}
fn tarjan_visit(
&self,
node: &str,
_graph: &[ModuleDep],
visited: &mut HashMap<String, bool>,
rec_stack: &mut HashMap<String, bool>,
_current_scc: &mut Vec<String>,
sccs: &mut Vec<Vec<String>>,
) {
visited.insert(node.to_string(), true);
rec_stack.insert(node.to_string(), true);
if let Some(module) = self.modules.get(node) {
for dep in &module.imports {
if !visited.get(dep).copied().unwrap_or(false) {
self.tarjan_visit(dep, _graph, visited, rec_stack, _current_scc, sccs);
} else if rec_stack.get(dep).copied().unwrap_or(false) {
sccs.push(vec![node.to_string(), dep.clone()]);
}
}
}
rec_stack.insert(node.to_string(), false);
}
#[allow(dead_code)]
pub fn get_statistics(&self) -> HashMap<String, usize> {
let mut stats = HashMap::new();
let mut total_imports = 0;
let mut total_opens = 0;
for module in self.modules.values() {
total_imports += module.imports.len();
total_opens += module.opens.len();
}
stats.insert("modules".to_string(), self.modules.len());
stats.insert("total_imports".to_string(), total_imports);
stats.insert("total_opens".to_string(), total_opens);
stats
}
#[allow(dead_code)]
pub fn get_public_interface(&self, module_name: &str) -> Vec<String> {
if let Some(module) = self.modules.get(module_name) {
module
.exported_names()
.into_iter()
.filter(|name| module.get_visibility(name) == Visibility::Public)
.collect()
} else {
Vec::new()
}
}
#[allow(dead_code)]
pub fn is_name_accessible(&self, name: &str, from_module: &str, to_module: &str) -> bool {
if let Some(target) = self.modules.get(to_module) {
let exported = target.exported_names();
if exported.contains(&name.to_string()) {
return target.is_accessible(name, from_module == to_module);
}
}
false
}
}
#[derive(Debug, Clone)]
pub struct NamespaceScope {
pub name: String,
pub opened: Vec<String>,
pub aliases: HashMap<String, String>,
pub visibility: HashMap<String, Visibility>,
pub parent: Option<Box<NamespaceScope>>,
}