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}