Skip to main content

sim_codec/implementation/
domain.rs

1//! Builder for domain codec libs.
2//!
3//! A "domain codec" lib (`codec:chat`, `codec:scene`, `codec:intent`, ...) was
4//! ~120 lines of near-identical scaffold: a UTF-8 `input_text` helper, a
5//! `Decoder`+`Encoder`, a `LibManifest` with one `Export::Codec`, and a
6//! `CodecRuntime` with ~10 fields mostly `None`. This collapses that to a few
7//! lines of glue: implement `Decoder`+`Encoder`, then build a [`DomainCodecLib`].
8
9use std::sync::Arc;
10
11use sim_kernel::{
12    AbiVersion, CodecId, DefaultFactory, Dependency, Error, Export, Factory, Lib, LibManifest,
13    LibTarget, Linker, LoadCx, Result, ShapeRef, Symbol, Version,
14};
15
16use crate::{CodecDefaultDecode, CodecRuntime, Decoder, Encoder, Input, codec_value};
17
18/// Read a codec `Input` as UTF-8 text, tagging any error with `codec`. The
19/// shared body of every domain codec's `input_text` helper.
20pub fn domain_input_text(codec: CodecId, input: Input) -> Result<String> {
21    match input {
22        Input::Text(text) => Ok(text),
23        Input::Bytes(bytes) => String::from_utf8(bytes).map_err(|err| Error::CodecError {
24            codec,
25            message: format!("codec input is not valid UTF-8: {err}"),
26        }),
27    }
28}
29
30/// Resolve a general codec's expression shape: the `primary` symbol, then
31/// `core/Expr`, then `core/Any`, then nil. This is the shared fallback chain
32/// that every general-purpose codec (json/binary/binary-base64/algol/lisp/doc)
33/// hand-rolled identically before OVERLAP6.07.
34pub fn resolve_expr_shape(linker: &Linker, primary: &Symbol) -> Result<ShapeRef> {
35    let nil = DefaultFactory.nil()?;
36    Ok(linker
37        .registry()
38        .shape_by_symbol(primary)
39        .or_else(|| {
40            linker
41                .registry()
42                .shape_by_symbol(&Symbol::qualified("core", "Expr"))
43        })
44        .or_else(|| {
45            linker
46                .registry()
47                .shape_by_symbol(&Symbol::qualified("core", "Any"))
48        })
49        .cloned()
50        .unwrap_or(nil))
51}
52
53/// Resolve a general codec's encode-options shape: `core/EncodeOptions`, then
54/// `core/Any`, then nil. The shared fallback chain those codecs hand-rolled.
55pub fn resolve_options_shape(linker: &Linker) -> Result<ShapeRef> {
56    let nil = DefaultFactory.nil()?;
57    Ok(linker
58        .registry()
59        .shape_by_symbol(&Symbol::qualified("core", "EncodeOptions"))
60        .or_else(|| {
61            linker
62                .registry()
63                .shape_by_symbol(&Symbol::qualified("core", "Any"))
64        })
65        .cloned()
66        .unwrap_or(nil))
67}
68
69/// A host-registered lib that exports one codec (and optionally the Shapes it
70/// uses) built from a decoder and encoder.
71pub struct DomainCodecLib {
72    symbol: Symbol,
73    codec_id: CodecId,
74    decoder: Arc<dyn Decoder>,
75    encoder: Arc<dyn Encoder>,
76    expr_shape_symbol: Symbol,
77    shapes: Vec<(Symbol, ShapeRef)>,
78}
79
80impl DomainCodecLib {
81    /// Build a domain codec lib. `expr_shape_symbol` is the codec's expression
82    /// shape; it is resolved (at load) from the lib's own registered shapes,
83    /// then the registry, then `core/Expr`, then `core/Any`, then nil.
84    pub fn new(
85        symbol: Symbol,
86        codec_id: CodecId,
87        decoder: Arc<dyn Decoder>,
88        encoder: Arc<dyn Encoder>,
89        expr_shape_symbol: Symbol,
90    ) -> Self {
91        Self {
92            symbol,
93            codec_id,
94            decoder,
95            encoder,
96            expr_shape_symbol,
97            shapes: Vec::new(),
98        }
99    }
100
101    /// Also register these Shapes when the lib loads (for codecs like
102    /// `codec:scene` that own their domain's node Shapes).
103    pub fn with_shapes(mut self, shapes: Vec<(Symbol, ShapeRef)>) -> Self {
104        self.shapes = shapes;
105        self
106    }
107}
108
109impl Lib for DomainCodecLib {
110    fn manifest(&self) -> LibManifest {
111        let mut exports: Vec<Export> = self
112            .shapes
113            .iter()
114            .map(|(symbol, _)| Export::Shape {
115                symbol: symbol.clone(),
116                shape_id: None,
117            })
118            .collect();
119        exports.push(Export::Codec {
120            symbol: self.symbol.clone(),
121            codec_id: Some(self.codec_id),
122        });
123        LibManifest {
124            id: self.symbol.clone(),
125            version: Version(env!("CARGO_PKG_VERSION").to_owned()),
126            abi: AbiVersion { major: 0, minor: 1 },
127            target: LibTarget::HostRegistered,
128            requires: Vec::<Dependency>::new(),
129            capabilities: Vec::new(),
130            exports,
131        }
132    }
133
134    fn load(&self, _cx: &mut LoadCx, linker: &mut Linker<'_>) -> Result<()> {
135        for (symbol, shape) in &self.shapes {
136            linker.shape_value(symbol.clone(), shape.clone())?;
137        }
138        let nil = DefaultFactory.nil()?;
139        let expr_shape = self
140            .shapes
141            .iter()
142            .find(|(symbol, _)| symbol == &self.expr_shape_symbol)
143            .map(|(_, shape)| shape.clone())
144            .or_else(|| {
145                linker
146                    .registry()
147                    .shape_by_symbol(&self.expr_shape_symbol)
148                    .cloned()
149            })
150            .or_else(|| {
151                linker
152                    .registry()
153                    .shape_by_symbol(&Symbol::qualified("core", "Expr"))
154                    .cloned()
155            })
156            .or_else(|| {
157                linker
158                    .registry()
159                    .shape_by_symbol(&Symbol::qualified("core", "Any"))
160                    .cloned()
161            })
162            .unwrap_or_else(|| nil.clone());
163        linker.codec_value(
164            self.symbol.clone(),
165            codec_value(CodecRuntime {
166                id: self.codec_id,
167                symbol: self.symbol.clone(),
168                decoder: Some(self.decoder.clone()),
169                located_decoder: None,
170                tree_decoder: None,
171                encoder: Some(self.encoder.clone()),
172                located_encoder: None,
173                tree_encoder: None,
174                expr_shape,
175                options_shape: nil,
176                default_decode: CodecDefaultDecode::Datum,
177            }),
178        )?;
179        Ok(())
180    }
181}