Skip to main content

sim_codec/
runtime_api.rs

1//! Eval-facing API that drives codecs through the kernel.
2//!
3//! Provides the `DecodedForm` result type, codec lookup/value helpers, and the
4//! decode/encode entry points that locate a codec by symbol and run it with the
5//! configured decode limits and read policy.
6
7use std::sync::Arc;
8
9use sim_kernel::{
10    Cx, Datum, DefaultFactory, Factory, LocatedExpr, LocatedExprTree, ReadPolicy, Result, Symbol,
11    Term, Value, WriteCx,
12};
13
14use crate::{
15    CodecDefaultDecode, CodecRuntime, DecodeLimits, DecodePosition, DecodeTarget, Input, Output,
16    ReadCx, encode_value_expr,
17};
18
19/// The position-resolved result of a default decode: inert data or an evaluable
20/// term.
21///
22/// Returned by [`decode_default_with_codec`]; the codec's
23/// [`CodecDefaultDecode`] policy and the requested [`DecodePosition`] together
24/// select which variant is produced.
25#[derive(Clone, Debug, PartialEq, Eq)]
26pub enum DecodedForm {
27    /// Decoded as inert data.
28    Datum(Datum),
29    /// Decoded as an evaluable term.
30    Term(Term),
31}
32
33/// Wrap a [`CodecRuntime`] as an opaque runtime [`Value`] for registry storage.
34pub fn codec_value(codec: CodecRuntime) -> Value {
35    DefaultFactory
36        .opaque(Arc::new(codec))
37        .expect("codec runtime should always be boxable")
38}
39
40/// Decode `input` with the codec named `symbol`, using default [`DecodeLimits`].
41///
42/// Looks the codec up in the kernel registry, runs its decoder under
43/// `read_policy`, and returns the raw `Expr`.
44pub fn decode_with_codec(
45    cx: &mut Cx,
46    symbol: &Symbol,
47    input: Input,
48    read_policy: ReadPolicy,
49) -> Result<sim_kernel::Expr> {
50    decode_with_codec_and_limits(cx, symbol, input, read_policy, DecodeLimits::default())
51}
52
53/// Decode `input` with the codec named `symbol` under explicit [`DecodeLimits`].
54pub fn decode_with_codec_and_limits(
55    cx: &mut Cx,
56    symbol: &Symbol,
57    input: Input,
58    read_policy: ReadPolicy,
59    limits: DecodeLimits,
60) -> Result<sim_kernel::Expr> {
61    decode_expr_with_codec_and_limits(cx, symbol, input, read_policy, limits).map(|(expr, _)| expr)
62}
63
64/// Decode `input` to a `Datum` with the codec named `symbol`, using default
65/// [`DecodeLimits`].
66pub fn decode_datum_with_codec(
67    cx: &mut Cx,
68    symbol: &Symbol,
69    input: Input,
70    read_policy: ReadPolicy,
71) -> Result<Datum> {
72    decode_datum_with_codec_and_limits(cx, symbol, input, read_policy, DecodeLimits::default())
73}
74
75/// Decode `input` to a `Datum` under explicit [`DecodeLimits`], failing if the
76/// decoded `Expr` is not inert data.
77pub fn decode_datum_with_codec_and_limits(
78    cx: &mut Cx,
79    symbol: &Symbol,
80    input: Input,
81    read_policy: ReadPolicy,
82    limits: DecodeLimits,
83) -> Result<Datum> {
84    let (expr, _) = decode_expr_with_codec_and_limits(cx, symbol, input, read_policy, limits)?;
85    Datum::try_from(expr)
86}
87
88/// Decode `input` to an evaluable `Term` with the codec named `symbol`, using
89/// default [`DecodeLimits`].
90pub fn decode_term_with_codec(
91    cx: &mut Cx,
92    symbol: &Symbol,
93    input: Input,
94    read_policy: ReadPolicy,
95) -> Result<Term> {
96    decode_term_with_codec_and_limits(cx, symbol, input, read_policy, DecodeLimits::default())
97}
98
99/// Decode `input` to a `Term` under explicit [`DecodeLimits`], lowering the
100/// decoded `Expr` to a term per the codec's [`CodecDefaultDecode`] policy.
101pub fn decode_term_with_codec_and_limits(
102    cx: &mut Cx,
103    symbol: &Symbol,
104    input: Input,
105    read_policy: ReadPolicy,
106    limits: DecodeLimits,
107) -> Result<Term> {
108    let (expr, default_decode) =
109        decode_expr_with_codec_and_limits(cx, symbol, input, read_policy, limits)?;
110    term_from_expr(default_decode, expr)
111}
112
113/// Decode `input` and resolve it to data or a term per the codec's policy and
114/// the requested `position`, using default [`DecodeLimits`].
115///
116/// This is the position-aware entry point: the codec's [`CodecDefaultDecode`]
117/// and `position` jointly pick the [`DecodedForm`] variant.
118pub fn decode_default_with_codec(
119    cx: &mut Cx,
120    symbol: &Symbol,
121    input: Input,
122    read_policy: ReadPolicy,
123    position: DecodePosition,
124) -> Result<DecodedForm> {
125    decode_default_with_codec_and_limits(
126        cx,
127        symbol,
128        input,
129        read_policy,
130        position,
131        DecodeLimits::default(),
132    )
133}
134
135/// Position-aware decode under explicit [`DecodeLimits`]; see
136/// [`decode_default_with_codec`].
137pub fn decode_default_with_codec_and_limits(
138    cx: &mut Cx,
139    symbol: &Symbol,
140    input: Input,
141    read_policy: ReadPolicy,
142    position: DecodePosition,
143    limits: DecodeLimits,
144) -> Result<DecodedForm> {
145    let (expr, default_decode) =
146        decode_expr_with_codec_and_limits(cx, symbol, input, read_policy, limits)?;
147    match default_decode.target_for(position) {
148        DecodeTarget::Datum => Datum::try_from(expr).map(DecodedForm::Datum),
149        DecodeTarget::Term => term_from_expr(default_decode, expr).map(DecodedForm::Term),
150    }
151}
152
153fn decode_expr_with_codec_and_limits(
154    cx: &mut Cx,
155    symbol: &Symbol,
156    input: Input,
157    read_policy: ReadPolicy,
158    limits: DecodeLimits,
159) -> Result<(sim_kernel::Expr, CodecDefaultDecode)> {
160    let value = cx.resolve_codec(symbol)?;
161    let codec =
162        value
163            .object()
164            .downcast_ref::<CodecRuntime>()
165            .ok_or(sim_kernel::Error::TypeMismatch {
166                expected: "codec",
167                found: "non-codec",
168            })?;
169    let mut read_cx = ReadCx {
170        cx,
171        codec: codec.id,
172        read_policy,
173        limits,
174    };
175    codec
176        .decode(&mut read_cx, input)
177        .map(|expr| (expr, codec.default_decode))
178}
179
180/// Encode `expr` with the codec named `symbol` under `options`.
181///
182/// Looks the codec up, builds a `WriteCx` from `options` (which fix the output
183/// position and fidelity), and runs the codec's encoder.
184pub fn encode_with_codec(
185    cx: &mut Cx,
186    symbol: &Symbol,
187    expr: &sim_kernel::Expr,
188    options: sim_kernel::EncodeOptions,
189) -> Result<Output> {
190    let value = cx.resolve_codec(symbol)?;
191    let codec =
192        value
193            .object()
194            .downcast_ref::<CodecRuntime>()
195            .ok_or(sim_kernel::Error::TypeMismatch {
196                expected: "codec",
197                found: "non-codec",
198            })?;
199    let mut write_cx = WriteCx {
200        cx,
201        codec: codec.id,
202        options,
203    };
204    codec.encode(&mut write_cx, expr)
205}
206
207/// Encode a `Datum` with the codec named `symbol`, lifting it to an `Expr`
208/// first.
209pub fn encode_datum_with_codec(
210    cx: &mut Cx,
211    symbol: &Symbol,
212    datum: &Datum,
213    options: sim_kernel::EncodeOptions,
214) -> Result<Output> {
215    encode_with_codec(cx, symbol, &sim_kernel::Expr::from(datum.clone()), options)
216}
217
218/// Encode a `Term` with the codec named `symbol`, lifting it to an `Expr` first.
219pub fn encode_term_with_codec(
220    cx: &mut Cx,
221    symbol: &Symbol,
222    term: &Term,
223    options: sim_kernel::EncodeOptions,
224) -> Result<Output> {
225    encode_with_codec(cx, symbol, &sim_kernel::Expr::from(term.clone()), options)
226}
227
228/// Encode a runtime `Value` with the codec named `symbol`, forcing lists and
229/// tables into an `Expr` via [`encode_value_expr`] before encoding.
230pub fn encode_value_with_codec(
231    cx: &mut Cx,
232    symbol: &Symbol,
233    value: &Value,
234    options: sim_kernel::EncodeOptions,
235) -> Result<Output> {
236    let codec_value = cx.resolve_codec(symbol)?;
237    let codec = codec_value.object().downcast_ref::<CodecRuntime>().ok_or(
238        sim_kernel::Error::TypeMismatch {
239            expected: "codec",
240            found: "non-codec",
241        },
242    )?;
243    let mut write_cx = WriteCx {
244        cx,
245        codec: codec.id,
246        options,
247    };
248    let expr = encode_value_expr(&mut write_cx, value)?;
249    codec.encode(&mut write_cx, &expr)
250}
251
252/// Decode `input` preserving source origin, attributing spans to `source_id`,
253/// using default [`DecodeLimits`].
254pub fn decode_located_with_codec(
255    cx: &mut Cx,
256    symbol: &Symbol,
257    input: Input,
258    read_policy: ReadPolicy,
259    source_id: impl Into<String>,
260) -> Result<LocatedExpr> {
261    decode_located_with_codec_and_limits(
262        cx,
263        symbol,
264        input,
265        read_policy,
266        source_id,
267        DecodeLimits::default(),
268    )
269}
270
271/// Origin-preserving decode under explicit [`DecodeLimits`]; see
272/// [`decode_located_with_codec`].
273pub fn decode_located_with_codec_and_limits(
274    cx: &mut Cx,
275    symbol: &Symbol,
276    input: Input,
277    read_policy: ReadPolicy,
278    source_id: impl Into<String>,
279    limits: DecodeLimits,
280) -> Result<LocatedExpr> {
281    let value = cx.resolve_codec(symbol)?;
282    let codec =
283        value
284            .object()
285            .downcast_ref::<CodecRuntime>()
286            .ok_or(sim_kernel::Error::TypeMismatch {
287                expected: "codec",
288                found: "non-codec",
289            })?;
290    let mut read_cx = ReadCx {
291        cx,
292        codec: codec.id,
293        read_policy,
294        limits,
295    };
296    codec.decode_located(&mut read_cx, input, source_id.into())
297}
298
299/// Encode a [`LocatedExpr`] with the codec named `symbol`, using its origin for
300/// fidelity when `options` request lossless-origin output.
301pub fn encode_located_with_codec(
302    cx: &mut Cx,
303    symbol: &Symbol,
304    expr: &LocatedExpr,
305    options: sim_kernel::EncodeOptions,
306) -> Result<Output> {
307    let value = cx.resolve_codec(symbol)?;
308    let codec =
309        value
310            .object()
311            .downcast_ref::<CodecRuntime>()
312            .ok_or(sim_kernel::Error::TypeMismatch {
313                expected: "codec",
314                found: "non-codec",
315            })?;
316    let mut write_cx = WriteCx {
317        cx,
318        codec: codec.id,
319        options,
320    };
321    codec.encode_located(&mut write_cx, expr)
322}
323
324/// Encode a [`LocatedExprTree`] with the codec named `symbol`, reproducing
325/// layout and trivia when `options` request lossless-origin output.
326pub fn encode_tree_with_codec(
327    cx: &mut Cx,
328    symbol: &Symbol,
329    expr: &LocatedExprTree,
330    options: sim_kernel::EncodeOptions,
331) -> Result<Output> {
332    let value = cx
333        .registry()
334        .codec_by_symbol(symbol)
335        .cloned()
336        .ok_or_else(|| sim_kernel::Error::Eval(format!("unknown codec {}", symbol)))?;
337    let codec = value
338        .object()
339        .as_any()
340        .downcast_ref::<CodecRuntime>()
341        .ok_or_else(|| sim_kernel::Error::Eval(format!("{} is not a codec runtime", symbol)))?;
342    let mut write_cx = WriteCx {
343        cx,
344        codec: codec.id,
345        options,
346    };
347    codec.encode_tree(&mut write_cx, expr)
348}
349
350/// Decode `input` into a full [`LocatedExprTree`] (trivia and layout retained),
351/// attributing spans to `source_id`, using default [`DecodeLimits`].
352pub fn decode_tree_with_codec(
353    cx: &mut Cx,
354    symbol: &Symbol,
355    input: Input,
356    read_policy: ReadPolicy,
357    source_id: impl Into<String>,
358) -> Result<LocatedExprTree> {
359    decode_tree_with_codec_and_limits(
360        cx,
361        symbol,
362        input,
363        read_policy,
364        source_id,
365        DecodeLimits::default(),
366    )
367}
368
369/// Full-tree decode under explicit [`DecodeLimits`]; see
370/// [`decode_tree_with_codec`].
371pub fn decode_tree_with_codec_and_limits(
372    cx: &mut Cx,
373    symbol: &Symbol,
374    input: Input,
375    read_policy: ReadPolicy,
376    source_id: impl Into<String>,
377    limits: DecodeLimits,
378) -> Result<LocatedExprTree> {
379    let value = cx.resolve_codec(symbol)?;
380    let codec =
381        value
382            .object()
383            .downcast_ref::<CodecRuntime>()
384            .ok_or(sim_kernel::Error::TypeMismatch {
385                expected: "codec",
386                found: "non-codec",
387            })?;
388    let mut read_cx = ReadCx {
389        cx,
390        codec: codec.id,
391        read_policy,
392        limits,
393    };
394    codec.decode_tree(&mut read_cx, input, source_id.into())
395}
396
397fn term_from_expr(default_decode: CodecDefaultDecode, expr: sim_kernel::Expr) -> Result<Term> {
398    match default_decode {
399        CodecDefaultDecode::Datum => Term::lower(expr),
400        CodecDefaultDecode::TermInEvalDatumOtherwise => Term::lower(lower_eval_surface(expr)),
401    }
402}
403
404fn lower_eval_surface(expr: sim_kernel::Expr) -> sim_kernel::Expr {
405    use sim_kernel::Expr;
406
407    match expr {
408        Expr::List(items) if items.len() > 1 => {
409            let mut items = items
410                .into_iter()
411                .map(lower_eval_surface)
412                .collect::<Vec<_>>();
413            let operator = Box::new(items.remove(0));
414            Expr::Call {
415                operator,
416                args: items,
417            }
418        }
419        Expr::List(items) => Expr::List(items.into_iter().map(lower_eval_surface).collect()),
420        Expr::Vector(items) => Expr::Vector(items.into_iter().map(lower_eval_surface).collect()),
421        Expr::Map(entries) => Expr::Map(
422            entries
423                .into_iter()
424                .map(|(key, value)| (lower_eval_surface(key), lower_eval_surface(value)))
425                .collect(),
426        ),
427        Expr::Set(items) => Expr::Set(items.into_iter().map(lower_eval_surface).collect()),
428        Expr::Block(items) => Expr::Block(items.into_iter().map(lower_eval_surface).collect()),
429        Expr::Quote { mode, expr } => Expr::Quote { mode, expr },
430        Expr::Annotated { expr, annotations } => Expr::Annotated {
431            expr: Box::new(lower_eval_surface(*expr)),
432            annotations: annotations
433                .into_iter()
434                .map(|(name, value)| (name, lower_eval_surface(value)))
435                .collect(),
436        },
437        Expr::Extension { tag, payload } => Expr::Extension {
438            tag,
439            payload: Box::new(lower_eval_surface(*payload)),
440        },
441        other => other,
442    }
443}