Skip to main content

sim_codec_doc/
codec.rs

1//! The `codec:doc` decoder/encoder and its host-registered lib. Decodes
2//! document text into a document `Expr` and encodes document values or strings
3//! back to text, installing the `doc/chunk-*` chunking functions alongside.
4
5use std::sync::Arc;
6
7use sim_codec::{
8    CodecDefaultDecode, CodecRuntime, DecodeBudget, Decoder, Encoder, Input, Output, ReadCx,
9    codec_value,
10};
11use sim_kernel::{
12    AbiVersion, Cx, DefaultFactory, Dependency, Error, Export, Lib, LibManifest, LibTarget, Linker,
13    Result, Symbol, Version, WriteCx,
14};
15
16use crate::document::{DocValue, decode_document};
17use crate::functions::{CHUNK_FUNCTIONS, DocChunkFunction};
18
19/// The `codec:doc` decoder/encoder.
20///
21/// As a [`Decoder`] it turns document text into a document `Expr`; as an
22/// [`Encoder`] it writes a document value or a raw string back to text and
23/// fails closed on any other expression.
24pub struct DocCodec;
25
26impl Decoder for DocCodec {
27    fn decode(&self, cx: &mut ReadCx<'_>, input: Input) -> Result<sim_kernel::Expr> {
28        let source = input.into_string()?;
29        let budget = DecodeBudget::new(cx.limits);
30        budget.check_input_bytes(cx.codec, source.len())?;
31        Ok(decode_document(&source).as_expr())
32    }
33}
34
35impl Encoder for DocCodec {
36    fn encode(&self, cx: &mut WriteCx<'_>, expr: &sim_kernel::Expr) -> Result<Output> {
37        match expr {
38            sim_kernel::Expr::String(text) => Ok(Output::Text(text.clone())),
39            sim_kernel::Expr::Map(_) => {
40                let doc = DocValue::from_expr(expr).map_err(|err| Error::CodecError {
41                    codec: cx.codec,
42                    message: err.to_string(),
43                })?;
44                Ok(Output::Text(doc.text))
45            }
46            _ => Err(Error::CodecError {
47                codec: cx.codec,
48                message: "codec:doc encodes document values or strings".to_owned(),
49            }),
50        }
51    }
52}
53
54/// The host-registered [`Lib`] that installs [`DocCodec`] as `codec:doc` and
55/// the `doc/chunk-*` chunking functions.
56pub struct DocCodecLib {
57    symbol: Symbol,
58    codec_id: sim_kernel::CodecId,
59}
60
61impl DocCodecLib {
62    /// Create the lib bound to the given codec id (obtained from
63    /// [`Registry::fresh_codec_id`](sim_kernel::Registry::fresh_codec_id)).
64    pub fn new(id: sim_kernel::CodecId) -> Self {
65        Self {
66            symbol: Symbol::qualified("codec", "doc"),
67            codec_id: id,
68        }
69    }
70}
71
72impl Lib for DocCodecLib {
73    fn manifest(&self) -> LibManifest {
74        let mut exports = vec![Export::Codec {
75            symbol: self.symbol.clone(),
76            codec_id: Some(self.codec_id),
77        }];
78        exports.extend(CHUNK_FUNCTIONS.iter().map(|kind| Export::Function {
79            symbol: kind.symbol(),
80            function_id: None,
81        }));
82        LibManifest {
83            id: self.symbol.clone(),
84            version: Version(env!("CARGO_PKG_VERSION").to_owned()),
85            abi: AbiVersion { major: 0, minor: 1 },
86            target: LibTarget::HostRegistered,
87            requires: Vec::<Dependency>::new(),
88            capabilities: Vec::new(),
89            exports,
90        }
91    }
92
93    fn load(&self, cx: &mut sim_kernel::LoadCx, linker: &mut Linker<'_>) -> Result<()> {
94        let _factory = DefaultFactory;
95        let expr_shape =
96            sim_codec::resolve_expr_shape(linker, &Symbol::qualified("codec", "Document"))?;
97        let options_shape = sim_codec::resolve_options_shape(linker)?;
98
99        linker.codec_value(
100            self.symbol.clone(),
101            codec_value(CodecRuntime {
102                id: self.codec_id,
103                symbol: self.symbol.clone(),
104                decoder: Some(Arc::new(DocCodec)),
105                located_decoder: None,
106                tree_decoder: None,
107                encoder: Some(Arc::new(DocCodec)),
108                located_encoder: None,
109                tree_encoder: None,
110                expr_shape,
111                options_shape,
112                default_decode: CodecDefaultDecode::Datum,
113            }),
114        )?;
115
116        for kind in CHUNK_FUNCTIONS {
117            linker.function_value(
118                kind.symbol(),
119                cx.factory()
120                    .opaque(Arc::new(DocChunkFunction::new(*kind)))?,
121            )?;
122        }
123        Ok(())
124    }
125}
126
127/// Install [`DocCodecLib`] into `cx`, registering `codec:doc` and the
128/// `doc/chunk-*` functions with a freshly allocated codec id.
129pub fn install_doc_codec(cx: &mut Cx) -> Result<()> {
130    let lib = DocCodecLib::new(cx.registry_mut().fresh_codec_id());
131    cx.load_lib(&lib)?;
132    Ok(())
133}