use std::collections::HashMap;
use std::sync::Arc;
use dashmap::DashMap;
use crate::atom::Atom;
use crate::error::ExecError;
use crate::loader::{Instruction, LambdaEntry, LineInfo, Literal};
use crate::native::NativeEntry;
#[derive(Copy, Clone, Debug)]
pub enum ResolvedImportTarget {
Code {
module: Atom,
label: u32,
},
Native(NativeEntry),
}
#[derive(Copy, Clone, Debug)]
pub struct ResolvedImport {
pub module: Atom,
pub function: Atom,
pub arity: u8,
pub target: ResolvedImportTarget,
}
#[derive(Clone, Debug)]
pub struct Module {
pub name: Atom,
pub exports: HashMap<(Atom, u8), u32>,
pub code: Vec<Instruction>,
pub literals: Vec<Literal>,
pub resolved_imports: Vec<ResolvedImport>,
pub lambdas: Vec<LambdaEntry>,
pub string_table: Vec<u8>,
pub line_info: Vec<LineInfo>,
}
#[derive(Clone, Debug)]
pub struct CodePointer {
pub module: Arc<Module>,
pub label: u32,
}
impl PartialEq for CodePointer {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.module, &other.module) && self.label == other.label
}
}
impl Eq for CodePointer {}
#[derive(Debug, Default)]
pub struct ModuleRegistry {
modules: DashMap<Atom, Arc<Module>>,
}
impl ModuleRegistry {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn insert(&self, module: Module) -> Arc<Module> {
self.insert_arc(Arc::new(module))
}
pub fn insert_arc(&self, module: Arc<Module>) -> Arc<Module> {
self.modules.insert(module.name, Arc::clone(&module));
module
}
#[must_use]
pub fn lookup(&self, name: Atom) -> Option<Arc<Module>> {
self.modules
.get(&name)
.map(|entry| Arc::clone(entry.value()))
}
pub fn lookup_mfa(
&self,
module: Atom,
function: Atom,
arity: u8,
) -> Result<CodePointer, ExecError> {
let loaded = self.lookup(module).ok_or(ExecError::Undef {
module,
function,
arity,
})?;
let label = loaded
.exports
.get(&(function, arity))
.copied()
.ok_or(ExecError::Undef {
module,
function,
arity,
})?;
Ok(CodePointer {
module: loaded,
label,
})
}
}
#[cfg(test)]
mod tests {
use super::{Module, ModuleRegistry};
use crate::atom::AtomTable;
use crate::error::ExecError;
fn empty_module(name: crate::atom::Atom) -> Module {
Module {
name,
exports: std::collections::HashMap::new(),
code: Vec::new(),
literals: Vec::new(),
resolved_imports: Vec::new(),
lambdas: Vec::new(),
string_table: Vec::new(),
line_info: Vec::new(),
}
}
#[test]
fn registry_stores_and_replaces_modules_by_name() {
let atoms = AtomTable::new();
let module_name = atoms.intern("sample");
let registry = ModuleRegistry::new();
let first = registry.insert(empty_module(module_name));
let mut replacement = empty_module(module_name);
replacement.code.push(crate::loader::Instruction::Return);
let second = registry.insert(replacement);
assert!(std::sync::Arc::ptr_eq(
®istry.lookup(module_name).expect("module loaded"),
&second
));
assert!(!std::sync::Arc::ptr_eq(&first, &second));
}
#[test]
fn registry_lookup_unloaded_module_returns_none() {
let atoms = AtomTable::new();
let registry = ModuleRegistry::new();
assert!(registry.lookup(atoms.intern("missing")).is_none());
}
#[test]
fn lookup_mfa_returns_code_pointer_for_export() {
let atoms = AtomTable::new();
let module_name = atoms.intern("sample");
let function = atoms.intern("main");
let registry = ModuleRegistry::new();
let mut module = empty_module(module_name);
module.exports.insert((function, 0), 7);
registry.insert(module);
let pointer = registry
.lookup_mfa(module_name, function, 0)
.expect("exported function");
assert_eq!(pointer.label, 7);
assert_eq!(pointer.module.name, module_name);
}
#[test]
fn lookup_mfa_reports_undef_for_missing_targets() {
let atoms = AtomTable::new();
let module_name = atoms.intern("sample");
let function = atoms.intern("main");
let other = atoms.intern("other");
let registry = ModuleRegistry::new();
registry.insert(empty_module(module_name));
assert!(matches!(
registry.lookup_mfa(other, function, 0),
Err(ExecError::Undef {
module,
function: undef_function,
arity: 0,
}) if module == other && undef_function == function
));
assert!(matches!(
registry.lookup_mfa(module_name, function, 0),
Err(ExecError::Undef {
module,
function: undef_function,
arity: 0,
}) if module == module_name && undef_function == function
));
assert!(matches!(
registry.lookup_mfa(module_name, function, 1),
Err(ExecError::Undef {
module,
function: undef_function,
arity: 1,
}) if module == module_name && undef_function == function
));
}
}