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 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 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 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 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 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 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;