use alloc::collections::BTreeMap;
use alloc::collections::btree_map::Entry as MapEntry;
use alloc::vec::Vec;
use source_lang::SourceId;
use symbol_lang::Symbol;
use crate::error::ResolveError;
use crate::id::ModuleId;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Visibility {
Public,
Private,
}
#[derive(Clone, Debug)]
enum Entry<T> {
Define {
visibility: Visibility,
value: T,
},
Import {
from: ModuleId,
},
}
#[derive(Clone, Debug)]
struct Module<T> {
name: Symbol,
source: SourceId,
entries: BTreeMap<Symbol, Entry<T>>,
}
#[derive(Clone, Debug)]
pub struct ModuleGraph<T> {
modules: Vec<Module<T>>,
}
impl<T> Default for ModuleGraph<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> ModuleGraph<T> {
#[must_use]
pub const fn new() -> Self {
Self {
modules: Vec::new(),
}
}
#[must_use]
pub fn with_capacity(modules: usize) -> Self {
Self {
modules: Vec::with_capacity(modules),
}
}
pub fn add_module(&mut self, name: Symbol, source: SourceId) -> ModuleId {
let index = self.modules.len();
debug_assert!(
index < u32::MAX as usize,
"module count exceeds u32 addressing"
);
self.modules.push(Module {
name,
source,
entries: BTreeMap::new(),
});
ModuleId::from_index(index as u32)
}
pub fn define(
&mut self,
module: ModuleId,
name: Symbol,
visibility: Visibility,
value: T,
) -> Result<(), ResolveError> {
let target = self.module_mut(module)?;
match target.entries.entry(name) {
MapEntry::Occupied(_) => Err(ResolveError::DuplicateName { module, name }),
MapEntry::Vacant(slot) => {
let _ = slot.insert(Entry::Define { visibility, value });
Ok(())
}
}
}
pub fn import(
&mut self,
into: ModuleId,
from: ModuleId,
name: Symbol,
) -> Result<(), ResolveError> {
if from.to_index() >= self.modules.len() {
return Err(ResolveError::UnknownModule(from));
}
let target = self.module_mut(into)?;
match target.entries.entry(name) {
MapEntry::Occupied(_) => Err(ResolveError::DuplicateName { module: into, name }),
MapEntry::Vacant(slot) => {
let _ = slot.insert(Entry::Import { from });
Ok(())
}
}
}
pub fn resolve(&self, module: ModuleId, name: Symbol) -> Result<&T, ResolveError> {
let source = self.module(module)?;
let from = match source.entries.get(&name) {
None => return Err(ResolveError::Unresolved { module, name }),
Some(Entry::Define { value, .. }) => return Ok(value),
Some(Entry::Import { from }) => *from,
};
let mut visited = Vec::new();
visited.push((module.to_u32(), name.as_u32()));
self.resolve_export(from, name, &mut visited)
}
#[must_use]
pub fn len(&self) -> usize {
self.modules.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.modules.is_empty()
}
#[must_use]
pub fn module_name(&self, module: ModuleId) -> Option<Symbol> {
self.modules.get(module.to_index()).map(|m| m.name)
}
#[must_use]
pub fn module_source(&self, module: ModuleId) -> Option<SourceId> {
self.modules.get(module.to_index()).map(|m| m.source)
}
fn module(&self, id: ModuleId) -> Result<&Module<T>, ResolveError> {
self.modules
.get(id.to_index())
.ok_or(ResolveError::UnknownModule(id))
}
fn module_mut(&mut self, id: ModuleId) -> Result<&mut Module<T>, ResolveError> {
self.modules
.get_mut(id.to_index())
.ok_or(ResolveError::UnknownModule(id))
}
fn resolve_export(
&self,
start: ModuleId,
name: Symbol,
visited: &mut Vec<(u32, u32)>,
) -> Result<&T, ResolveError> {
let mut module = start;
let terminal = loop {
let key = (module.to_u32(), name.as_u32());
if visited.contains(&key) {
return Err(ResolveError::ImportCycle { module, name });
}
visited.push(key);
match self.module(module)?.entries.get(&name) {
None => return Err(ResolveError::Unresolved { module, name }),
Some(Entry::Define {
visibility: Visibility::Public,
..
}) => break module,
Some(Entry::Define {
visibility: Visibility::Private,
..
}) => return Err(ResolveError::Private { module, name }),
Some(Entry::Import { from }) => module = *from,
}
};
match self.module(terminal)?.entries.get(&name) {
Some(Entry::Define { value, .. }) => Ok(value),
_ => Err(ResolveError::Unresolved {
module: terminal,
name,
}),
}
}
}
#[cfg(test)]
#[allow(
clippy::expect_used,
reason = "unwrapping a known-good setup is fine in tests"
)]
mod tests {
use intern_lang::Interner;
use source_lang::SourceMap;
use super::*;
fn fixture() -> (ModuleGraph<u32>, Interner, SourceMap) {
(ModuleGraph::new(), Interner::new(), SourceMap::new())
}
#[test]
fn test_add_module_assigns_sequential_stable_ids() {
let (mut g, mut n, mut s) = fixture();
let a = g.add_module(n.intern("a"), s.add("a", "").expect("fits"));
let b = g.add_module(n.intern("b"), s.add("b", "").expect("fits"));
assert_eq!(a.to_u32(), 0);
assert_eq!(b.to_u32(), 1);
assert_eq!(g.len(), 2);
assert_eq!(g.module_name(a), Some(n.intern("a")));
}
#[test]
fn test_define_then_resolve_returns_value() {
let (mut g, mut n, mut s) = fixture();
let m = g.add_module(n.intern("m"), s.add("m", "").expect("fits"));
let x = n.intern("x");
g.define(m, x, Visibility::Public, 99).expect("unique");
assert_eq!(g.resolve(m, x), Ok(&99));
}
#[test]
fn test_define_duplicate_in_same_module_is_error() {
let (mut g, mut n, mut s) = fixture();
let m = g.add_module(n.intern("m"), s.add("m", "").expect("fits"));
let x = n.intern("x");
g.define(m, x, Visibility::Public, 1)
.expect("first is unique");
assert_eq!(
g.define(m, x, Visibility::Public, 2),
Err(ResolveError::DuplicateName { module: m, name: x }),
);
}
#[test]
fn test_import_resolves_through_to_definition() {
let (mut g, mut n, mut s) = fixture();
let lib = g.add_module(n.intern("lib"), s.add("lib", "").expect("fits"));
let app = g.add_module(n.intern("app"), s.add("app", "").expect("fits"));
let item = n.intern("item");
g.define(lib, item, Visibility::Public, 7).expect("unique");
g.import(app, lib, item).expect("unique");
assert_eq!(g.resolve(app, item), Ok(&7));
}
#[test]
fn test_resolve_unknown_name_is_unresolved() {
let (mut g, mut n, mut s) = fixture();
let m = g.add_module(n.intern("m"), s.add("m", "").expect("fits"));
let gone = n.intern("gone");
assert_eq!(
g.resolve(m, gone),
Err(ResolveError::Unresolved {
module: m,
name: gone
}),
);
}
#[test]
fn test_private_definition_is_local_only() {
let (mut g, mut n, mut s) = fixture();
let lib = g.add_module(n.intern("lib"), s.add("lib", "").expect("fits"));
let app = g.add_module(n.intern("app"), s.add("app", "").expect("fits"));
let secret = n.intern("secret");
g.define(lib, secret, Visibility::Private, 5)
.expect("unique");
assert_eq!(g.resolve(lib, secret), Ok(&5));
g.import(app, lib, secret).expect("unique");
assert_eq!(
g.resolve(app, secret),
Err(ResolveError::Private {
module: lib,
name: secret
}),
);
}
#[test]
fn test_import_cycle_is_reported_not_looped() {
let (mut g, mut n, mut s) = fixture();
let a = g.add_module(n.intern("a"), s.add("a", "").expect("fits"));
let b = g.add_module(n.intern("b"), s.add("b", "").expect("fits"));
let x = n.intern("x");
g.import(a, b, x).expect("unique");
g.import(b, a, x).expect("unique");
assert!(matches!(
g.resolve(a, x),
Err(ResolveError::ImportCycle { .. })
));
}
#[test]
fn test_self_import_is_a_cycle() {
let (mut g, mut n, mut s) = fixture();
let a = g.add_module(n.intern("a"), s.add("a", "").expect("fits"));
let x = n.intern("x");
g.import(a, a, x).expect("unique");
assert!(matches!(
g.resolve(a, x),
Err(ResolveError::ImportCycle { .. })
));
}
#[test]
fn test_unknown_module_id_is_error_not_panic() {
let (mut g, mut n, mut s) = fixture();
let real = g.add_module(n.intern("a"), s.add("a", "").expect("fits"));
let mut other: ModuleGraph<u32> = ModuleGraph::new();
let mut s2 = SourceMap::new();
let foreign = other.add_module(n.intern("z"), s2.add("z", "").expect("fits"));
let _ = foreign;
let beyond = ModuleId::from_index(g.len() as u32 + 5);
let x = n.intern("x");
assert_eq!(
g.resolve(beyond, x),
Err(ResolveError::UnknownModule(beyond))
);
assert!(g.resolve(real, x).is_err());
}
#[test]
fn test_re_export_chain_resolves_to_origin() {
let (mut g, mut n, mut s) = fixture();
let c = g.add_module(n.intern("c"), s.add("c", "").expect("fits"));
let b = g.add_module(n.intern("b"), s.add("b", "").expect("fits"));
let a = g.add_module(n.intern("a"), s.add("a", "").expect("fits"));
let item = n.intern("item");
g.define(c, item, Visibility::Public, 123).expect("unique");
g.import(b, c, item).expect("unique"); g.import(a, b, item).expect("unique"); assert_eq!(g.resolve(a, item), Ok(&123));
}
}