Skip to main content

sim_lib_namespace/
namespace.rs

1//! The namespace value: a scope of symbol bindings with exports and imports.
2//!
3//! The kernel supplies the [`Symbol`] and [`Diagnostic`] contracts; this module
4//! supplies the concrete namespace organ behavior -- interning local bindings,
5//! marking exports, and importing exported bindings (with optional rename and
6//! shadow control) from one namespace into another. See the crate
7//! [`README`](https://docs.rs/sim-runtime) for the constellation framing.
8
9use std::collections::{BTreeMap, BTreeSet};
10
11use sim_kernel::{Diagnostic, Error, Result, Severity, Symbol};
12
13/// Whether a [`Namespace`] is a package or a module scope.
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub enum NamespaceKind {
16    /// A package: a top-level namespace that other namespaces import from.
17    Package,
18    /// A module: a nested scope that imports and re-resolves bindings.
19    Module,
20}
21
22/// Where a [`NamespaceEntry`] binding came from.
23#[derive(Clone, Debug, PartialEq, Eq)]
24pub enum NamespaceBindingSource {
25    /// Defined directly in this namespace.
26    Local,
27    /// Imported from another namespace's export.
28    Import {
29        /// The source namespace symbol the binding was imported from.
30        namespace: Symbol,
31        /// The exported name in the source namespace.
32        exported: Symbol,
33    },
34}
35
36/// One binding in a [`Namespace`]: a name, its resolution target, and its source.
37#[derive(Clone, Debug, PartialEq, Eq)]
38pub struct NamespaceEntry {
39    name: Symbol,
40    target: Symbol,
41    source: NamespaceBindingSource,
42}
43
44impl NamespaceEntry {
45    /// Construct an entry binding `name` to `target` with the given `source`.
46    pub fn new(name: Symbol, target: Symbol, source: NamespaceBindingSource) -> Self {
47        Self {
48            name,
49            target,
50            source,
51        }
52    }
53
54    /// The name this binding is reached by within its namespace.
55    pub fn name(&self) -> &Symbol {
56        &self.name
57    }
58
59    /// The symbol this binding resolves to.
60    pub fn target(&self) -> &Symbol {
61        &self.target
62    }
63
64    /// Whether this binding is local or imported, and from where.
65    pub fn source(&self) -> &NamespaceBindingSource {
66        &self.source
67    }
68
69    fn imported_as(&self, alias: Symbol, namespace: Symbol, exported: Symbol) -> Self {
70        Self {
71            name: alias,
72            target: self.target.clone(),
73            source: NamespaceBindingSource::Import {
74                namespace,
75                exported,
76            },
77        }
78    }
79}
80
81/// Options controlling a single [`Namespace::import_from`] call.
82///
83/// Built fluently from [`ImportOptions::new`]; defaults to no rename and no
84/// shadowing (an import over an existing binding is a conflict).
85#[derive(Clone, Debug, Default, PartialEq, Eq)]
86pub struct ImportOptions {
87    rename: Option<Symbol>,
88    allow_shadow: bool,
89}
90
91impl ImportOptions {
92    /// Default options: import under the exported name, reject shadowing.
93    pub fn new() -> Self {
94        Self::default()
95    }
96
97    /// Import the binding under `name` instead of its exported name.
98    pub fn rename(mut self, name: Symbol) -> Self {
99        self.rename = Some(name);
100        self
101    }
102
103    /// Allow the import to overwrite an existing binding of the same name.
104    pub fn allow_shadow(mut self) -> Self {
105        self.allow_shadow = true;
106        self
107    }
108}
109
110/// A named scope of symbol bindings: the core value of the namespace organ.
111///
112/// A namespace holds local and imported [`NamespaceEntry`] bindings, a set of
113/// exported names, and any [`Diagnostic`]s raised while building it. Bindings
114/// are ordered by symbol so resolution and iteration are deterministic. Imports
115/// flow from one namespace's exports into another via
116/// [`Namespace::import_from`].
117///
118/// # Examples
119///
120/// ```
121/// use sim_kernel::Symbol;
122/// use sim_lib_namespace::{ImportOptions, Namespace, NamespaceBindingSource};
123///
124/// let mut source = Namespace::package(Symbol::qualified("pkg", "sequence"));
125/// source
126///     .define(Symbol::new("map"), Symbol::qualified("sequence", "map.v1"))
127///     .unwrap();
128/// source.export(Symbol::new("map")).unwrap();
129///
130/// let mut user = Namespace::module(Symbol::qualified("module", "user"));
131/// user.import_from(
132///     &source,
133///     &Symbol::new("map"),
134///     ImportOptions::new().rename(Symbol::new("seq-map")),
135/// )
136/// .unwrap();
137///
138/// let entry = user.resolve(&Symbol::new("seq-map")).unwrap();
139/// assert_eq!(entry.target(), &Symbol::qualified("sequence", "map.v1"));
140/// assert_eq!(
141///     entry.source(),
142///     &NamespaceBindingSource::Import {
143///         namespace: Symbol::qualified("pkg", "sequence"),
144///         exported: Symbol::new("map"),
145///     }
146/// );
147/// ```
148#[derive(Clone, Debug)]
149pub struct Namespace {
150    symbol: Symbol,
151    kind: NamespaceKind,
152    bindings: BTreeMap<Symbol, NamespaceEntry>,
153    exports: BTreeSet<Symbol>,
154    diagnostics: Vec<Diagnostic>,
155}
156
157impl Namespace {
158    /// Create an empty package namespace named `symbol`.
159    pub fn package(symbol: Symbol) -> Self {
160        Self::new(symbol, NamespaceKind::Package)
161    }
162
163    /// Create an empty module namespace named `symbol`.
164    pub fn module(symbol: Symbol) -> Self {
165        Self::new(symbol, NamespaceKind::Module)
166    }
167
168    /// Create an empty namespace named `symbol` of the given `kind`.
169    pub fn new(symbol: Symbol, kind: NamespaceKind) -> Self {
170        Self {
171            symbol,
172            kind,
173            bindings: BTreeMap::new(),
174            exports: BTreeSet::new(),
175            diagnostics: Vec::new(),
176        }
177    }
178
179    /// The symbol that names this namespace.
180    pub fn symbol(&self) -> &Symbol {
181        &self.symbol
182    }
183
184    /// Whether this namespace is a package or a module.
185    pub fn kind(&self) -> NamespaceKind {
186        self.kind
187    }
188
189    /// Diagnostics accumulated while building this namespace (e.g. shadow conflicts).
190    pub fn diagnostics(&self) -> &[Diagnostic] {
191        &self.diagnostics
192    }
193
194    /// Define a local binding from `name` to `target` in this namespace.
195    ///
196    /// # Errors
197    ///
198    /// Returns an error and records a diagnostic if `name` is already bound.
199    pub fn define(&mut self, name: Symbol, target: Symbol) -> Result<()> {
200        self.insert_binding(
201            name.clone(),
202            NamespaceEntry::new(name, target, NamespaceBindingSource::Local),
203            false,
204        )
205    }
206
207    /// Mark an existing binding `name` as exported so others may import it.
208    ///
209    /// # Errors
210    ///
211    /// Returns [`Error::UnknownSymbol`](sim_kernel::Error::UnknownSymbol) if
212    /// `name` is not bound in this namespace.
213    pub fn export(&mut self, name: Symbol) -> Result<()> {
214        if !self.bindings.contains_key(&name) {
215            return Err(Error::UnknownSymbol { symbol: name });
216        }
217        self.exports.insert(name);
218        Ok(())
219    }
220
221    /// Import an exported binding from `source` into this namespace.
222    ///
223    /// The imported entry keeps the source's resolution target but records an
224    /// [`NamespaceBindingSource::Import`] origin. `options` may rename the
225    /// binding or allow it to shadow an existing one.
226    ///
227    /// # Errors
228    ///
229    /// Returns an error if `exported` is not exported by `source`, or if the
230    /// destination name is already bound and shadowing was not allowed.
231    pub fn import_from(
232        &mut self,
233        source: &Namespace,
234        exported: &Symbol,
235        options: ImportOptions,
236    ) -> Result<()> {
237        let entry = source.exported_entry(exported)?;
238        let alias = options.rename.unwrap_or_else(|| exported.clone());
239        let imported = entry.imported_as(alias.clone(), source.symbol.clone(), exported.clone());
240        self.insert_binding(alias, imported, options.allow_shadow)
241    }
242
243    /// Look up the binding for `name`, returning `None` if it is unbound.
244    pub fn resolve(&self, name: &Symbol) -> Option<&NamespaceEntry> {
245        self.bindings.get(name)
246    }
247
248    /// Look up the binding for `name`, requiring that it is also exported.
249    ///
250    /// # Errors
251    ///
252    /// Returns [`Error::UnknownSymbol`](sim_kernel::Error::UnknownSymbol) if
253    /// `name` is not exported (or not bound) by this namespace.
254    pub fn exported_entry(&self, name: &Symbol) -> Result<&NamespaceEntry> {
255        if !self.exports.contains(name) {
256            return Err(Error::UnknownSymbol {
257                symbol: name.clone(),
258            });
259        }
260        self.bindings.get(name).ok_or_else(|| Error::UnknownSymbol {
261            symbol: name.clone(),
262        })
263    }
264
265    fn insert_binding(
266        &mut self,
267        name: Symbol,
268        entry: NamespaceEntry,
269        allow_shadow: bool,
270    ) -> Result<()> {
271        if self.bindings.contains_key(&name) && !allow_shadow {
272            let diagnostic = shadow_conflict_diagnostic(&self.symbol, &name);
273            let message = diagnostic.message.clone();
274            self.diagnostics.push(diagnostic);
275            return Err(Error::Eval(message));
276        }
277        self.bindings.insert(name, entry);
278        Ok(())
279    }
280}
281
282/// The diagnostic code attached to a namespace shadow-conflict diagnostic.
283pub fn namespace_shadow_conflict_symbol() -> Symbol {
284    Symbol::qualified("namespace", "shadow-conflict")
285}
286
287fn shadow_conflict_diagnostic(namespace: &Symbol, name: &Symbol) -> Diagnostic {
288    Diagnostic {
289        severity: Severity::Error,
290        message: format!("namespace {namespace} shadow conflict for {name}"),
291        source: None,
292        span: None,
293        code: Some(namespace_shadow_conflict_symbol()),
294        related: Vec::new(),
295    }
296}