Skip to main content

sim_codec_bitwise/
codec.rs

1//! The `BitwiseCodec` runtime object, `Lib` registration, and frame functions.
2//!
3//! Exposes the free `encode_*` / `decode_*` frame helpers and wires the bitwise
4//! reader and writer into the codec decoder/encoder, located, and tree roles.
5
6use std::sync::Arc;
7
8use sim_codec::{
9    CodecDefaultDecode, CodecRuntime, Decoder, Encoder, Input, LocatedDecoder, LocatedEncoder,
10    Output, ReadCx, TreeDecoder, TreeEncoder, codec_value, validate_expr_tree,
11};
12use sim_kernel::{
13    AbiVersion, Dependency, Export, Expr, Lib, LibManifest, LibTarget, Linker, LocatedExpr,
14    LocatedExprTree, Result, Symbol, Version, WriteCx,
15};
16
17use crate::cookbook::{BitwiseRoundtripReport, roundtrip_report_symbol};
18use crate::reader::FrameReader;
19use crate::types::{FLAG_DENSE, FLAG_NONE, FLAG_ORIGIN, FLAG_TREE_ORIGIN};
20use crate::writer::FrameWriter;
21use crate::{BitwiseFrame, DecodeLimits, FrameTables};
22
23/// Bitwise codec runtime object: the canonical, minimal sibling of
24/// `codec:binary`.
25///
26/// As a general-purpose expression codec it round-trips kernel `Expr` values as
27/// bit-packed, self-delimiting frames, implementing every codec role --
28/// [`Decoder`]/[`Encoder`], located [`LocatedDecoder`]/[`LocatedEncoder`], and
29/// tree [`TreeDecoder`]/[`TreeEncoder`] -- over the shared `Expr` graph. It
30/// fails closed (under [`DecodeLimits`]) on any input that is not a well-formed
31/// frame, and its plain-mode output is the smallest canonical byte string for a
32/// value. Decoded bytes are treated strictly as data, never as executable input.
33pub struct BitwiseCodec;
34
35impl Decoder for BitwiseCodec {
36    fn decode(&self, cx: &mut ReadCx<'_>, input: Input) -> Result<Expr> {
37        let bytes = input_bytes(input);
38        decode_located_tree_frame_with_limits(cx.codec, &bytes, DecodeLimits::from(cx.limits))
39            .map(|(_, tree)| tree.located().expr)
40    }
41}
42
43impl Encoder for BitwiseCodec {
44    fn encode(&self, _cx: &mut WriteCx<'_>, expr: &Expr) -> Result<Output> {
45        Ok(Output::Bytes(encode_frame(expr)?.0))
46    }
47}
48
49impl LocatedDecoder for BitwiseCodec {
50    fn decode_located(
51        &self,
52        cx: &mut ReadCx<'_>,
53        input: Input,
54        _source_id: String,
55    ) -> Result<LocatedExpr> {
56        let bytes = input_bytes(input);
57        decode_located_tree_frame_with_limits(cx.codec, &bytes, DecodeLimits::from(cx.limits))
58            .map(|(_, tree)| tree.located())
59    }
60}
61
62impl LocatedEncoder for BitwiseCodec {
63    fn encode_located(&self, cx: &mut WriteCx<'_>, expr: &LocatedExpr) -> Result<Output> {
64        Ok(Output::Bytes(
65            encode_located_frame(expr, cx.options.lossless_origin)?.0,
66        ))
67    }
68}
69
70impl TreeDecoder for BitwiseCodec {
71    fn decode_tree(
72        &self,
73        cx: &mut ReadCx<'_>,
74        input: Input,
75        _source_id: String,
76    ) -> Result<LocatedExprTree> {
77        let bytes = input_bytes(input);
78        decode_located_tree_frame_with_limits(cx.codec, &bytes, DecodeLimits::from(cx.limits))
79            .map(|(_, tree)| tree)
80    }
81}
82
83impl TreeEncoder for BitwiseCodec {
84    fn encode_tree(&self, cx: &mut WriteCx<'_>, expr: &LocatedExprTree) -> Result<Output> {
85        validate_expr_tree(cx.codec, expr)?;
86        Ok(Output::Bytes(
87            encode_located_tree_frame(expr, cx.options.lossless_origin)?.0,
88        ))
89    }
90}
91
92fn input_bytes(input: Input) -> Vec<u8> {
93    match input {
94        Input::Text(text) => text.into_bytes(),
95        Input::Bytes(bytes) => bytes,
96    }
97}
98
99/// Encodes a bare [`Expr`] into a [`BitwiseFrame`], without source origins.
100///
101/// This is the plain, canonical form: the smallest byte string for the value,
102/// suitable as a content-address input (see [`crate::canonical_bytes`]).
103pub fn encode_frame(expr: &Expr) -> Result<BitwiseFrame> {
104    encode_located_frame(
105        &LocatedExpr {
106            expr: expr.clone(),
107            origin: None,
108        },
109        false,
110    )
111}
112
113/// Encodes a bare [`Expr`] into a dense [`BitwiseFrame`] with structural sharing.
114///
115/// Dense mode assigns every subexpression a pre-order number and, on a
116/// value-equal subtree already emitted, writes a `Ref` back-reference tag
117/// instead of re-encoding it. It is a deterministic pure function
118/// of the value tree and round-trips by [`Expr::canonical_eq`], but it is
119/// explicitly opt-in: the plain [`encode_frame`] and [`crate::canonical_bytes`]
120/// stay ref-free so the content-addressing key is unchanged.
121pub fn encode_dense(expr: &Expr) -> Result<BitwiseFrame> {
122    let tables = FrameTables::collect(expr);
123    let mut writer = FrameWriter::new(tables);
124    writer.flags = FLAG_DENSE;
125    writer.set_dense(true);
126    writer.write_header()?;
127    writer.write_expr(expr)?;
128    Ok(BitwiseFrame(writer.finish()))
129}
130
131/// Encodes a [`LocatedExpr`] into a [`BitwiseFrame`].
132///
133/// When `include_origin` is set and `located` carries an origin, the frame is
134/// flagged to carry that single origin so the located form round-trips.
135pub fn encode_located_frame(located: &LocatedExpr, include_origin: bool) -> Result<BitwiseFrame> {
136    let tables = FrameTables::collect(&located.expr);
137    let mut writer = FrameWriter::new(tables);
138    writer.flags = if include_origin && located.origin.is_some() {
139        FLAG_ORIGIN
140    } else {
141        FLAG_NONE
142    };
143    writer.write_header()?;
144    writer.write_expr(&located.expr)?;
145    if writer.flags & FLAG_ORIGIN != 0 {
146        writer.write_origin(
147            located
148                .origin
149                .as_ref()
150                .expect("origin flag requires origin payload"),
151        )?;
152    }
153    Ok(BitwiseFrame(writer.finish()))
154}
155
156/// Encodes a [`LocatedExprTree`] into a [`BitwiseFrame`].
157///
158/// When `include_origin` is set the frame carries the per-node origin tree so
159/// the full located tree round-trips; otherwise only the `Expr` body is
160/// written. The tree is validated before encoding and rejected if malformed.
161pub fn encode_located_tree_frame(
162    tree: &LocatedExprTree,
163    include_origin: bool,
164) -> Result<BitwiseFrame> {
165    validate_expr_tree(sim_kernel::CodecId(0), tree)?;
166    let tables = FrameTables::collect(&tree.expr);
167    let mut writer = FrameWriter::new(tables);
168    writer.flags = if include_origin {
169        FLAG_TREE_ORIGIN
170    } else {
171        FLAG_NONE
172    };
173    writer.write_header()?;
174    writer.write_expr(&tree.expr)?;
175    if writer.flags & FLAG_TREE_ORIGIN != 0 {
176        writer.write_origin_tree(tree)?;
177    }
178    Ok(BitwiseFrame(writer.finish()))
179}
180
181/// Decodes frame `bytes` into its side [`FrameTables`] and bare [`Expr`].
182///
183/// Any source origins carried by the frame are dropped. Decoding is bounded by
184/// the default [`DecodeLimits`] and fails closed on malformed or oversize input.
185pub fn decode_frame(codec: sim_kernel::CodecId, bytes: &[u8]) -> Result<(FrameTables, Expr)> {
186    let (tables, tree) = decode_located_tree_frame(codec, bytes)?;
187    Ok((tables, tree.located().expr))
188}
189
190/// Decodes frame `bytes` into its side [`FrameTables`] and a [`LocatedExpr`].
191///
192/// The top-level origin is recovered when the frame carries one. Decoding is
193/// bounded by the default [`DecodeLimits`] and fails closed on bad input.
194pub fn decode_located_frame(
195    codec: sim_kernel::CodecId,
196    bytes: &[u8],
197) -> Result<(FrameTables, LocatedExpr)> {
198    let (tables, tree) = decode_located_tree_frame(codec, bytes)?;
199    Ok((tables, tree.located()))
200}
201
202/// Decodes frame `bytes` into its side [`FrameTables`] and a full
203/// [`LocatedExprTree`], using the default [`DecodeLimits`].
204pub fn decode_located_tree_frame(
205    codec: sim_kernel::CodecId,
206    bytes: &[u8],
207) -> Result<(FrameTables, LocatedExprTree)> {
208    decode_located_tree_frame_with_limits(codec, bytes, DecodeLimits::default())
209}
210
211/// Decodes frame `bytes` into its side [`FrameTables`] and a
212/// [`LocatedExprTree`], enforcing the supplied `limits`.
213///
214/// This is the bounded decode primitive the codec roles call. It rejects an
215/// unknown version, unknown/dense flag bits, out-of-range table indices,
216/// oversize counts, reserved tags, and any nonzero or byte-sized trailing
217/// padding, failing closed on untrusted input.
218pub fn decode_located_tree_frame_with_limits(
219    codec: sim_kernel::CodecId,
220    bytes: &[u8],
221    limits: DecodeLimits,
222) -> Result<(FrameTables, LocatedExprTree)> {
223    let mut reader = FrameReader::new(codec, bytes, limits)?;
224    let tables = reader.read_header()?;
225    let expr = reader.read_expr()?;
226    let mut tree = if reader.flags & FLAG_TREE_ORIGIN != 0 {
227        reader.read_origin_tree(expr)?
228    } else {
229        LocatedExprTree::from_expr_recursive(expr)
230    };
231    if reader.flags & FLAG_ORIGIN != 0 {
232        tree.origin = Some(reader.read_origin()?);
233    }
234    reader.require_zero_padding()?;
235    Ok((tables, tree))
236}
237
238/// [`Lib`] that registers the bitwise codec with the runtime.
239///
240/// Its manifest exports the `codec/bitwise` codec, and loading wires a
241/// [`BitwiseCodec`] into the linker as the decode and encode surface for all
242/// six codec roles.
243pub struct BitwiseCodecLib {
244    symbol: Symbol,
245    codec_id: sim_kernel::CodecId,
246}
247
248impl BitwiseCodecLib {
249    /// Creates the codec lib bound to the runtime-assigned `id` for
250    /// `codec/bitwise`.
251    pub fn new(id: sim_kernel::CodecId) -> Self {
252        Self {
253            symbol: Symbol::qualified("codec", "bitwise"),
254            codec_id: id,
255        }
256    }
257}
258
259impl Lib for BitwiseCodecLib {
260    fn manifest(&self) -> LibManifest {
261        LibManifest {
262            id: self.symbol.clone(),
263            version: Version(env!("CARGO_PKG_VERSION").to_owned()),
264            abi: AbiVersion { major: 0, minor: 1 },
265            target: LibTarget::HostRegistered,
266            requires: Vec::<Dependency>::new(),
267            capabilities: Vec::new(),
268            exports: vec![
269                Export::Codec {
270                    symbol: self.symbol.clone(),
271                    codec_id: Some(self.codec_id),
272                },
273                Export::Function {
274                    symbol: roundtrip_report_symbol(),
275                    function_id: None,
276                },
277            ],
278        }
279    }
280
281    fn load(&self, cx: &mut sim_kernel::LoadCx, linker: &mut Linker) -> Result<()> {
282        let expr_shape =
283            sim_codec::resolve_expr_shape(linker, &Symbol::qualified("codec", "BitwiseFrame"))?;
284        let options_shape = sim_codec::resolve_options_shape(linker)?;
285
286        linker.codec_value(
287            self.symbol.clone(),
288            codec_value(CodecRuntime {
289                id: self.codec_id,
290                symbol: self.symbol.clone(),
291                decoder: Some(Arc::new(BitwiseCodec)),
292                located_decoder: Some(Arc::new(BitwiseCodec)),
293                tree_decoder: Some(Arc::new(BitwiseCodec)),
294                encoder: Some(Arc::new(BitwiseCodec)),
295                located_encoder: Some(Arc::new(BitwiseCodec)),
296                tree_encoder: Some(Arc::new(BitwiseCodec)),
297                expr_shape,
298                options_shape,
299                default_decode: CodecDefaultDecode::Datum,
300            }),
301        )?;
302        linker.function_value(
303            roundtrip_report_symbol(),
304            cx.factory().opaque(Arc::new(BitwiseRoundtripReport))?,
305        )?;
306        Ok(())
307    }
308}