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}