use std::collections::{BTreeMap, BTreeSet};
use sim_kernel::{Diagnostic, Error, Result, Severity, Symbol};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum NamespaceKind {
Package,
Module,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum NamespaceBindingSource {
Local,
Import {
namespace: Symbol,
exported: Symbol,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NamespaceEntry {
name: Symbol,
target: Symbol,
source: NamespaceBindingSource,
}
impl NamespaceEntry {
pub fn new(name: Symbol, target: Symbol, source: NamespaceBindingSource) -> Self {
Self {
name,
target,
source,
}
}
pub fn name(&self) -> &Symbol {
&self.name
}
pub fn target(&self) -> &Symbol {
&self.target
}
pub fn source(&self) -> &NamespaceBindingSource {
&self.source
}
fn imported_as(&self, alias: Symbol, namespace: Symbol, exported: Symbol) -> Self {
Self {
name: alias,
target: self.target.clone(),
source: NamespaceBindingSource::Import {
namespace,
exported,
},
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ImportOptions {
rename: Option<Symbol>,
allow_shadow: bool,
}
impl ImportOptions {
pub fn new() -> Self {
Self::default()
}
pub fn rename(mut self, name: Symbol) -> Self {
self.rename = Some(name);
self
}
pub fn allow_shadow(mut self) -> Self {
self.allow_shadow = true;
self
}
}
#[derive(Clone, Debug)]
pub struct Namespace {
symbol: Symbol,
kind: NamespaceKind,
bindings: BTreeMap<Symbol, NamespaceEntry>,
exports: BTreeSet<Symbol>,
diagnostics: Vec<Diagnostic>,
}
impl Namespace {
pub fn package(symbol: Symbol) -> Self {
Self::new(symbol, NamespaceKind::Package)
}
pub fn module(symbol: Symbol) -> Self {
Self::new(symbol, NamespaceKind::Module)
}
pub fn new(symbol: Symbol, kind: NamespaceKind) -> Self {
Self {
symbol,
kind,
bindings: BTreeMap::new(),
exports: BTreeSet::new(),
diagnostics: Vec::new(),
}
}
pub fn symbol(&self) -> &Symbol {
&self.symbol
}
pub fn kind(&self) -> NamespaceKind {
self.kind
}
pub fn diagnostics(&self) -> &[Diagnostic] {
&self.diagnostics
}
pub fn define(&mut self, name: Symbol, target: Symbol) -> Result<()> {
self.insert_binding(
name.clone(),
NamespaceEntry::new(name, target, NamespaceBindingSource::Local),
false,
)
}
pub fn export(&mut self, name: Symbol) -> Result<()> {
if !self.bindings.contains_key(&name) {
return Err(Error::UnknownSymbol { symbol: name });
}
self.exports.insert(name);
Ok(())
}
pub fn import_from(
&mut self,
source: &Namespace,
exported: &Symbol,
options: ImportOptions,
) -> Result<()> {
let entry = source.exported_entry(exported)?;
let alias = options.rename.unwrap_or_else(|| exported.clone());
let imported = entry.imported_as(alias.clone(), source.symbol.clone(), exported.clone());
self.insert_binding(alias, imported, options.allow_shadow)
}
pub fn resolve(&self, name: &Symbol) -> Option<&NamespaceEntry> {
self.bindings.get(name)
}
pub fn exported_entry(&self, name: &Symbol) -> Result<&NamespaceEntry> {
if !self.exports.contains(name) {
return Err(Error::UnknownSymbol {
symbol: name.clone(),
});
}
self.bindings.get(name).ok_or_else(|| Error::UnknownSymbol {
symbol: name.clone(),
})
}
fn insert_binding(
&mut self,
name: Symbol,
entry: NamespaceEntry,
allow_shadow: bool,
) -> Result<()> {
if self.bindings.contains_key(&name) && !allow_shadow {
let diagnostic = shadow_conflict_diagnostic(&self.symbol, &name);
let message = diagnostic.message.clone();
self.diagnostics.push(diagnostic);
return Err(Error::Eval(message));
}
self.bindings.insert(name, entry);
Ok(())
}
}
pub fn namespace_shadow_conflict_symbol() -> Symbol {
Symbol::qualified("namespace", "shadow-conflict")
}
fn shadow_conflict_diagnostic(namespace: &Symbol, name: &Symbol) -> Diagnostic {
Diagnostic {
severity: Severity::Error,
message: format!("namespace {namespace} shadow conflict for {name}"),
source: None,
span: None,
code: Some(namespace_shadow_conflict_symbol()),
related: Vec::new(),
}
}