Skip to main content

sim_kernel/library/
boot_codec.rs

1use crate::{
2    AbiVersion, CapabilityName, ClassId, CodecId, Datum, Error, Export, ExportKind, ExportRecord,
3    ExportState, FunctionId, LibId, LibManifest, LibTarget, MacroId, NumberDomainId, Result,
4    RuntimeId, ShapeId, SiteId, Symbol, Version,
5};
6
7use super::{
8    boot::{LibBootDependency, LibBootReceipt, LibSourceSpec, RegistryBootState},
9    loaders::{CatalogSource, LibSource},
10};
11
12const BOOT_STATE_FORMAT: &str = "registry-boot-state-v1";
13
14impl RegistryBootState {
15    /// Encodes the boot state as canonical SIM data.
16    pub fn to_datum(&self) -> Datum {
17        node(
18            "registry-boot-state",
19            vec![
20                ("format", Datum::String(BOOT_STATE_FORMAT.to_owned())),
21                (
22                    "receipts",
23                    Datum::List(self.receipts.iter().map(LibBootReceipt::to_datum).collect()),
24                ),
25            ],
26        )
27    }
28
29    /// Decodes a boot state from canonical SIM data.
30    pub fn from_datum(datum: &Datum) -> Result<Self> {
31        let fields = expect_node(datum, "registry-boot-state")?;
32        let format = expect_string(required_field(fields, "format")?)?;
33        if format != BOOT_STATE_FORMAT {
34            return Err(Error::Lib(format!(
35                "unsupported registry boot state {format}"
36            )));
37        }
38        let receipts = expect_list(required_field(fields, "receipts")?)?
39            .iter()
40            .map(LibBootReceipt::from_datum)
41            .collect::<Result<Vec<_>>>()?;
42        Ok(Self { receipts })
43    }
44}
45
46impl LibBootReceipt {
47    /// Encodes the receipt as canonical SIM data.
48    pub fn to_datum(&self) -> Datum {
49        node(
50            "lib-boot-receipt",
51            vec![
52                ("lib-id", u32_datum(self.lib_id.0)),
53                ("requested-source", self.requested_source.to_datum()),
54                ("resolved-source", self.resolved_source.to_datum()),
55                ("manifest", manifest_datum(&self.manifest)),
56                (
57                    "dependencies",
58                    Datum::List(
59                        self.dependencies
60                            .iter()
61                            .map(LibBootDependency::to_datum)
62                            .collect(),
63                    ),
64                ),
65                (
66                    "exports",
67                    Datum::List(self.exports.iter().map(export_record_datum).collect()),
68                ),
69            ],
70        )
71    }
72
73    /// Decodes the receipt from canonical SIM data.
74    pub fn from_datum(datum: &Datum) -> Result<Self> {
75        let fields = expect_node(datum, "lib-boot-receipt")?;
76        Ok(Self {
77            lib_id: LibId(expect_u32(required_field(fields, "lib-id")?)?),
78            requested_source: LibSourceSpec::from_datum(required_field(
79                fields,
80                "requested-source",
81            )?)?,
82            resolved_source: LibSourceSpec::from_datum(required_field(fields, "resolved-source")?)?,
83            manifest: manifest_from_datum(required_field(fields, "manifest")?)?,
84            dependencies: expect_list(required_field(fields, "dependencies")?)?
85                .iter()
86                .map(LibBootDependency::from_datum)
87                .collect::<Result<Vec<_>>>()?,
88            exports: expect_list(required_field(fields, "exports")?)?
89                .iter()
90                .map(export_record_from_datum)
91                .collect::<Result<Vec<_>>>()?,
92        })
93    }
94}
95
96impl LibBootDependency {
97    fn to_datum(&self) -> Datum {
98        node(
99            "lib-boot-dependency",
100            vec![
101                ("lib-id", u32_datum(self.lib_id.0)),
102                ("symbol", Datum::Symbol(self.symbol.clone())),
103            ],
104        )
105    }
106
107    fn from_datum(datum: &Datum) -> Result<Self> {
108        let fields = expect_node(datum, "lib-boot-dependency")?;
109        Ok(Self {
110            lib_id: LibId(expect_u32(required_field(fields, "lib-id")?)?),
111            symbol: expect_symbol(required_field(fields, "symbol")?)?.clone(),
112        })
113    }
114}
115
116impl LibSourceSpec {
117    /// Encodes the source spec as canonical SIM data.
118    pub fn to_datum(&self) -> Datum {
119        match self {
120            Self::Symbol(symbol) => {
121                source_node(Symbol::new("symbol"), Datum::Symbol(symbol.clone()))
122            }
123            Self::Open { kind, payload } => source_node(kind.clone(), payload.clone()),
124        }
125    }
126
127    /// Decodes the source spec from canonical SIM data.
128    pub fn from_datum(datum: &Datum) -> Result<Self> {
129        let fields = expect_node(datum, "lib-source")?;
130        let kind = expect_symbol(required_field(fields, "kind")?)?;
131        let value = required_field(fields, "value")?;
132        match kind.name.as_ref() {
133            "symbol" if kind.namespace.is_none() => Ok(Self::Symbol(expect_symbol(value)?.clone())),
134            _ => Ok(Self::Open {
135                kind: kind.clone(),
136                payload: value.clone(),
137            }),
138        }
139    }
140}
141
142impl From<CatalogSource> for LibSourceSpec {
143    fn from(source: CatalogSource) -> Self {
144        match source {
145            CatalogSource::Open { kind, payload } => Self::Open { kind, payload },
146        }
147    }
148}
149
150impl From<LibSourceSpec> for LibSource {
151    fn from(source: LibSourceSpec) -> Self {
152        match source {
153            LibSourceSpec::Symbol(symbol) => Self::Symbol(symbol),
154            LibSourceSpec::Open { kind, payload } => Self::Open { kind, payload },
155        }
156    }
157}
158
159impl TryFrom<LibSource> for LibSourceSpec {
160    type Error = Error;
161
162    fn try_from(source: LibSource) -> Result<Self> {
163        match source {
164            LibSource::Symbol(symbol) => Ok(Self::Symbol(symbol)),
165            LibSource::Open { kind, payload } => Ok(Self::Open { kind, payload }),
166            LibSource::Host(_) => Err(Error::Lib(
167                "host lib sources are live values, not boot data".to_owned(),
168            )),
169        }
170    }
171}
172
173fn manifest_datum(manifest: &LibManifest) -> Datum {
174    node(
175        "lib-manifest",
176        vec![
177            ("id", Datum::Symbol(manifest.id.clone())),
178            ("version", Datum::String(manifest.version.0.clone())),
179            ("abi-major", u16_datum(manifest.abi.major)),
180            ("abi-minor", u16_datum(manifest.abi.minor)),
181            ("target", Datum::Symbol(manifest.target.to_symbol())),
182            (
183                "requires",
184                Datum::List(manifest.requires.iter().map(dependency_datum).collect()),
185            ),
186            (
187                "capabilities",
188                Datum::List(
189                    manifest
190                        .capabilities
191                        .iter()
192                        .map(|capability| Datum::String(capability.as_str().to_owned()))
193                        .collect(),
194                ),
195            ),
196            (
197                "exports",
198                Datum::List(manifest.exports.iter().map(export_datum).collect()),
199            ),
200        ],
201    )
202}
203
204fn manifest_from_datum(datum: &Datum) -> Result<LibManifest> {
205    let fields = expect_node(datum, "lib-manifest")?;
206    Ok(LibManifest {
207        id: expect_symbol(required_field(fields, "id")?)?.clone(),
208        version: Version(expect_string(required_field(fields, "version")?)?.to_owned()),
209        abi: AbiVersion {
210            major: expect_u16(required_field(fields, "abi-major")?)?,
211            minor: expect_u16(required_field(fields, "abi-minor")?)?,
212        },
213        target: LibTarget::from_symbol(expect_symbol(required_field(fields, "target")?)?),
214        requires: expect_list(required_field(fields, "requires")?)?
215            .iter()
216            .map(dependency_from_datum)
217            .collect::<Result<Vec<_>>>()?,
218        capabilities: expect_list(required_field(fields, "capabilities")?)?
219            .iter()
220            .map(|datum| Ok(CapabilityName::new(expect_string(datum)?.to_owned())))
221            .collect::<Result<Vec<_>>>()?,
222        exports: expect_list(required_field(fields, "exports")?)?
223            .iter()
224            .map(export_from_datum)
225            .collect::<Result<Vec<_>>>()?,
226    })
227}
228
229fn dependency_datum(dependency: &crate::Dependency) -> Datum {
230    node(
231        "dependency",
232        vec![
233            ("id", Datum::Symbol(dependency.id.clone())),
234            (
235                "minimum-version",
236                dependency
237                    .minimum_version
238                    .as_ref()
239                    .map(|version| Datum::String(version.0.clone()))
240                    .unwrap_or(Datum::Nil),
241            ),
242        ],
243    )
244}
245
246fn dependency_from_datum(datum: &Datum) -> Result<crate::Dependency> {
247    let fields = expect_node(datum, "dependency")?;
248    let minimum_version = match required_field(fields, "minimum-version")? {
249        Datum::Nil => None,
250        other => Some(Version(expect_string(other)?.to_owned())),
251    };
252    Ok(crate::Dependency {
253        id: expect_symbol(required_field(fields, "id")?)?.clone(),
254        minimum_version,
255    })
256}
257
258fn export_datum(export: &Export) -> Datum {
259    let (kind, symbol, stable_id) = match export {
260        Export::Class { symbol, class_id } => {
261            (Symbol::new("class"), symbol, class_id.map(|id| id.0))
262        }
263        Export::Function {
264            symbol,
265            function_id,
266        } => (Symbol::new("function"), symbol, function_id.map(|id| id.0)),
267        Export::Macro { symbol, macro_id } => {
268            (Symbol::new("macro"), symbol, macro_id.map(|id| id.0))
269        }
270        Export::Shape { symbol, shape_id } => {
271            (Symbol::new("shape"), symbol, shape_id.map(|id| id.0))
272        }
273        Export::Codec { symbol, codec_id } => {
274            (Symbol::new("codec"), symbol, codec_id.map(|id| id.0))
275        }
276        Export::NumberDomain {
277            symbol,
278            number_domain_id,
279        } => (
280            Symbol::new("number-domain"),
281            symbol,
282            number_domain_id.map(|id| id.0),
283        ),
284        Export::Value { symbol } => (Symbol::new("value"), symbol, None),
285        Export::Site { symbol, runtime_id } => {
286            let stable_id = match runtime_id {
287                Some(RuntimeId::Site(id)) => Some(id.0),
288                _ => None,
289            };
290            (Symbol::new("site"), symbol, stable_id)
291        }
292        Export::Open { kind, symbol } => (kind.symbol().clone(), symbol, None),
293    };
294    node(
295        "export",
296        vec![
297            ("kind", Datum::Symbol(kind)),
298            ("symbol", Datum::Symbol(symbol.clone())),
299            ("stable-id", stable_id.map(u32_datum).unwrap_or(Datum::Nil)),
300        ],
301    )
302}
303
304fn export_from_datum(datum: &Datum) -> Result<Export> {
305    let fields = expect_node(datum, "export")?;
306    let kind = expect_symbol(required_field(fields, "kind")?)?;
307    let symbol = expect_symbol(required_field(fields, "symbol")?)?.clone();
308    let stable_id = optional_u32(required_field(fields, "stable-id")?)?;
309    match kind.name.as_ref() {
310        "class" if kind.namespace.is_none() => Ok(Export::Class {
311            symbol,
312            class_id: stable_id.map(ClassId),
313        }),
314        "function" if kind.namespace.is_none() => Ok(Export::Function {
315            symbol,
316            function_id: stable_id.map(FunctionId),
317        }),
318        "macro" if kind.namespace.is_none() => Ok(Export::Macro {
319            symbol,
320            macro_id: stable_id.map(MacroId),
321        }),
322        "shape" if kind.namespace.is_none() => Ok(Export::Shape {
323            symbol,
324            shape_id: stable_id.map(ShapeId),
325        }),
326        "codec" if kind.namespace.is_none() => Ok(Export::Codec {
327            symbol,
328            codec_id: stable_id.map(CodecId),
329        }),
330        "number-domain" if kind.namespace.is_none() => Ok(Export::NumberDomain {
331            symbol,
332            number_domain_id: stable_id.map(NumberDomainId),
333        }),
334        "value" if kind.namespace.is_none() => Ok(Export::Value { symbol }),
335        "site" if kind.namespace.is_none() => Ok(Export::Site {
336            symbol,
337            runtime_id: stable_id.map(|id| RuntimeId::Site(SiteId(id))),
338        }),
339        _ if stable_id.is_none() => Ok(Export::Open {
340            kind: ExportKind::new(kind.clone()),
341            symbol,
342        }),
343        _ => Err(Error::Lib(format!(
344            "open export kind {kind} cannot carry stable-id"
345        ))),
346    }
347}
348
349fn export_record_datum(record: &ExportRecord) -> Datum {
350    node(
351        "export-record",
352        vec![
353            ("kind", Datum::Symbol(record.kind.symbol().clone())),
354            ("symbol", Datum::Symbol(record.symbol.clone())),
355            ("state", export_state_datum(&record.state)),
356        ],
357    )
358}
359
360fn export_record_from_datum(datum: &Datum) -> Result<ExportRecord> {
361    let fields = expect_node(datum, "export-record")?;
362    Ok(ExportRecord {
363        kind: ExportKind::new(expect_symbol(required_field(fields, "kind")?)?.clone()),
364        symbol: expect_symbol(required_field(fields, "symbol")?)?.clone(),
365        state: export_state_from_datum(required_field(fields, "state")?)?,
366    })
367}
368
369fn export_state_datum(state: &ExportState) -> Datum {
370    match state {
371        ExportState::Resolved { id } => node(
372            "export-state",
373            vec![
374                ("kind", Datum::Symbol(Symbol::new("resolved"))),
375                ("runtime-id", runtime_id_datum(*id)),
376            ],
377        ),
378        ExportState::Declared => state_node("declared", Datum::Nil),
379        ExportState::Unsupported { reason } => {
380            state_node("unsupported", Datum::String(reason.clone()))
381        }
382        ExportState::Invalid { error } => state_node("invalid", Datum::String(error.clone())),
383    }
384}
385
386fn export_state_from_datum(datum: &Datum) -> Result<ExportState> {
387    let fields = expect_node(datum, "export-state")?;
388    let kind = expect_symbol(required_field(fields, "kind")?)?;
389    let value = fields
390        .iter()
391        .find_map(|(field, value)| (field.name.as_ref() == "value").then_some(value));
392    match kind.name.as_ref() {
393        "resolved" if kind.namespace.is_none() => Ok(ExportState::Resolved {
394            id: runtime_id_from_datum(required_field(fields, "runtime-id")?)?,
395        }),
396        "declared" if kind.namespace.is_none() => Ok(ExportState::Declared),
397        "unsupported" if kind.namespace.is_none() => Ok(ExportState::Unsupported {
398            reason: expect_string(value.unwrap_or(&Datum::Nil))?.to_owned(),
399        }),
400        "invalid" if kind.namespace.is_none() => Ok(ExportState::Invalid {
401            error: expect_string(value.unwrap_or(&Datum::Nil))?.to_owned(),
402        }),
403        _ => Err(Error::Lib(format!("unknown export state {kind}"))),
404    }
405}
406
407fn runtime_id_datum(id: RuntimeId) -> Datum {
408    let (kind, value) = match id {
409        RuntimeId::Class(id) => ("class", Some(id.0)),
410        RuntimeId::Function(id) => ("function", Some(id.0)),
411        RuntimeId::Macro(id) => ("macro", Some(id.0)),
412        RuntimeId::Shape(id) => ("shape", Some(id.0)),
413        RuntimeId::Codec(id) => ("codec", Some(id.0)),
414        RuntimeId::NumberDomain(id) => ("number-domain", Some(id.0)),
415        RuntimeId::Site(id) => ("site", Some(id.0)),
416        RuntimeId::Value => ("value", None),
417    };
418    node(
419        "runtime-id",
420        vec![
421            ("kind", Datum::Symbol(Symbol::new(kind))),
422            ("value", value.map(u32_datum).unwrap_or(Datum::Nil)),
423        ],
424    )
425}
426
427fn runtime_id_from_datum(datum: &Datum) -> Result<RuntimeId> {
428    let fields = expect_node(datum, "runtime-id")?;
429    let kind = expect_symbol(required_field(fields, "kind")?)?;
430    let value = optional_u32(required_field(fields, "value")?)?;
431    match kind.name.as_ref() {
432        "class" if kind.namespace.is_none() => {
433            Ok(RuntimeId::Class(ClassId(required_id(value, kind)?)))
434        }
435        "function" if kind.namespace.is_none() => {
436            Ok(RuntimeId::Function(FunctionId(required_id(value, kind)?)))
437        }
438        "macro" if kind.namespace.is_none() => {
439            Ok(RuntimeId::Macro(MacroId(required_id(value, kind)?)))
440        }
441        "shape" if kind.namespace.is_none() => {
442            Ok(RuntimeId::Shape(ShapeId(required_id(value, kind)?)))
443        }
444        "codec" if kind.namespace.is_none() => {
445            Ok(RuntimeId::Codec(CodecId(required_id(value, kind)?)))
446        }
447        "number-domain" if kind.namespace.is_none() => Ok(RuntimeId::NumberDomain(NumberDomainId(
448            required_id(value, kind)?,
449        ))),
450        "site" if kind.namespace.is_none() => {
451            Ok(RuntimeId::Site(SiteId(required_id(value, kind)?)))
452        }
453        "value" if kind.namespace.is_none() => Ok(RuntimeId::Value),
454        _ => Err(Error::Lib(format!("unknown runtime id kind {kind}"))),
455    }
456}
457
458fn required_id(value: Option<u32>, kind: &Symbol) -> Result<u32> {
459    value.ok_or_else(|| Error::Lib(format!("runtime id kind {kind} requires an id")))
460}
461
462fn source_node(kind: Symbol, value: Datum) -> Datum {
463    node(
464        "lib-source",
465        vec![("kind", Datum::Symbol(kind)), ("value", value)],
466    )
467}
468
469fn state_node(kind: &'static str, value: Datum) -> Datum {
470    node(
471        "export-state",
472        vec![("kind", Datum::Symbol(Symbol::new(kind))), ("value", value)],
473    )
474}
475
476fn node(tag: &'static str, fields: Vec<(&'static str, Datum)>) -> Datum {
477    Datum::Node {
478        tag: Symbol::qualified("library", tag),
479        fields: fields
480            .into_iter()
481            .map(|(field, value)| (Symbol::new(field), value))
482            .collect(),
483    }
484}
485
486fn expect_node<'a>(datum: &'a Datum, tag: &'static str) -> Result<&'a [(Symbol, Datum)]> {
487    let Datum::Node {
488        tag: actual,
489        fields,
490    } = datum
491    else {
492        return Err(Error::TypeMismatch {
493            expected: "datum node",
494            found: datum_kind(datum),
495        });
496    };
497    let expected = Symbol::qualified("library", tag);
498    if actual != &expected {
499        return Err(Error::Lib(format!(
500            "expected datum node {expected}, found {actual}"
501        )));
502    }
503    Ok(fields)
504}
505
506fn required_field<'a>(fields: &'a [(Symbol, Datum)], name: &'static str) -> Result<&'a Datum> {
507    fields
508        .iter()
509        .find_map(|(field, value)| {
510            (field.namespace.is_none() && field.name.as_ref() == name).then_some(value)
511        })
512        .ok_or_else(|| Error::Lib(format!("missing boot datum field {name}")))
513}
514
515fn expect_list(datum: &Datum) -> Result<&[Datum]> {
516    match datum {
517        Datum::List(items) => Ok(items),
518        other => Err(Error::TypeMismatch {
519            expected: "datum list",
520            found: datum_kind(other),
521        }),
522    }
523}
524
525fn expect_symbol(datum: &Datum) -> Result<&Symbol> {
526    match datum {
527        Datum::Symbol(symbol) => Ok(symbol),
528        other => Err(Error::TypeMismatch {
529            expected: "datum symbol",
530            found: datum_kind(other),
531        }),
532    }
533}
534
535fn expect_string(datum: &Datum) -> Result<&str> {
536    match datum {
537        Datum::String(value) => Ok(value),
538        other => Err(Error::TypeMismatch {
539            expected: "datum string",
540            found: datum_kind(other),
541        }),
542    }
543}
544
545fn u16_datum(value: u16) -> Datum {
546    Datum::String(value.to_string())
547}
548
549fn u32_datum(value: u32) -> Datum {
550    Datum::String(value.to_string())
551}
552
553fn expect_u16(datum: &Datum) -> Result<u16> {
554    expect_string(datum)?
555        .parse::<u16>()
556        .map_err(|err| Error::Lib(format!("invalid u16 boot datum: {err}")))
557}
558
559fn expect_u32(datum: &Datum) -> Result<u32> {
560    expect_string(datum)?
561        .parse::<u32>()
562        .map_err(|err| Error::Lib(format!("invalid u32 boot datum: {err}")))
563}
564
565fn optional_u32(datum: &Datum) -> Result<Option<u32>> {
566    match datum {
567        Datum::Nil => Ok(None),
568        other => expect_u32(other).map(Some),
569    }
570}
571
572fn datum_kind(datum: &Datum) -> &'static str {
573    match datum {
574        Datum::Nil => "datum nil",
575        Datum::Bool(_) => "datum bool",
576        Datum::Number(_) => "datum number",
577        Datum::Symbol(_) => "datum symbol",
578        Datum::String(_) => "datum string",
579        Datum::Bytes(_) => "datum bytes",
580        Datum::List(_) => "datum list",
581        Datum::Vector(_) => "datum vector",
582        Datum::Map(_) => "datum map",
583        Datum::Set(_) => "datum set",
584        Datum::Node { .. } => "datum node",
585    }
586}
587
588#[cfg(test)]
589mod tests;