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