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}