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` to an evaluable [`sim_kernel::Expr`], applying the codec's
114/// eval-surface lowering (a lisp `(f x)` becomes a [`sim_kernel::Expr::Call`])
115/// but WITHOUT the
116/// [`Term`]/[`Datum`] round-trip that [`decode_term_with_codec`] performs.
117///
118/// The Term lowering forces every `Expr::List` to a pure [`Datum`], which fails
119/// (`expected datum expression, found call expression`) when a list legitimately
120/// contains a non-datum sub-form -- e.g. a `let` binding container `((x 5))`
121/// whose clause `(x 5)` eval-lowers to a call, or a `match` clause. Evaluating
122/// the eval-lowered `Expr` directly preserves that structure so a special form
123/// receives its raw structural argument and interprets it (call- or list-shaped
124/// clauses are both accepted by the organ forms). For plain function calls this
125/// is behaviour-identical to `decode_term_with_codec` followed by `Expr::from`.
126pub fn decode_eval_expr_with_codec(
127    cx: &mut Cx,
128    symbol: &Symbol,
129    input: Input,
130    read_policy: ReadPolicy,
131) -> Result<sim_kernel::Expr> {
132    decode_eval_expr_with_codec_and_limits(cx, symbol, input, read_policy, DecodeLimits::default())
133}
134
135/// [`decode_eval_expr_with_codec`] under explicit [`DecodeLimits`].
136pub fn decode_eval_expr_with_codec_and_limits(
137    cx: &mut Cx,
138    symbol: &Symbol,
139    input: Input,
140    read_policy: ReadPolicy,
141    limits: DecodeLimits,
142) -> Result<sim_kernel::Expr> {
143    let (expr, default_decode) =
144        decode_expr_with_codec_and_limits(cx, symbol, input, read_policy, limits)?;
145    Ok(match default_decode {
146        CodecDefaultDecode::Datum => expr,
147        CodecDefaultDecode::TermInEvalDatumOtherwise => lower_eval_surface(expr),
148    })
149}
150
151/// Decode `input` and resolve it to data or a term per the codec's policy and
152/// the requested `position`, using default [`DecodeLimits`].
153///
154/// This is the position-aware entry point: the codec's [`CodecDefaultDecode`]
155/// and `position` jointly pick the [`DecodedForm`] variant.
156pub fn decode_default_with_codec(
157    cx: &mut Cx,
158    symbol: &Symbol,
159    input: Input,
160    read_policy: ReadPolicy,
161    position: DecodePosition,
162) -> Result<DecodedForm> {
163    decode_default_with_codec_and_limits(
164        cx,
165        symbol,
166        input,
167        read_policy,
168        position,
169        DecodeLimits::default(),
170    )
171}
172
173/// Position-aware decode under explicit [`DecodeLimits`]; see
174/// [`decode_default_with_codec`].
175pub fn decode_default_with_codec_and_limits(
176    cx: &mut Cx,
177    symbol: &Symbol,
178    input: Input,
179    read_policy: ReadPolicy,
180    position: DecodePosition,
181    limits: DecodeLimits,
182) -> Result<DecodedForm> {
183    let (expr, default_decode) =
184        decode_expr_with_codec_and_limits(cx, symbol, input, read_policy, limits)?;
185    match default_decode.target_for(position) {
186        DecodeTarget::Datum => Datum::try_from(expr).map(DecodedForm::Datum),
187        DecodeTarget::Term => term_from_expr(default_decode, expr).map(DecodedForm::Term),
188    }
189}
190
191fn decode_expr_with_codec_and_limits(
192    cx: &mut Cx,
193    symbol: &Symbol,
194    input: Input,
195    read_policy: ReadPolicy,
196    limits: DecodeLimits,
197) -> Result<(sim_kernel::Expr, CodecDefaultDecode)> {
198    let value = cx.resolve_codec(symbol)?;
199    let codec =
200        value
201            .object()
202            .downcast_ref::<CodecRuntime>()
203            .ok_or(sim_kernel::Error::TypeMismatch {
204                expected: "codec",
205                found: "non-codec",
206            })?;
207    let mut read_cx = ReadCx {
208        cx,
209        codec: codec.id,
210        read_policy,
211        limits,
212    };
213    codec
214        .decode(&mut read_cx, input)
215        .map(|expr| (expr, codec.default_decode))
216}
217
218/// Encode `expr` with the codec named `symbol` under `options`.
219///
220/// Looks the codec up, builds a `WriteCx` from `options` (which fix the output
221/// position and fidelity), and runs the codec's encoder.
222pub fn encode_with_codec(
223    cx: &mut Cx,
224    symbol: &Symbol,
225    expr: &sim_kernel::Expr,
226    options: sim_kernel::EncodeOptions,
227) -> Result<Output> {
228    let value = cx.resolve_codec(symbol)?;
229    let codec =
230        value
231            .object()
232            .downcast_ref::<CodecRuntime>()
233            .ok_or(sim_kernel::Error::TypeMismatch {
234                expected: "codec",
235                found: "non-codec",
236            })?;
237    let mut write_cx = WriteCx {
238        cx,
239        codec: codec.id,
240        options,
241    };
242    codec.encode(&mut write_cx, expr)
243}
244
245/// Encode a `Datum` with the codec named `symbol`, lifting it to an `Expr`
246/// first.
247pub fn encode_datum_with_codec(
248    cx: &mut Cx,
249    symbol: &Symbol,
250    datum: &Datum,
251    options: sim_kernel::EncodeOptions,
252) -> Result<Output> {
253    encode_with_codec(cx, symbol, &sim_kernel::Expr::from(datum.clone()), options)
254}
255
256/// Encode a `Term` with the codec named `symbol`, lifting it to an `Expr` first.
257pub fn encode_term_with_codec(
258    cx: &mut Cx,
259    symbol: &Symbol,
260    term: &Term,
261    options: sim_kernel::EncodeOptions,
262) -> Result<Output> {
263    encode_with_codec(cx, symbol, &sim_kernel::Expr::from(term.clone()), options)
264}
265
266/// Encode a runtime `Value` with the codec named `symbol`, forcing lists and
267/// tables into an `Expr` via [`encode_value_expr`] before encoding.
268pub fn encode_value_with_codec(
269    cx: &mut Cx,
270    symbol: &Symbol,
271    value: &Value,
272    options: sim_kernel::EncodeOptions,
273) -> Result<Output> {
274    let codec_value = cx.resolve_codec(symbol)?;
275    let codec = codec_value.object().downcast_ref::<CodecRuntime>().ok_or(
276        sim_kernel::Error::TypeMismatch {
277            expected: "codec",
278            found: "non-codec",
279        },
280    )?;
281    let mut write_cx = WriteCx {
282        cx,
283        codec: codec.id,
284        options,
285    };
286    let expr = encode_value_expr(&mut write_cx, value)?;
287    codec.encode(&mut write_cx, &expr)
288}
289
290/// Decode `input` preserving source origin, attributing spans to `source_id`,
291/// using default [`DecodeLimits`].
292pub fn decode_located_with_codec(
293    cx: &mut Cx,
294    symbol: &Symbol,
295    input: Input,
296    read_policy: ReadPolicy,
297    source_id: impl Into<String>,
298) -> Result<LocatedExpr> {
299    decode_located_with_codec_and_limits(
300        cx,
301        symbol,
302        input,
303        read_policy,
304        source_id,
305        DecodeLimits::default(),
306    )
307}
308
309/// Origin-preserving decode under explicit [`DecodeLimits`]; see
310/// [`decode_located_with_codec`].
311pub fn decode_located_with_codec_and_limits(
312    cx: &mut Cx,
313    symbol: &Symbol,
314    input: Input,
315    read_policy: ReadPolicy,
316    source_id: impl Into<String>,
317    limits: DecodeLimits,
318) -> Result<LocatedExpr> {
319    let value = cx.resolve_codec(symbol)?;
320    let codec =
321        value
322            .object()
323            .downcast_ref::<CodecRuntime>()
324            .ok_or(sim_kernel::Error::TypeMismatch {
325                expected: "codec",
326                found: "non-codec",
327            })?;
328    let mut read_cx = ReadCx {
329        cx,
330        codec: codec.id,
331        read_policy,
332        limits,
333    };
334    codec.decode_located(&mut read_cx, input, source_id.into())
335}
336
337/// Encode a [`LocatedExpr`] with the codec named `symbol`, using its origin for
338/// fidelity when `options` request lossless-origin output.
339pub fn encode_located_with_codec(
340    cx: &mut Cx,
341    symbol: &Symbol,
342    expr: &LocatedExpr,
343    options: sim_kernel::EncodeOptions,
344) -> Result<Output> {
345    let value = cx.resolve_codec(symbol)?;
346    let codec =
347        value
348            .object()
349            .downcast_ref::<CodecRuntime>()
350            .ok_or(sim_kernel::Error::TypeMismatch {
351                expected: "codec",
352                found: "non-codec",
353            })?;
354    let mut write_cx = WriteCx {
355        cx,
356        codec: codec.id,
357        options,
358    };
359    codec.encode_located(&mut write_cx, expr)
360}
361
362/// Encode a [`LocatedExprTree`] with the codec named `symbol`, reproducing
363/// layout and trivia when `options` request lossless-origin output.
364pub fn encode_tree_with_codec(
365    cx: &mut Cx,
366    symbol: &Symbol,
367    expr: &LocatedExprTree,
368    options: sim_kernel::EncodeOptions,
369) -> Result<Output> {
370    let value = cx
371        .registry()
372        .codec_by_symbol(symbol)
373        .cloned()
374        .ok_or_else(|| sim_kernel::Error::Eval(format!("unknown codec {}", symbol)))?;
375    let codec = value
376        .object()
377        .as_any()
378        .downcast_ref::<CodecRuntime>()
379        .ok_or_else(|| sim_kernel::Error::Eval(format!("{} is not a codec runtime", symbol)))?;
380    let mut write_cx = WriteCx {
381        cx,
382        codec: codec.id,
383        options,
384    };
385    codec.encode_tree(&mut write_cx, expr)
386}
387
388/// Decode `input` into a full [`LocatedExprTree`] (trivia and layout retained),
389/// attributing spans to `source_id`, using default [`DecodeLimits`].
390pub fn decode_tree_with_codec(
391    cx: &mut Cx,
392    symbol: &Symbol,
393    input: Input,
394    read_policy: ReadPolicy,
395    source_id: impl Into<String>,
396) -> Result<LocatedExprTree> {
397    decode_tree_with_codec_and_limits(
398        cx,
399        symbol,
400        input,
401        read_policy,
402        source_id,
403        DecodeLimits::default(),
404    )
405}
406
407/// Full-tree decode under explicit [`DecodeLimits`]; see
408/// [`decode_tree_with_codec`].
409pub fn decode_tree_with_codec_and_limits(
410    cx: &mut Cx,
411    symbol: &Symbol,
412    input: Input,
413    read_policy: ReadPolicy,
414    source_id: impl Into<String>,
415    limits: DecodeLimits,
416) -> Result<LocatedExprTree> {
417    let value = cx.resolve_codec(symbol)?;
418    let codec =
419        value
420            .object()
421            .downcast_ref::<CodecRuntime>()
422            .ok_or(sim_kernel::Error::TypeMismatch {
423                expected: "codec",
424                found: "non-codec",
425            })?;
426    let mut read_cx = ReadCx {
427        cx,
428        codec: codec.id,
429        read_policy,
430        limits,
431    };
432    codec.decode_tree(&mut read_cx, input, source_id.into())
433}
434
435fn term_from_expr(default_decode: CodecDefaultDecode, expr: sim_kernel::Expr) -> Result<Term> {
436    match default_decode {
437        CodecDefaultDecode::Datum => Term::lower(expr),
438        CodecDefaultDecode::TermInEvalDatumOtherwise => Term::lower(lower_eval_surface(expr)),
439    }
440}
441
442fn lower_eval_surface(expr: sim_kernel::Expr) -> sim_kernel::Expr {
443    use sim_kernel::Expr;
444
445    match expr {
446        Expr::List(items) if items.len() > 1 => {
447            let mut items = items
448                .into_iter()
449                .map(lower_eval_surface)
450                .collect::<Vec<_>>();
451            let operator = Box::new(items.remove(0));
452            Expr::Call {
453                operator,
454                args: items,
455            }
456        }
457        Expr::List(items) => Expr::List(items.into_iter().map(lower_eval_surface).collect()),
458        Expr::Vector(items) => Expr::Vector(items.into_iter().map(lower_eval_surface).collect()),
459        Expr::Map(entries) => Expr::Map(
460            entries
461                .into_iter()
462                .map(|(key, value)| (lower_eval_surface(key), lower_eval_surface(value)))
463                .collect(),
464        ),
465        Expr::Set(items) => Expr::Set(items.into_iter().map(lower_eval_surface).collect()),
466        Expr::Block(items) => Expr::Block(items.into_iter().map(lower_eval_surface).collect()),
467        Expr::Quote { mode, expr } => Expr::Quote { mode, expr },
468        Expr::Annotated { expr, annotations } => Expr::Annotated {
469            expr: Box::new(lower_eval_surface(*expr)),
470            annotations: annotations
471                .into_iter()
472                .map(|(name, value)| (name, lower_eval_surface(value)))
473                .collect(),
474        },
475        Expr::Extension { tag, payload } => Expr::Extension {
476            tag,
477            payload: Box::new(lower_eval_surface(*payload)),
478        },
479        other => other,
480    }
481}