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