Skip to main content

sim_kernel/library/
model.rs

1use std::sync::Arc;
2
3use crate::{
4    capability::CapabilityName,
5    env::Cx,
6    error::Result,
7    id::{
8        ClassId, CodecId, FunctionId, LibId, MacroId, NumberDomainId, RuntimeId, ShapeId, Symbol,
9    },
10    value::Value,
11};
12
13/// A library version string, compared component-wise by dotted numeric
14/// components, ignoring trailing zero components.
15#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
16pub struct Version(pub String);
17
18/// The ABI version a library targets, as a major/minor pair.
19#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
20pub struct AbiVersion {
21    /// Major ABI version; incompatible changes bump this.
22    pub major: u16,
23    /// Minor ABI version; backward-compatible additions bump this.
24    pub minor: u16,
25}
26
27/// The kind of artifact a library is loaded from.
28///
29/// Every variant is codec-agnostic: the kernel never names a concrete codec.
30/// A library defined by decoding source through some codec is
31/// [`LibTarget::CodecSource`], carrying that codec's [`Symbol`] as open data,
32/// so a new source dialect is expressible without editing this enum.
33#[derive(Clone, Debug, PartialEq, Eq)]
34pub enum LibTarget {
35    /// A native Rust library linked into the host.
36    Native,
37    /// A Wasm component loaded through the ABI transport.
38    WasmComponent,
39    /// A library defined by source decoded through the named codec (open data,
40    /// e.g. the symbol `codec/lisp`).
41    CodecSource(Symbol),
42    /// A library that contributes only data exports, no executable behavior.
43    DataOnly,
44    /// A library registered directly by the host (trusted).
45    HostRegistered,
46}
47
48impl LibTarget {
49    /// Renders the target as its stable serialized [`Symbol`].
50    ///
51    /// The closed variants serialize to unqualified tags (`native`,
52    /// `wasm-component`, `data-only`, `host-registered`); a
53    /// [`LibTarget::CodecSource`] serializes to its codec symbol verbatim
54    /// (e.g. `codec/lisp`), keeping the codec identity as open data rather than
55    /// a closed kernel string.
56    pub fn to_symbol(&self) -> Symbol {
57        match self {
58            LibTarget::Native => Symbol::new("native"),
59            LibTarget::WasmComponent => Symbol::new("wasm-component"),
60            LibTarget::CodecSource(codec) => codec.clone(),
61            LibTarget::DataOnly => Symbol::new("data-only"),
62            LibTarget::HostRegistered => Symbol::new("host-registered"),
63        }
64    }
65
66    /// Reconstructs a target from its serialized [`Symbol`].
67    ///
68    /// The unqualified closed tags map to their variants. The `lisp-source`
69    /// wire tag is accepted as a closed spelling for `CodecSource(codec/lisp)`.
70    /// Any other symbol is treated as an open [`LibTarget::CodecSource`].
71    pub fn from_symbol(symbol: &Symbol) -> Self {
72        if symbol.namespace.is_none() {
73            match symbol.name.as_ref() {
74                "native" => return LibTarget::Native,
75                "wasm-component" => return LibTarget::WasmComponent,
76                "data-only" => return LibTarget::DataOnly,
77                "host-registered" => return LibTarget::HostRegistered,
78                // Closed tag for the loadable Lisp codec source.
79                "lisp-source" => return LibTarget::CodecSource(Symbol::qualified("codec", "lisp")),
80                _ => {}
81            }
82        }
83        LibTarget::CodecSource(symbol.clone())
84    }
85}
86
87/// A dependency on another library, optionally pinned to a minimum version.
88#[derive(Clone, Debug, PartialEq, Eq)]
89pub struct Dependency {
90    /// Symbol of the required library.
91    pub id: Symbol,
92    /// Lowest acceptable version, if any.
93    pub minimum_version: Option<Version>,
94}
95
96/// A single export declared by a library manifest, by export kind.
97#[derive(Clone, Debug, PartialEq, Eq)]
98pub enum Export {
99    /// A class export; `class_id` is present once a stable id is reserved.
100    Class {
101        /// Symbol the class is exported under.
102        symbol: Symbol,
103        /// Reserved stable class id, if known.
104        class_id: Option<ClassId>,
105    },
106    /// A function export; `function_id` is present once a stable id is reserved.
107    Function {
108        /// Symbol the function is exported under.
109        symbol: Symbol,
110        /// Reserved stable function id, if known.
111        function_id: Option<FunctionId>,
112    },
113    /// A macro export; `macro_id` is present once a stable id is reserved.
114    Macro {
115        /// Symbol the macro is exported under.
116        symbol: Symbol,
117        /// Reserved stable macro id, if known.
118        macro_id: Option<MacroId>,
119    },
120    /// A shape export; `shape_id` is present once a stable id is reserved.
121    Shape {
122        /// Symbol the shape is exported under.
123        symbol: Symbol,
124        /// Reserved stable shape id, if known.
125        shape_id: Option<ShapeId>,
126    },
127    /// A codec export; `codec_id` is present once a stable id is reserved.
128    Codec {
129        /// Symbol the codec is exported under.
130        symbol: Symbol,
131        /// Reserved stable codec id, if known.
132        codec_id: Option<CodecId>,
133    },
134    /// A number-domain export; `number_domain_id` is present once reserved.
135    NumberDomain {
136        /// Symbol the number domain is exported under.
137        symbol: Symbol,
138        /// Reserved stable number-domain id, if known.
139        number_domain_id: Option<NumberDomainId>,
140    },
141    /// A plain value export.
142    Value {
143        /// Symbol the value is exported under.
144        symbol: Symbol,
145    },
146    /// An opaque placement-site export.
147    ///
148    /// The symbol is the placement key. The kernel stores only the runtime
149    /// value and stable id; libraries outside the kernel decide whether that
150    /// value behaves as an evaluation site.
151    Site {
152        /// Symbol the site is exported under.
153        symbol: Symbol,
154        /// Reserved opaque runtime id, if known.
155        runtime_id: Option<RuntimeId>,
156    },
157    /// An open export declaration with no kernel runtime-value model.
158    ///
159    /// Open declarations let manifests name export kinds that the kernel does
160    /// not resolve itself. They can commit as declared, unsupported, or invalid
161    /// export records, but they do not carry stable runtime ids.
162    Open {
163        /// Open export kind.
164        kind: ExportKind,
165        /// Symbol the export is declared under.
166        symbol: Symbol,
167    },
168}
169
170/// An open, symbol-keyed export kind tag.
171///
172/// Export kinds are carried as data rather than a closed kernel enum so that
173/// libraries can introduce new kinds without a kernel change; the well-known
174/// kinds are named by the associated constants.
175#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
176pub struct ExportKind(Symbol);
177
178impl ExportKind {
179    /// Well-known kind name for class exports.
180    pub const CLASS: &'static str = "class";
181    /// Well-known kind name for function exports.
182    pub const FUNCTION: &'static str = "function";
183    /// Well-known kind name for macro exports.
184    pub const MACRO: &'static str = "macro";
185    /// Well-known kind name for shape exports.
186    pub const SHAPE: &'static str = "shape";
187    /// Well-known kind name for codec exports.
188    pub const CODEC: &'static str = "codec";
189    /// Well-known kind name for number-domain exports.
190    pub const NUMBER_DOMAIN: &'static str = "number-domain";
191    /// Well-known kind name for plain value exports.
192    pub const VALUE: &'static str = "value";
193    /// Well-known kind name for opaque site exports.
194    pub const SITE: &'static str = "site";
195
196    /// Wraps an arbitrary symbol as an export kind.
197    pub fn new(symbol: Symbol) -> Self {
198        Self(symbol)
199    }
200
201    /// Builds an export kind from a well-known static kind name.
202    pub fn named(name: &'static str) -> Self {
203        Self(Symbol::new(name))
204    }
205
206    /// Returns the underlying symbol.
207    pub fn symbol(&self) -> &Symbol {
208        &self.0
209    }
210
211    /// Returns the unqualified kind name, or `None` if the symbol is namespaced.
212    pub fn name(&self) -> Option<&str> {
213        match &self.0.namespace {
214            Some(_) => None,
215            None => Some(self.0.name.as_ref()),
216        }
217    }
218
219    /// Maps a well-known kind to its static label for duplicate-export errors,
220    /// falling back to `"export"` for unrecognized kinds.
221    pub fn duplicate_error_kind(&self) -> &'static str {
222        match self.name() {
223            Some(Self::CLASS) => Self::CLASS,
224            Some(Self::FUNCTION) => Self::FUNCTION,
225            Some(Self::MACRO) => Self::MACRO,
226            Some(Self::SHAPE) => Self::SHAPE,
227            Some(Self::CODEC) => Self::CODEC,
228            Some(Self::NUMBER_DOMAIN) => Self::NUMBER_DOMAIN,
229            Some(Self::VALUE) => Self::VALUE,
230            Some(Self::SITE) => Self::SITE,
231            _ => "export",
232        }
233    }
234}
235
236/// The resolution state of an export within a loaded library.
237#[derive(Clone, Debug, PartialEq, Eq)]
238pub enum ExportState {
239    /// Resolved to a registry-assigned stable runtime id.
240    Resolved {
241        /// The runtime id the export resolved to.
242        id: RuntimeId,
243    },
244    /// Declared in the manifest but not yet resolved to behavior.
245    Declared,
246    /// Recognized but not supported in this host, with a human-readable reason.
247    Unsupported {
248        /// Why the export is unsupported here.
249        reason: String,
250    },
251    /// Rejected as invalid, with a human-readable error.
252    Invalid {
253        /// Why the export was rejected.
254        error: String,
255    },
256}
257
258/// One resolved export row: its kind, symbol, and resolution state.
259///
260/// `ExportRecord` is the open metadata surface the kernel prefers over closed
261/// enums for reporting what a library contributes (see the README "Library
262/// system" section).
263///
264/// # Examples
265///
266/// ```
267/// use sim_kernel::library::{Export, ExportKind, ExportRecord, ExportState};
268/// use sim_kernel::Symbol;
269///
270/// let export = Export::Value {
271///     symbol: Symbol::new("answer"),
272/// };
273/// let record: ExportRecord = export.declared_record();
274/// assert_eq!(record.kind, ExportKind::named(ExportKind::VALUE));
275/// assert_eq!(record.symbol, Symbol::new("answer"));
276/// assert_eq!(record.state, ExportState::Declared);
277/// ```
278#[derive(Clone, Debug, PartialEq, Eq)]
279pub struct ExportRecord {
280    /// The export kind.
281    pub kind: ExportKind,
282    /// The symbol the export is bound under.
283    pub symbol: Symbol,
284    /// The current resolution state of the export.
285    pub state: ExportState,
286}
287
288impl Export {
289    /// Returns the symbol this export is declared under.
290    pub fn symbol(&self) -> &Symbol {
291        match self {
292            Self::Class { symbol, .. }
293            | Self::Function { symbol, .. }
294            | Self::Macro { symbol, .. }
295            | Self::Shape { symbol, .. }
296            | Self::Codec { symbol, .. }
297            | Self::NumberDomain { symbol, .. }
298            | Self::Value { symbol }
299            | Self::Site { symbol, .. }
300            | Self::Open { symbol, .. } => symbol,
301        }
302    }
303
304    /// Returns the static kind label for this export.
305    pub fn kind(&self) -> &'static str {
306        match self {
307            Self::Class { .. } => "class",
308            Self::Function { .. } => "function",
309            Self::Macro { .. } => "macro",
310            Self::Shape { .. } => "shape",
311            Self::Codec { .. } => "codec",
312            Self::NumberDomain { .. } => "number-domain",
313            Self::Value { .. } => "value",
314            Self::Site { .. } => "site",
315            Self::Open { .. } => "export",
316        }
317    }
318
319    /// Returns this export's kind as an [`ExportKind`] tag.
320    pub fn kind_symbol(&self) -> ExportKind {
321        match self {
322            Self::Open { kind, .. } => kind.clone(),
323            _ => ExportKind::named(self.kind()),
324        }
325    }
326
327    /// Builds a [`Declared`](ExportState::Declared) [`ExportRecord`] for this
328    /// export.
329    pub fn declared_record(&self) -> ExportRecord {
330        ExportRecord {
331            kind: self.kind_symbol(),
332            symbol: self.symbol().clone(),
333            state: ExportState::Declared,
334        }
335    }
336}
337
338/// The self-description a library presents at load time.
339///
340/// The manifest names the library, its version and ABI, how it is loaded, what
341/// it requires, what capabilities it requests, and what it exports. The kernel
342/// validates and registers against this; the library supplies it.
343#[derive(Clone, Debug, PartialEq, Eq)]
344pub struct LibManifest {
345    /// Symbol identifying the library.
346    pub id: Symbol,
347    /// The library's version.
348    pub version: Version,
349    /// The ABI version the library targets.
350    pub abi: AbiVersion,
351    /// How the library is loaded.
352    pub target: LibTarget,
353    /// Other libraries this one depends on.
354    pub requires: Vec<Dependency>,
355    /// Capabilities the library requests at load time.
356    pub capabilities: Vec<CapabilityName>,
357    /// The exports the library declares.
358    pub exports: Vec<Export>,
359}
360
361impl LibManifest {
362    /// Returns a [`Declared`](ExportState::Declared) [`ExportRecord`] for each
363    /// declared export.
364    ///
365    /// # Examples
366    ///
367    /// ```
368    /// use sim_kernel::library::{
369    ///     AbiVersion, Export, ExportKind, LibManifest, LibTarget, Version,
370    /// };
371    /// use sim_kernel::Symbol;
372    ///
373    /// let manifest = LibManifest {
374    ///     id: Symbol::new("demo"),
375    ///     version: Version("0.1.0".to_owned()),
376    ///     abi: AbiVersion { major: 0, minor: 1 },
377    ///     target: LibTarget::HostRegistered,
378    ///     requires: Vec::new(),
379    ///     capabilities: Vec::new(),
380    ///     exports: vec![Export::Value { symbol: Symbol::new("answer") }],
381    /// };
382    ///
383    /// let records = manifest.declared_export_records();
384    /// assert_eq!(records.len(), 1);
385    /// assert_eq!(records[0].kind, ExportKind::named(ExportKind::VALUE));
386    /// ```
387    pub fn declared_export_records(&self) -> Vec<ExportRecord> {
388        self.exports.iter().map(Export::declared_record).collect()
389    }
390}
391
392/// A library that has been loaded and committed into the [`Registry`].
393///
394/// [`Registry`]: crate::library::Registry
395#[derive(Clone, Debug, PartialEq, Eq)]
396pub struct LoadedLib {
397    /// The stable id assigned at load time.
398    pub id: LibId,
399    /// The manifest the library was loaded from.
400    pub manifest: LibManifest,
401    /// The resolved export records produced during load.
402    pub exports: Vec<ExportRecord>,
403    /// Whether the library was loaded as trusted (host-registered).
404    pub trusted: bool,
405}
406
407/// The outcome of running a library-supplied [`Test`].
408#[derive(Clone, Debug, PartialEq, Eq)]
409pub struct TestReport {
410    /// Symbol naming the test.
411    pub name: Symbol,
412    /// Whether the test passed.
413    pub passed: bool,
414    /// Optional human-readable detail (e.g. a failure message).
415    pub detail: Option<String>,
416    /// The mode the test ran under.
417    pub mode: Symbol,
418    /// Events recorded while running the test.
419    pub events: Vec<Value>,
420    /// The effect produced by the test, if any.
421    pub effect: Option<Value>,
422    /// A shape-level report value, if the test produced one.
423    pub shape_report: Option<Value>,
424    /// Whether the test was skipped rather than run.
425    pub skipped: bool,
426}
427
428impl TestReport {
429    /// Builds a report from a pass/fail result with default (unknown) mode and
430    /// no events.
431    pub fn from_result(name: Symbol, passed: bool, detail: Option<String>) -> Self {
432        Self {
433            name,
434            passed,
435            detail,
436            mode: Symbol::new("unknown"),
437            events: Vec::new(),
438            effect: None,
439            shape_report: None,
440            skipped: false,
441        }
442    }
443
444    /// Builds a report marking the named test as skipped.
445    pub fn skipped(name: Symbol, detail: Option<String>) -> Self {
446        Self {
447            name,
448            passed: false,
449            detail,
450            mode: Symbol::new("unknown"),
451            events: Vec::new(),
452            effect: None,
453            shape_report: None,
454            skipped: true,
455        }
456    }
457}
458
459/// A library-supplied test the registry can hold and run.
460///
461/// The kernel defines the contract; the test body is library behavior.
462pub trait Test: Send + Sync {
463    /// Symbol naming this test.
464    fn symbol(&self) -> Symbol;
465    /// Symbol of the library that owns this test.
466    fn lib(&self) -> Symbol;
467    /// Produces a value describing this test without running it.
468    fn describe(&self, cx: &mut Cx) -> Result<Value>;
469    /// Runs the test and returns its [`TestReport`].
470    fn run(&self, cx: &mut Cx) -> Result<TestReport>;
471}
472
473/// A registered test with its owning library and the subjects it covers.
474#[derive(Clone)]
475pub struct RegisteredTest {
476    /// Symbol naming the test.
477    pub symbol: Symbol,
478    /// Symbol of the owning library.
479    pub lib: Symbol,
480    /// The test implementation.
481    pub test: Arc<dyn Test>,
482    /// Symbols of the exports this test exercises.
483    pub subjects: Vec<Symbol>,
484}