Skip to main content

sim_codec_algol/
runtime.rs

1//! Runtime wiring for the Algol codec: `AlgolCodec` implements the decoder and
2//! encoder over a Pratt parser, and `AlgolCodecLib` is the `Lib` that builds the
3//! manifest and registers the codec with the linker.
4
5use crate::encode::{decode_escape, encode_algol, encode_algol_tree};
6use crate::parse::{decode_algol_located_with_budget, raw_number_tag};
7use crate::pratt::{PrattParser, default_pratt_table};
8use sim_codec::{
9    CodecDefaultDecode, CodecRuntime, DecodeBudget, Decoder, Encoder, Input, LocatedDecoder,
10    Output, ReadCx, TreeDecoder, TreeEncoder, codec_value,
11};
12use sim_kernel::{
13    AbiVersion, DefaultFactory, Dependency, Error, Export, Expr, Lib, LibManifest, LibTarget,
14    Linker, LocatedExprTree, Result, SourceId, Symbol, Version, WriteCx, pratt_table_value,
15};
16use std::sync::Arc;
17
18/// Runtime decoder and encoder for the Algol surface, wrapping a [`PrattParser`]
19/// over [`crate::default_pratt_table`].
20///
21/// Implements the `Decoder`, `LocatedDecoder`, `TreeDecoder`, `Encoder`, and
22/// `TreeEncoder` traits so the codec participates in every decode/encode lane:
23/// it parses infix text into checked `Expr` forms and renders any `Expr` back to
24/// infix text.
25pub struct AlgolCodec {
26    parser: PrattParser,
27}
28
29impl Default for AlgolCodec {
30    fn default() -> Self {
31        Self {
32            parser: PrattParser::new(default_pratt_table()),
33        }
34    }
35}
36
37impl Decoder for AlgolCodec {
38    fn decode(&self, cx: &mut ReadCx<'_>, input: Input) -> Result<Expr> {
39        let source = input.into_string()?;
40        let mut budget = DecodeBudget::new(cx.limits);
41        budget.check_input_bytes(cx.codec, source.len())?;
42        let mut tree =
43            self.parser
44                .parse_text_tree_with_budget(cx.codec, "<algol>", &source, &mut budget)?;
45        rewrite_number_domains_tree(cx, &mut tree)?;
46        decode_escape(cx, tree.expr)
47    }
48}
49
50impl LocatedDecoder for AlgolCodec {
51    fn decode_located(
52        &self,
53        cx: &mut ReadCx<'_>,
54        input: Input,
55        source_id: String,
56    ) -> Result<sim_kernel::LocatedExpr> {
57        let source = input.into_string()?;
58        let mut budget = DecodeBudget::new(cx.limits);
59        budget.check_input_bytes(cx.codec, source.len())?;
60        cx.cx
61            .sources_mut()
62            .intern_text(SourceId(source_id.clone()), &source);
63        let mut located =
64            decode_algol_located_with_budget(cx.codec, source_id, &source, &mut budget)?;
65        located.expr = rewrite_number_domains_expr(cx, located.expr)?;
66        Ok(located)
67    }
68}
69
70impl TreeDecoder for AlgolCodec {
71    fn decode_tree(
72        &self,
73        cx: &mut ReadCx<'_>,
74        input: Input,
75        source_id: String,
76    ) -> Result<LocatedExprTree> {
77        let source = input.into_string()?;
78        let mut budget = DecodeBudget::new(cx.limits);
79        budget.check_input_bytes(cx.codec, source.len())?;
80        cx.cx
81            .sources_mut()
82            .intern_text(SourceId(source_id.clone()), &source);
83        let mut tree =
84            self.parser
85                .parse_text_tree_with_budget(cx.codec, source_id, &source, &mut budget)?;
86        rewrite_number_domains_tree(cx, &mut tree)?;
87        Ok(tree)
88    }
89}
90
91fn rewrite_number_domains_expr(cx: &mut ReadCx<'_>, expr: Expr) -> Result<Expr> {
92    Ok(match expr {
93        Expr::Extension { tag, payload } if tag == raw_number_tag() => {
94            let Expr::String(raw) = *payload else {
95                return Err(Error::CodecError {
96                    codec: cx.codec,
97                    message: "algol number literal payload must be a string".to_owned(),
98                });
99            };
100            match cx.cx.parse_number_literal(&raw)? {
101                Some(number) => Expr::Number(number),
102                None => {
103                    return Err(Error::CodecError {
104                        codec: cx.codec,
105                        message: format!("no number domain accepted literal {raw}"),
106                    });
107                }
108            }
109        }
110        Expr::List(items) => Expr::List(
111            items
112                .into_iter()
113                .map(|item| rewrite_number_domains_expr(cx, item))
114                .collect::<Result<Vec<_>>>()?,
115        ),
116        Expr::Vector(items) => Expr::Vector(
117            items
118                .into_iter()
119                .map(|item| rewrite_number_domains_expr(cx, item))
120                .collect::<Result<Vec<_>>>()?,
121        ),
122        Expr::Map(entries) => Expr::Map(
123            entries
124                .into_iter()
125                .map(|(key, value)| {
126                    Ok((
127                        rewrite_number_domains_expr(cx, key)?,
128                        rewrite_number_domains_expr(cx, value)?,
129                    ))
130                })
131                .collect::<Result<Vec<_>>>()?,
132        ),
133        Expr::Set(items) => Expr::Set(
134            items
135                .into_iter()
136                .map(|item| rewrite_number_domains_expr(cx, item))
137                .collect::<Result<Vec<_>>>()?,
138        ),
139        Expr::Call { operator, args } => Expr::Call {
140            operator: Box::new(rewrite_number_domains_expr(cx, *operator)?),
141            args: args
142                .into_iter()
143                .map(|item| rewrite_number_domains_expr(cx, item))
144                .collect::<Result<Vec<_>>>()?,
145        },
146        Expr::Infix {
147            operator,
148            left,
149            right,
150        } => Expr::Infix {
151            operator,
152            left: Box::new(rewrite_number_domains_expr(cx, *left)?),
153            right: Box::new(rewrite_number_domains_expr(cx, *right)?),
154        },
155        Expr::Prefix { operator, arg } => Expr::Prefix {
156            operator,
157            arg: Box::new(rewrite_number_domains_expr(cx, *arg)?),
158        },
159        Expr::Postfix { operator, arg } => Expr::Postfix {
160            operator,
161            arg: Box::new(rewrite_number_domains_expr(cx, *arg)?),
162        },
163        Expr::Block(items) => Expr::Block(
164            items
165                .into_iter()
166                .map(|item| rewrite_number_domains_expr(cx, item))
167                .collect::<Result<Vec<_>>>()?,
168        ),
169        Expr::Quote { mode, expr } => Expr::Quote { mode, expr },
170        Expr::Annotated { expr, annotations } => Expr::Annotated {
171            expr: Box::new(rewrite_number_domains_expr(cx, *expr)?),
172            annotations: annotations
173                .into_iter()
174                .map(|(name, value)| Ok((name, rewrite_number_domains_expr(cx, value)?)))
175                .collect::<Result<Vec<_>>>()?,
176        },
177        Expr::Extension { tag, payload } => Expr::Extension {
178            tag,
179            payload: Box::new(rewrite_number_domains_expr(cx, *payload)?),
180        },
181        other => other,
182    })
183}
184
185fn rewrite_number_domains_tree(cx: &mut ReadCx<'_>, tree: &mut LocatedExprTree) -> Result<()> {
186    tree.expr = rewrite_number_domains_expr(cx, tree.expr.clone())?;
187    if matches!(tree.expr, Expr::Quote { .. }) {
188        return Ok(());
189    }
190    for child in &mut tree.children {
191        rewrite_number_domains_tree(cx, child)?;
192    }
193    Ok(())
194}
195
196impl Encoder for AlgolCodec {
197    fn encode(&self, cx: &mut WriteCx<'_>, expr: &Expr) -> Result<Output> {
198        Ok(Output::Text(encode_algol(
199            expr,
200            &self.parser.operators,
201            0,
202            cx,
203        )?))
204    }
205}
206
207impl TreeEncoder for AlgolCodec {
208    fn encode_tree(&self, cx: &mut WriteCx<'_>, expr: &LocatedExprTree) -> Result<Output> {
209        Ok(Output::Text(encode_algol_tree(
210            expr,
211            &self.parser.operators,
212            0,
213            cx,
214        )?))
215    }
216}
217
218/// The [`Lib`] that registers the Algol codec (`codec:algol`) with the runtime.
219///
220/// Its manifest depends on `codec:lisp` (used by the `expr.lisp(...)` escape for
221/// forms outside the infix grammar) and exports the codec value plus the
222/// `pratt:arithmetic` operator table value.
223pub struct AlgolCodecLib {
224    symbol: Symbol,
225    codec_id: sim_kernel::CodecId,
226}
227
228impl AlgolCodecLib {
229    /// Creates the lib bound to the given runtime-assigned codec id.
230    pub fn new(id: sim_kernel::CodecId) -> Self {
231        Self {
232            symbol: Symbol::qualified("codec", "algol"),
233            codec_id: id,
234        }
235    }
236}
237
238impl Lib for AlgolCodecLib {
239    fn manifest(&self) -> LibManifest {
240        LibManifest {
241            id: self.symbol.clone(),
242            version: Version(env!("CARGO_PKG_VERSION").to_owned()),
243            abi: AbiVersion { major: 0, minor: 1 },
244            target: LibTarget::HostRegistered,
245            requires: vec![Dependency {
246                id: Symbol::qualified("codec", "lisp"),
247                minimum_version: None,
248            }],
249            capabilities: Vec::new(),
250            exports: vec![
251                Export::Codec {
252                    symbol: self.symbol.clone(),
253                    codec_id: Some(self.codec_id),
254                },
255                Export::Value {
256                    symbol: Symbol::qualified("pratt", "arithmetic"),
257                },
258            ],
259        }
260    }
261
262    fn load(&self, _cx: &mut sim_kernel::LoadCx, linker: &mut Linker) -> Result<()> {
263        let _factory = DefaultFactory;
264        let expr_shape =
265            sim_codec::resolve_expr_shape(linker, &Symbol::qualified("codec", "AlgolSurface"))?;
266        let options_shape = sim_codec::resolve_options_shape(linker)?;
267
268        linker.codec_value(
269            self.symbol.clone(),
270            codec_value(CodecRuntime {
271                id: self.codec_id,
272                symbol: self.symbol.clone(),
273                decoder: Some(Arc::new(AlgolCodec::default())),
274                located_decoder: Some(Arc::new(AlgolCodec::default())),
275                tree_decoder: Some(Arc::new(AlgolCodec::default())),
276                encoder: Some(Arc::new(AlgolCodec::default())),
277                located_encoder: None,
278                tree_encoder: Some(Arc::new(AlgolCodec::default())),
279                expr_shape,
280                options_shape,
281                default_decode: CodecDefaultDecode::TermInEvalDatumOtherwise,
282            }),
283        )?;
284        linker.value(
285            Symbol::qualified("pratt", "arithmetic"),
286            pratt_table_value(
287                Symbol::qualified("pratt", "arithmetic"),
288                default_pratt_table(),
289            ),
290        )?;
291        Ok(())
292    }
293}