Skip to main content

sim_codec_json/
tree_json.rs

1//! Located and tree `Expr <-> JSON` projections.
2//!
3//! Extends the canonical projection to carry source origins: emits and parses
4//! `$located` forms for `LocatedExpr` and origin-bearing `LocatedExprTree`
5//! values.
6
7use serde_json::{Map, Value as JsonValue, json};
8use sim_codec::DecodeBudget;
9use sim_kernel::{
10    Expr, LocatedExpr, LocatedExprTree, NumberLiteral, Origin, Result, SourceId, Span, Trivia,
11};
12
13use crate::expr_json::{expr_to_json, json_to_expr};
14use crate::helpers::{
15    base64_decode, base64_encode, json_error, json_to_symbol, local_to_json, optional_bytes,
16    parse_quote_mode, parse_symbol, quote_mode_name, required_array, required_bool, required_str,
17    required_u64, required_value, symbol_from_json, symbol_to_json,
18};
19
20/// Projects a [`LocatedExpr`] onto JSON, optionally wrapping it in a `$located`
21/// form that carries its source origin when `include_origin` is set.
22pub fn located_expr_to_json(located: &LocatedExpr, include_origin: bool) -> JsonValue {
23    if !include_origin {
24        return expr_to_json(&located.expr);
25    }
26
27    let mut object = Map::new();
28    object.insert("$located".to_owned(), expr_to_json(&located.expr));
29    if let Some(origin) = &located.origin {
30        object.insert("origin".to_owned(), origin_to_json(origin));
31    }
32    JsonValue::Object(object)
33}
34
35/// Projects a [`LocatedExprTree`] onto JSON, recursively carrying per-node
36/// origins as `$located` forms when `include_origin` is set.
37pub fn tree_to_json(tree: &LocatedExprTree, include_origin: bool) -> JsonValue {
38    tree_expr_to_json(tree, include_origin)
39}
40
41/// Reads JSON back into a [`LocatedExpr`], recovering the source origin from a
42/// `$located` wrapper when present and otherwise decoding a bare expression.
43pub fn json_to_located_expr(
44    codec: sim_kernel::CodecId,
45    value: &JsonValue,
46    budget: &mut DecodeBudget,
47    depth: usize,
48) -> Result<LocatedExpr> {
49    if let Some(object) = value.as_object()
50        && let Some(expr_value) = object.get("$located")
51    {
52        return Ok(LocatedExpr {
53            expr: json_to_expr(codec, expr_value, budget, depth)?,
54            origin: object
55                .get("origin")
56                .map(|origin| json_to_origin(codec, origin, budget))
57                .transpose()?,
58        });
59    }
60
61    Ok(LocatedExpr {
62        expr: json_to_expr(codec, value, budget, depth)?,
63        origin: None,
64    })
65}
66
67/// Reads JSON back into a [`LocatedExprTree`], recovering per-node origins from
68/// `$located` wrappers, under the decode `budget` and node `depth` limits.
69pub fn json_to_tree(
70    codec: sim_kernel::CodecId,
71    value: &JsonValue,
72    budget: &mut DecodeBudget,
73    depth: usize,
74) -> Result<LocatedExprTree> {
75    if let Some(object) = value.as_object()
76        && let Some(expr_value) = object.get("$located")
77    {
78        let mut tree = json_expr_to_tree(codec, expr_value, budget, depth)?;
79        tree.origin = object
80            .get("origin")
81            .map(|origin| json_to_origin(codec, origin, budget))
82            .transpose()?;
83        return Ok(tree);
84    }
85    json_expr_to_tree(codec, value, budget, depth)
86}
87
88fn json_expr_to_tree(
89    codec: sim_kernel::CodecId,
90    value: &JsonValue,
91    budget: &mut DecodeBudget,
92    depth: usize,
93) -> Result<LocatedExprTree> {
94    budget.enter_node(codec, depth)?;
95    let Some(tag) = value.get("$expr").and_then(JsonValue::as_str) else {
96        return Err(json_error(codec, "missing $expr"));
97    };
98
99    match tag {
100        "nil" => Ok(LocatedExprTree::without_children(Expr::Nil, None)),
101        "bool" => Ok(LocatedExprTree::without_children(
102            Expr::Bool(required_bool(codec, value, "value")?),
103            None,
104        )),
105        "number" => Ok(LocatedExprTree::without_children(
106            Expr::Number(NumberLiteral {
107                domain: parse_symbol(required_str(codec, value, "domain")?),
108                canonical: required_str(codec, value, "value")?.to_owned(),
109            }),
110            None,
111        )),
112        "symbol" => Ok(LocatedExprTree::without_children(
113            json_to_symbol(codec, value)?,
114            None,
115        )),
116        "local" => Ok(LocatedExprTree::without_children(
117            Expr::Local(symbol_from_json(codec, value)?),
118            None,
119        )),
120        "string" => {
121            let text = required_str(codec, value, "value")?;
122            budget.check_string_bytes(codec, text.len())?;
123            Ok(LocatedExprTree::without_children(
124                Expr::String(text.to_owned()),
125                None,
126            ))
127        }
128        "bytes" => {
129            let encoded = required_str(codec, value, "base64")?;
130            budget.check_blob_bytes(codec, encoded.len())?;
131            let decoded = base64_decode(codec, encoded)?;
132            budget.check_blob_bytes(codec, decoded.len())?;
133            Ok(LocatedExprTree::without_children(
134                Expr::Bytes(decoded),
135                None,
136            ))
137        }
138        "list" => tree_sequence(codec, value, "items", Expr::List, budget, depth + 1),
139        "vector" => tree_sequence(codec, value, "items", Expr::Vector, budget, depth + 1),
140        "map" => {
141            let entries = required_array(codec, value, "entries")?;
142            budget.check_collection_len(codec, entries.len())?;
143            let entries = entries
144                .iter()
145                .map(|entry| {
146                    Ok((
147                        json_to_tree(
148                            codec,
149                            required_value(codec, entry, "key")?,
150                            budget,
151                            depth + 1,
152                        )?,
153                        json_to_tree(
154                            codec,
155                            required_value(codec, entry, "value")?,
156                            budget,
157                            depth + 1,
158                        )?,
159                    ))
160                })
161                .collect::<Result<Vec<_>>>()?;
162            Ok(LocatedExprTree {
163                expr: Expr::Map(
164                    entries
165                        .iter()
166                        .map(|(key, value)| (key.expr.clone(), value.expr.clone()))
167                        .collect(),
168                ),
169                origin: None,
170                children: entries
171                    .into_iter()
172                    .flat_map(|(key, value)| [key, value])
173                    .collect(),
174            })
175        }
176        "set" => tree_sequence(codec, value, "items", Expr::Set, budget, depth + 1),
177        "call" => {
178            let operator = json_to_tree(
179                codec,
180                required_value(codec, value, "operator")?,
181                budget,
182                depth + 1,
183            )?;
184            let raw_args = required_array(codec, value, "args")?;
185            budget.check_collection_len(codec, raw_args.len())?;
186            let args = raw_args
187                .iter()
188                .map(|arg| json_to_tree(codec, arg, budget, depth + 1))
189                .collect::<Result<Vec<_>>>()?;
190            let mut children = Vec::with_capacity(args.len() + 1);
191            children.push(operator.clone());
192            children.extend(args.iter().cloned());
193            Ok(LocatedExprTree {
194                expr: Expr::Call {
195                    operator: Box::new(operator.expr.clone()),
196                    args: args.iter().map(|arg| arg.expr.clone()).collect(),
197                },
198                origin: None,
199                children,
200            })
201        }
202        "infix" => {
203            let left = json_to_tree(
204                codec,
205                required_value(codec, value, "left")?,
206                budget,
207                depth + 1,
208            )?;
209            let right = json_to_tree(
210                codec,
211                required_value(codec, value, "right")?,
212                budget,
213                depth + 1,
214            )?;
215            Ok(LocatedExprTree {
216                expr: Expr::Infix {
217                    operator: symbol_from_json(codec, required_value(codec, value, "operator")?)?,
218                    left: Box::new(left.expr.clone()),
219                    right: Box::new(right.expr.clone()),
220                },
221                origin: None,
222                children: vec![left, right],
223            })
224        }
225        "prefix" => {
226            let arg = json_to_tree(
227                codec,
228                required_value(codec, value, "arg")?,
229                budget,
230                depth + 1,
231            )?;
232            Ok(LocatedExprTree {
233                expr: Expr::Prefix {
234                    operator: symbol_from_json(codec, required_value(codec, value, "operator")?)?,
235                    arg: Box::new(arg.expr.clone()),
236                },
237                origin: None,
238                children: vec![arg],
239            })
240        }
241        "postfix" => {
242            let arg = json_to_tree(
243                codec,
244                required_value(codec, value, "arg")?,
245                budget,
246                depth + 1,
247            )?;
248            Ok(LocatedExprTree {
249                expr: Expr::Postfix {
250                    operator: symbol_from_json(codec, required_value(codec, value, "operator")?)?,
251                    arg: Box::new(arg.expr.clone()),
252                },
253                origin: None,
254                children: vec![arg],
255            })
256        }
257        "block" => tree_sequence(codec, value, "items", Expr::Block, budget, depth + 1),
258        "quote" => {
259            let expr = json_to_tree(
260                codec,
261                required_value(codec, value, "expr")?,
262                budget,
263                depth + 1,
264            )?;
265            Ok(LocatedExprTree {
266                expr: Expr::Quote {
267                    mode: parse_quote_mode(codec, required_str(codec, value, "mode")?)?,
268                    expr: Box::new(expr.expr.clone()),
269                },
270                origin: None,
271                children: vec![expr],
272            })
273        }
274        "annotated" => {
275            let expr = json_to_tree(
276                codec,
277                required_value(codec, value, "expr")?,
278                budget,
279                depth + 1,
280            )?;
281            let annotations = required_array(codec, value, "annotations")?;
282            budget.check_collection_len(codec, annotations.len())?;
283            let annotations = annotations
284                .iter()
285                .map(|entry| {
286                    Ok((
287                        symbol_from_json(codec, required_value(codec, entry, "key")?)?,
288                        json_to_tree(
289                            codec,
290                            required_value(codec, entry, "value")?,
291                            budget,
292                            depth + 1,
293                        )?,
294                    ))
295                })
296                .collect::<Result<Vec<_>>>()?;
297            let mut children = Vec::with_capacity(annotations.len() + 1);
298            children.push(expr.clone());
299            children.extend(annotations.iter().map(|(_, value)| value.clone()));
300            Ok(LocatedExprTree {
301                expr: Expr::Annotated {
302                    expr: Box::new(expr.expr.clone()),
303                    annotations: annotations
304                        .iter()
305                        .map(|(key, value)| (key.clone(), value.expr.clone()))
306                        .collect(),
307                },
308                origin: None,
309                children,
310            })
311        }
312        "extension" => {
313            let payload = json_to_tree(
314                codec,
315                required_value(codec, value, "payload")?,
316                budget,
317                depth + 1,
318            )?;
319            Ok(LocatedExprTree {
320                expr: Expr::Extension {
321                    tag: symbol_from_json(codec, required_value(codec, value, "tag")?)?,
322                    payload: Box::new(payload.expr.clone()),
323                },
324                origin: None,
325                children: vec![payload],
326            })
327        }
328        other => Err(json_error(codec, format!("unknown expr tag {other}"))),
329    }
330}
331
332fn tree_expr_to_json(tree: &LocatedExprTree, include_origin: bool) -> JsonValue {
333    let expr_value = match &tree.expr {
334        Expr::Nil => json!({ "$expr": "nil" }),
335        Expr::Bool(value) => json!({ "$expr": "bool", "value": value }),
336        Expr::Number(number) => json!({
337            "$expr": "number",
338            "domain": number.domain.to_string(),
339            "value": number.canonical,
340        }),
341        Expr::Symbol(symbol) => symbol_to_json(symbol),
342        Expr::Local(symbol) => local_to_json(symbol),
343        Expr::String(value) => json!({ "$expr": "string", "value": value }),
344        Expr::Bytes(value) => json!({ "$expr": "bytes", "base64": base64_encode(value) }),
345        Expr::List(_) => tree_sequence_json("list", &tree.children, include_origin),
346        Expr::Vector(_) => tree_sequence_json("vector", &tree.children, include_origin),
347        Expr::Map(_) => json!({
348            "$expr": "map",
349            "entries": tree.children.chunks(2).map(|pair| json!({
350                "key": tree_to_json(&pair[0], include_origin),
351                "value": tree_to_json(&pair[1], include_origin),
352            })).collect::<Vec<_>>(),
353        }),
354        Expr::Set(_) => tree_sequence_json("set", &tree.children, include_origin),
355        Expr::Call { .. } => json!({
356            "$expr": "call",
357            "operator": tree_to_json(&tree.children[0], include_origin),
358            "args": tree.children[1..].iter().map(|item| tree_to_json(item, include_origin)).collect::<Vec<_>>(),
359        }),
360        Expr::Infix { operator, .. } => json!({
361            "$expr": "infix",
362            "operator": symbol_to_json(operator),
363            "left": tree_to_json(&tree.children[0], include_origin),
364            "right": tree_to_json(&tree.children[1], include_origin),
365        }),
366        Expr::Prefix { operator, .. } => json!({
367            "$expr": "prefix",
368            "operator": symbol_to_json(operator),
369            "arg": tree_to_json(&tree.children[0], include_origin),
370        }),
371        Expr::Postfix { operator, .. } => json!({
372            "$expr": "postfix",
373            "operator": symbol_to_json(operator),
374            "arg": tree_to_json(&tree.children[0], include_origin),
375        }),
376        Expr::Block(_) => tree_sequence_json("block", &tree.children, include_origin),
377        Expr::Quote { mode, .. } => json!({
378            "$expr": "quote",
379            "mode": quote_mode_name(*mode),
380            "expr": tree_to_json(&tree.children[0], include_origin),
381        }),
382        Expr::Annotated { annotations, .. } => json!({
383            "$expr": "annotated",
384            "expr": tree_to_json(&tree.children[0], include_origin),
385            "annotations": annotations.iter().zip(tree.children[1..].iter()).map(|((key, _), value)| json!({
386                "key": symbol_to_json(key),
387                "value": tree_to_json(value, include_origin),
388            })).collect::<Vec<_>>(),
389        }),
390        Expr::Extension { tag, .. } => json!({
391            "$expr": "extension",
392            "tag": symbol_to_json(tag),
393            "payload": tree_to_json(&tree.children[0], include_origin),
394        }),
395    };
396    if include_origin && let Some(origin) = tree.origin.as_ref() {
397        let mut object = Map::new();
398        object.insert("$located".to_owned(), expr_value);
399        object.insert("origin".to_owned(), origin_to_json(origin));
400        JsonValue::Object(object)
401    } else {
402        expr_value
403    }
404}
405
406fn tree_sequence_json(
407    tag: &'static str,
408    items: &[LocatedExprTree],
409    include_origin: bool,
410) -> JsonValue {
411    json!({
412        "$expr": tag,
413        "items": items.iter().map(|item| tree_to_json(item, include_origin)).collect::<Vec<_>>(),
414    })
415}
416
417fn tree_sequence(
418    codec: sim_kernel::CodecId,
419    value: &JsonValue,
420    field: &str,
421    build: fn(Vec<Expr>) -> Expr,
422    budget: &mut DecodeBudget,
423    depth: usize,
424) -> Result<LocatedExprTree> {
425    let children = required_array(codec, value, field)?;
426    budget.check_collection_len(codec, children.len())?;
427    let children = children
428        .iter()
429        .map(|item| json_to_tree(codec, item, budget, depth))
430        .collect::<Result<Vec<_>>>()?;
431    Ok(LocatedExprTree {
432        expr: build(children.iter().map(|item| item.expr.clone()).collect()),
433        origin: None,
434        children,
435    })
436}
437
438fn origin_to_json(origin: &Origin) -> JsonValue {
439    json!({
440        "codec": origin.codec.0,
441        "source": origin.source.0,
442        "span": {
443            "start": origin.span.start,
444            "end": origin.span.end,
445        },
446        "trivia": origin.trivia.iter().map(trivia_to_json).collect::<Vec<_>>(),
447    })
448}
449
450fn trivia_to_json(trivia: &Trivia) -> JsonValue {
451    match trivia {
452        Trivia::Whitespace(text) => json!({ "kind": "whitespace", "text": text }),
453        Trivia::LineComment(text) => json!({ "kind": "line-comment", "text": text }),
454        Trivia::BlockComment(text) => json!({ "kind": "block-comment", "text": text }),
455    }
456}
457
458fn json_to_origin(
459    codec: sim_kernel::CodecId,
460    value: &JsonValue,
461    budget: &mut DecodeBudget,
462) -> Result<Origin> {
463    let trivia = required_array(codec, value, "trivia")?;
464    budget.check_collection_len(codec, trivia.len())?;
465    let origin = Origin {
466        codec: sim_kernel::CodecId(required_u64(codec, value, "codec")? as u32),
467        source: SourceId(required_str(codec, value, "source")?.to_owned()),
468        span: {
469            let span = required_value(codec, value, "span")?;
470            Span {
471                start: required_u64(codec, span, "start")? as usize,
472                end: required_u64(codec, span, "end")? as usize,
473            }
474        },
475        trivia: trivia
476            .iter()
477            .map(|item| json_to_trivia(codec, item, budget))
478            .collect::<Result<Vec<_>>>()?,
479    };
480    if let Some(raw) = optional_bytes(codec, value, "raw")? {
481        let mut registry = sim_kernel::SourceRegistry::default();
482        registry.intern_span(&origin, &raw);
483    }
484    Ok(origin)
485}
486
487fn json_to_trivia(
488    codec: sim_kernel::CodecId,
489    value: &JsonValue,
490    budget: &mut DecodeBudget,
491) -> Result<Trivia> {
492    budget.add_trivia(codec)?;
493    let kind = required_str(codec, value, "kind")?;
494    let text = required_str(codec, value, "text")?.to_owned();
495    match kind {
496        "whitespace" => Ok(Trivia::Whitespace(text)),
497        "line-comment" => Ok(Trivia::LineComment(text)),
498        "block-comment" => Ok(Trivia::BlockComment(text)),
499        other => Err(json_error(codec, format!("unknown trivia kind {other}"))),
500    }
501}