elly_core/ast.rs
1//! The Elly AST and its s-expression dump.
2//!
3//! `to_sexp` provides a compact parenthesized form for parse golden tests. Leaf text
4//! uses [`Text`] (inline/`Rc` handle) to make the AST `'static` and independent of
5//! Muon source lifetimes; structure is `alloc`ated.
6//!
7//! Patterns are first-class AST nodes (see `docs/done/2026-07-26_elly-patterns-native.md`)
8//! used by `&`-abstractions, `let` bindings, and `__match` clauses. The evaluator
9//! interprets [`Pattern`] nodes directly; the AST rephrases Muon syntax without
10//! compiling patterns, which is deferred to the VM.
11
12use alloc::boxed::Box;
13use alloc::format;
14use alloc::rc::Rc;
15use alloc::string::{String, ToString};
16use alloc::vec::Vec;
17
18use num_bigint::BigInt;
19
20pub use crate::eval::Builtin;
21pub use crate::module::ModuleData;
22
23/// An owned, cheap-clone text leaf using hipstr's `Local` (`Rc`) backend.
24/// Short strings (≤ 23 bytes on 64-bit) are inlined; longer strings use a shared
25/// `Rc<str>` slice. This ensures `Expr` and `Value` are `'static` and independent
26/// of the Muon source. Host tags are stored via [`Text::from_static`].
27pub type Text = hipstr::LocalHipStr<'static>;
28
29/// An Elly expression for the initial subset. Strings and records are deferred
30/// per the spec.
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub enum Expr {
33 /// Arbitrary-precision integer literal parsed from tokens (`0`, `-123`, `0xCAFE`).
34 Int(BigInt),
35 /// The unit value `()`. Used as a nullary marker in chains (`f ()`), standalone,
36 /// or as empty-value sugar in maps (`{ .k: }`). Matches [`Pattern::Unit`].
37 /// Rendered as `()`.
38 Unit,
39 /// Runtime list literal from Muon `#`-prefixed brackets (`#[…]`). Supports
40 /// any arity, including empty `#[ ]` or single-element `#[e]`. Grouping
41 /// parentheses `(…)` are handled at parse time.
42 List(Vec<Expr>),
43 /// Unresolved reference to a binding or free name. The resolver rewrites
44 /// in-scope names to [`Expr::Local`] and rejects others. Evaluator only
45 /// sees `Local`.
46 Name(Text),
47 /// Resolved reference using a de Bruijn `index`. The environment is a cons
48 /// list of bindings (innermost first); `index` is the distance from the top.
49 /// The original `name` is boxed and kept for rendering to keep `Expr` at four words.
50 Local { name: Box<Text>, index: u32 },
51 /// A `__`-builtin resolved at parse time via `Builtin::from_name`. Since `__`
52 /// names are unbindable, this avoids environment lookups at runtime. Renders
53 /// as the `Name` it replaces.
54 Builtin(Builtin),
55 /// Reference to an item in the enclosing module. Resolved by
56 /// `resolve_module_bodies`. Evaluated via the home module in the environment.
57 /// `index` is the item's position in the sorted item table. `depth` is the
58 /// number of [`Module`](crate::EnvNode::Module) terminals to skip. `name` is
59 /// boxed for size.
60 ModItem {
61 name: Box<Text>,
62 index: u32,
63 depth: u32,
64 },
65 /// Recursive binding group: `recur (a = va, b = vb) e`. Bindings are simultaneous
66 /// and mutually visible. The group is treated as a module where bindings are
67 /// items and the scoped expression is the module body. See
68 /// `docs/done/2026-08-13_elly-recur.md`.
69 Recur(Rc<ModuleData>),
70 /// Dotted literal (`.foo`, `.0`), stored without the leading dot.
71 Symbol(Text),
72 /// UTF-8 string literal decoded from Muon `<str>`. Evaluates to `Value::Str`.
73 /// Rendered as a quoted literal.
74 Str(Text),
75 /// Flattened call spine: index 0 is the callee, `[1..]` are arguments. Covers
76 /// both standard and spread calls. Evaluated via `apply_n` in `eval.rs`. Holds
77 /// at least two elements; currying is handled by the evaluator.
78 App(Box<[Expr]>),
79 /// Abstraction `&<pattern> body`. The `Lambda` (patterns and body) is shared
80 /// via `Rc`. Multiple `&` parameters are collected into one `Lambda`. Evaluator
81 /// creates closures by capturing the environment. Renders as nested `(abs …)`
82 /// for stability.
83 Abs(Rc<Lambda>),
84 /// Map literal from Muon `#{ … }`. Entries are (key, value) pairs in source
85 /// order. Keys are evaluated at runtime.
86 Map(Vec<(Expr, Expr)>),
87 /// Reference to the home module: `Self`, `#Self(…)`, or `x.Self`. Found via the
88 /// environment's terminal. `depth` is the number of `Module` terminals to skip.
89 /// See `docs/done/2026-09-11_elly-v1-self.md`.
90 Home { leaf: HomeLeaf, depth: u32 },
91 /// Sequence `{ c0, c1, …, e }`, also the target for `let`. Clauses are evaluated
92 /// sequentially in a shared scope; the last clause's value is the block's result.
93 /// Represented explicitly to support future effects (e.g., `break`, `defer`).
94 /// Boxed to keep `Expr` at four words.
95 Block(Box<[(Option<Pattern>, Expr)]>),
96 /// Matching construct `case <subject> { &p0 b0, ... }`. Evaluates subject once
97 /// and executes the first matching arm's body. Raises `.no_match` if no arms
98 /// match. Subjectless cases are lowered to `&__arg (case __arg { ... })`.
99 Case {
100 subject: Rc<Expr>,
101 cases: Box<[(Pattern, Expr)]>,
102 },
103}
104
105/// Home-module leaf types: the module itself (`Self`), construction
106/// (`#Self(…)`), or payload read (`x.Self`). These share the same resolution
107/// machinery (one walk to the terminal) and differ only in their eval result.
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109pub enum HomeLeaf {
110 /// `Self` — the home module as a `Value::Module`.
111 Module,
112 /// `#Self(…)` / `#Self …` — the object constructor for the home module.
113 /// Always the callee of an [`App`](Expr::App). Renders as `(new …)`.
114 New,
115 /// `x.Self` — the payload reader for the home module. Always the callee
116 /// of an [`App`](Expr::App). In patterns, spelled as [`Pattern::Unwrap`].
117 Value,
118}
119
120impl HomeLeaf {
121 /// The name reported by the resolver for leaves outside a module body.
122 pub fn name(self) -> &'static str {
123 "Self"
124 }
125}
126// TODO: annotate expressions with source spans
127// TODO: do not hardcode `Vec`/`Box`
128
129/// Returns the body of a `recur` group. Required by grammar; `ModuleData`
130/// uses `Option` because standalone modules may lack one.
131pub(crate) fn recur_body(data: &ModuleData) -> &Expr {
132 data.body().expect("a recur group always has a body")
133}
134
135/// The code of an abstraction: parameter patterns and body. Shared via `Rc`
136/// between [`Expr::Abs`] and `Value::Closure`. Arity is `head.len()`, capped
137/// at 255. Every abstraction binds at least one parameter.
138#[derive(Debug, Clone, PartialEq, Eq)]
139pub struct Lambda {
140 pub head: Box<[Pattern]>,
141 pub body: Expr,
142 /// Environment capture strategy, determined by the resolver.
143 pub captures: Captures,
144}
145
146impl Lambda {
147 /// Number of parameters bound before the body executes.
148 pub fn arity(&self) -> usize {
149 self.head.len()
150 }
151}
152
153/// Environment capture strategy for a [`Lambda`] closure.
154#[derive(Debug, Clone, PartialEq, Eq)]
155pub enum Captures {
156 /// Unresolved state from the parser. Refused by the evaluator.
157 Unresolved,
158 /// Share the defining environment by reference. Used when the closure
159 /// cannot outlive its creator (e.g., direct applications, `__match` arms,
160 /// or `__Int.for` callbacks), avoiding frame allocation.
161 Chain,
162 /// Capture free variables into a fresh frame, sorted by `outer` index.
163 /// This is the default for escaping closures. An empty plan is a closed lambda.
164 Frame(Box<[Capture]>),
165}
166
167/// A captured free variable: `outer` is the de Bruijn index in the enclosing
168/// activation at evaluation; `slot` is its position in the closure's frame.
169/// The plan is sorted by `outer` so the evaluator fills the frame in one
170/// linear walk of the environment.
171#[derive(Debug, Clone, Copy, PartialEq, Eq)]
172pub struct Capture {
173 pub outer: u32,
174 pub slot: u32,
175}
176
177/// A pattern: the left-hand side of a binding (`&<pat> …`, `let (<pat> = …)`,
178/// or a `__match` clause). Matched by the evaluator's native matcher
179/// (`match_pattern` in `eval.rs`). `Bind`/`Discard` are irrefutable.
180#[derive(Debug, Clone, PartialEq, Eq)]
181pub enum Pattern {
182 /// `_` — matches any value without binding.
183 Discard,
184 /// `name` — binds the subject to `name`. A cell is consed onto the environment;
185 /// names are resolved to de Bruijn indices at parse time.
186 Bind(Text),
187 /// `name = <pat>` — binds the subject to `name`, then matches `<pat>` against
188 /// the same subject.
189 At(Text, Box<Pattern>),
190 /// `= <expr>` — matches if the subject equals the value of `<expr>`.
191 /// Evaluation errors propagate; they are not refutations.
192 Equal(Expr),
193 /// `< <expr>` — matches if the subject is ordered before the value of `<expr>`.
194 /// Only defined for comparable kinds (e.g., `Int`); others refute with `.not_int`.
195 Less(Expr),
196 /// `> <expr>` — matches if the subject is ordered after the value of `<expr>`.
197 Greater(Expr),
198 /// `<pat> as <Proto>` — matches if the subject's prototype is `<Proto>`, then
199 /// matches `<pat>` against the subject.
200 Type(ProtoRef, Box<Pattern>),
201 /// `Self <pat>` — matches if the subject is an object of the home module and
202 /// its payload matches `<pat>`. Prototype check is implicit.
203 /// `depth` is the distance to the home module terminal.
204 Unwrap { depth: u32, inner: Box<Pattern> },
205 /// `(p1 | p2)` — tries arms sequentially. Both arms must bind the same names.
206 /// Once an arm matches, the pattern is committed (no backtracking).
207 Or(Box<Pattern>, Box<Pattern>),
208 /// `&()` — matches only the unit value.
209 Unit,
210 /// `[p0, ...]` list pattern. `rest` handles the remainder: `None` for exact
211 /// arity, `Some(Discard)` for minimum arity, or `Some(Bind(name))` to bind.
212 List {
213 elems: Vec<Pattern>,
214 rest: Option<Box<Pattern>>,
215 },
216 /// `{ k0: p0, ... }` map pattern. `rest` handles the remainder similarly to lists.
217 Map {
218 entries: Vec<(MapKey, Pattern)>,
219 rest: Option<Box<Pattern>>,
220 },
221 /// `<pat> when (<cond>)` guarded pattern. Matches `inner`, then the guard.
222 /// Plain guards must be `Bool`; pattern guards match `gpat` against the condition.
223 When {
224 inner: Box<Pattern>,
225 when: (Option<Box<Pattern>>, Expr),
226 },
227}
228
229/// Map-pattern key. `Lookup` probes a concrete key; `Capture` peels the first
230/// remaining entry and matches its key against the pattern.
231#[derive(Debug, Clone, PartialEq, Eq)]
232pub enum MapKey {
233 Lookup(Expr),
234 Capture(Box<Pattern>),
235}
236
237/// Prototype used in `as <Proto>` patterns.
238#[derive(Debug, Clone, PartialEq, Eq)]
239pub enum ProtoRef {
240 /// Fast form: a reference to a builtin module prototype (e.g., `Int`).
241 /// Compiles to a discriminant test [`TyKind`].
242 Kind(TyKind),
243 /// Any other prototype, evaluated at match time and required to be a module.
244 Ref(Expr),
245}
246
247impl ProtoRef {
248 /// Render the reference: a kind by its bare name (`Int`), so the existing
249 /// goldens are unchanged, and anything else as the expression it is.
250 fn write_sexp(&self, out: &mut String) {
251 match self {
252 ProtoRef::Kind(ty) => out.push_str(ty.name()),
253 ProtoRef::Ref(expr) => expr.write_sexp(out),
254 }
255 }
256
257 fn write_json(&self, out: &mut String) {
258 match self {
259 ProtoRef::Kind(ty) => json_str(ty.name(), out),
260 ProtoRef::Ref(expr) => expr.write_json(out),
261 }
262 }
263}
264
265/// Value kinds with builtin module prototypes, allowing `as <Proto>` to
266/// compile to a discriminant test (see [`ProtoRef::Kind`]).
267#[derive(Debug, Clone, Copy, PartialEq, Eq)]
268pub enum TyKind {
269 Int,
270 Sym,
271 Str,
272 List,
273 Map,
274 Fun,
275 /// Module value (prototype `__Mod`).
276 Mod,
277 /// Unit value (prototype `__Unit`).
278 Unit,
279 /// Boolean (iota of `__Bool`).
280 Bool,
281}
282
283impl TyKind {
284 /// Spelling used in `as <Proto>` surface and dumps.
285 pub fn name(self) -> &'static str {
286 match self {
287 TyKind::Int => "Int",
288 TyKind::Sym => "Sym",
289 TyKind::Str => "Str",
290 TyKind::List => "List",
291 TyKind::Map => "Map",
292 TyKind::Fun => "Fun",
293 TyKind::Mod => "Mod",
294 TyKind::Unit => "Unit",
295 TyKind::Bool => "Bool",
296 }
297 }
298}
299
300impl Expr {
301 /// Serialize to a compact s-expression, e.g. `(app (name f) (name x))`.
302 pub fn to_sexp(&self) -> String {
303 let mut out = String::new();
304 self.write_sexp(&mut out);
305 out
306 }
307
308 // TODO: replace with impl de::Deserialize, impl ser::Serialize
309 /// Serialize to compact, tagged JSON, e.g. `{"app":[{"name":"f"},{"name":"x"}]}`.
310 /// Used by the `elly-wasm` playground to render the AST.
311 pub fn to_json(&self) -> String {
312 let mut out = String::new();
313 self.write_json(&mut out);
314 out
315 }
316
317 fn write_json(&self, out: &mut String) {
318 match self {
319 Expr::Int(n) => {
320 // Emit as bare number if it fits in f64 [-2^53, 2^53] exactly;
321 // otherwise emit as a tagged decimal string to avoid precision loss.
322 if fits_f64_exactly(n) {
323 out.push_str(&n.to_string());
324 } else {
325 out.push_str("{\"Int\":");
326 json_str(&n.to_string(), out);
327 out.push('}');
328 }
329 }
330 // Name, Local, and Builtin all render by name.
331 Expr::Name(n) => {
332 out.push_str("{\"name\":");
333 json_str(n, out);
334 out.push('}');
335 }
336 Expr::Local { name, .. } => {
337 out.push_str("{\"name\":");
338 json_str(name.as_str(), out);
339 out.push('}');
340 }
341 Expr::Builtin(op) => {
342 out.push_str("{\"name\":");
343 json_str(op.name(), out);
344 out.push('}');
345 }
346 // Home leaves render as `{"self":true}`, `{"new":true}`, or `{"value":true}`.
347 // Depth is derived and not rendered.
348 Expr::Home { leaf, .. } => match leaf {
349 HomeLeaf::Module => out.push_str("{\"self\":true}"),
350 HomeLeaf::New => out.push_str("{\"new\":true}"),
351 HomeLeaf::Value => out.push_str("{\"value\":true}"),
352 },
353 Expr::ModItem { name, .. } => {
354 out.push_str("{\"moditem\":");
355 json_str(name.as_str(), out);
356 out.push('}');
357 }
358 Expr::Recur(data) => {
359 out.push_str("{\"recur\":[");
360 for (i, (name, body)) in data.items().iter().enumerate() {
361 if i > 0 {
362 out.push(',');
363 }
364 out.push('[');
365 json_str(name.as_str(), out);
366 out.push(',');
367 body.write_json(out);
368 out.push(']');
369 }
370 out.push_str("],\"body\":");
371 recur_body(data).write_json(out);
372 out.push('}');
373 }
374 Expr::Symbol(s) => {
375 let mut dotted = String::from(".");
376 dotted.push_str(s);
377 out.push_str("{\"sym\":");
378 json_str(&dotted, out);
379 out.push('}');
380 }
381 Expr::Str(s) => {
382 out.push_str("{\"str\":");
383 json_str(s, out);
384 out.push('}');
385 }
386 // Render the call spine flat to preserve stored arity.
387 Expr::App(items) => write_app_json(items, out),
388 // Render collected head as nested single-parameter abstractions.
389 Expr::Abs(code) => write_abs_json(&code.head, &code.body, out),
390 Expr::Unit => out.push_str("{\"unit\":true}"),
391 Expr::List(elems) => {
392 out.push_str("{\"list\":[");
393 for (i, e) in elems.iter().enumerate() {
394 if i > 0 {
395 out.push(',');
396 }
397 e.write_json(out);
398 }
399 out.push_str("]}");
400 }
401 Expr::Map(entries) => {
402 out.push_str("{\"map\":[");
403 for (i, (k, v)) in entries.iter().enumerate() {
404 if i > 0 {
405 out.push(',');
406 }
407 out.push('[');
408 k.write_json(out);
409 out.push(',');
410 v.write_json(out);
411 out.push(']');
412 }
413 out.push_str("]}");
414 }
415 // `{"block":[<clause>…]}`. Binds are `{"bind":{"pat":…,"value":…}}`,
416 // others are the expression itself, in evaluation order.
417 Expr::Block(clauses) => {
418 out.push_str("{\"block\":[");
419 for (i, (pat, value)) in clauses.iter().enumerate() {
420 if i > 0 {
421 out.push(',');
422 }
423 if let Some(pat) = pat {
424 out.push_str("{\"bind\":{\"pat\":");
425 pat.write_json(out);
426 out.push_str(",\"value\":");
427 value.write_json(out);
428 out.push_str("}}");
429 } else {
430 value.write_json(out);
431 }
432 }
433 out.push_str("]}");
434 }
435 // `{"case":{"subject":…,"arms":[{"pat":…,"body":…}, …]}}`.
436 Expr::Case { subject, cases } => {
437 out.push_str("{\"case\":{\"subject\":");
438 subject.write_json(out);
439 out.push_str(",\"arms\":[");
440 for (i, (pat, body)) in cases.iter().enumerate() {
441 if i > 0 {
442 out.push(',');
443 }
444 out.push_str("{\"pat\":");
445 pat.write_json(out);
446 out.push_str(",\"body\":");
447 body.write_json(out);
448 out.push('}');
449 }
450 out.push_str("]}}");
451 }
452 }
453 }
454
455 fn write_sexp(&self, out: &mut String) {
456 match self {
457 Expr::Int(n) => {
458 out.push_str("(int ");
459 out.push_str(&n.to_string());
460 out.push(')');
461 }
462 // Name, Local, and Builtin all render by name.
463 Expr::Name(n) => {
464 out.push_str("(name ");
465 out.push_str(n);
466 out.push(')');
467 }
468 Expr::Local { name, .. } => {
469 out.push_str("(name ");
470 out.push_str(name.as_str());
471 out.push(')');
472 }
473 Expr::Builtin(op) => {
474 out.push_str("(name ");
475 out.push_str(op.name());
476 out.push(')');
477 }
478 // Home leaves render as `(self)`, `(new)`, or `(value)`.
479 // Depth is derived and not rendered.
480 Expr::Home { leaf, .. } => match leaf {
481 HomeLeaf::Module => out.push_str("(self)"),
482 HomeLeaf::New => out.push_str("(new)"),
483 HomeLeaf::Value => out.push_str("(value)"),
484 },
485 Expr::ModItem { name, .. } => {
486 out.push_str("(moditem ");
487 out.push_str(name.as_str());
488 out.push(')');
489 }
490 // `(recur (bind a …) (bind b …) <body>)` — bindings in sorted order, then body.
491 Expr::Recur(data) => {
492 out.push_str("(recur");
493 for (name, body) in data.items() {
494 out.push_str(" (bind ");
495 out.push_str(name.as_str());
496 out.push(' ');
497 body.write_sexp(out);
498 out.push(')');
499 }
500 out.push(' ');
501 recur_body(data).write_sexp(out);
502 out.push(')');
503 }
504 Expr::Symbol(s) => {
505 out.push_str("(sym .");
506 out.push_str(s);
507 out.push(')');
508 }
509 // Strings are rendered as bare quoted literals.
510 Expr::Str(s) => json_str(s, out),
511 // Render the call spine flat to preserve stored arity.
512 Expr::App(items) => write_app_sexp(items, out),
513 // Render collected head as nested single-parameter abstractions.
514 Expr::Abs(code) => write_abs_sexp(&code.head, &code.body, out),
515 Expr::Unit => out.push_str("(unit)"),
516 Expr::List(elems) => {
517 out.push_str("(list");
518 for e in elems {
519 out.push(' ');
520 e.write_sexp(out);
521 }
522 out.push(')');
523 }
524 Expr::Map(entries) => {
525 out.push_str("(map");
526 for (k, v) in entries {
527 out.push_str(" (entry ");
528 k.write_sexp(out);
529 out.push(' ');
530 v.write_sexp(out);
531 out.push(')');
532 }
533 out.push(')');
534 }
535 // `(block (bind <pat> <value>) … <value>)` — clauses in evaluation order.
536 Expr::Block(clauses) => {
537 out.push_str("(block");
538 for (pat, expr) in clauses.iter() {
539 out.push(' ');
540 if let Some(pat) = pat {
541 out.push_str("(bind ");
542 pat.write_sexp(out);
543 out.push(' ');
544 expr.write_sexp(out);
545 out.push(')');
546 } else {
547 expr.write_sexp(out);
548 }
549 }
550 out.push(')');
551 }
552 // `(case <subject> (arm <pat> <body>) …)`.
553 Expr::Case { subject, cases } => {
554 out.push_str("(case ");
555 subject.write_sexp(out);
556 for (pat, body) in cases.iter() {
557 out.push_str(" (arm ");
558 pat.write_sexp(out);
559 out.push(' ');
560 body.write_sexp(out);
561 out.push(')');
562 }
563 out.push(')');
564 }
565 }
566 }
567}
568
569/// Renders a flat call spine `[callee, a0, a1, …]` as `(app callee a0 a1)`, preserving
570/// stored arity. `#Self(…)` and `x.Self` render as `(new a0)` and `(value a0)`.
571fn write_app_sexp(items: &[Expr], out: &mut String) {
572 let (open, body) = match &items[0] {
573 Expr::Home {
574 leaf: HomeLeaf::New,
575 ..
576 } => ("(new", &items[1..]),
577 Expr::Home {
578 leaf: HomeLeaf::Value,
579 ..
580 } => ("(value", &items[1..]),
581 _ => ("(app", items),
582 };
583 out.push_str(open);
584 for it in body {
585 out.push(' ');
586 it.write_sexp(out);
587 }
588 out.push(')');
589}
590
591/// Renders a collected head `[p0, p1, …]` over `body` as nested `(abs p0 (abs p1 body))`.
592fn write_abs_sexp(head: &[Pattern], body: &Expr, out: &mut String) {
593 out.push_str("(abs ");
594 head[0].write_sexp(out);
595 out.push(' ');
596 if head.len() == 1 {
597 body.write_sexp(out);
598 } else {
599 write_abs_sexp(&head[1..], body, out);
600 }
601 out.push(')');
602}
603
604/// JSON twin of [`write_app_sexp`]: `{"app":[callee, a0, a1, …]}`, with the same
605/// special cases for `new` and `value` spines.
606fn write_app_json(items: &[Expr], out: &mut String) {
607 let (tag, body) = match &items[0] {
608 Expr::Home {
609 leaf: HomeLeaf::New,
610 ..
611 } => ("new", &items[1..]),
612 Expr::Home {
613 leaf: HomeLeaf::Value,
614 ..
615 } => ("value", &items[1..]),
616 _ => ("app", items),
617 };
618 out.push_str("{\"");
619 out.push_str(tag);
620 out.push_str("\":[");
621 for (i, it) in body.iter().enumerate() {
622 if i > 0 {
623 out.push(',');
624 }
625 it.write_json(out);
626 }
627 out.push_str("]}");
628}
629
630/// JSON twin of [`write_abs_sexp`]: nested `{"abs":{"param":…,"body":…}}`.
631fn write_abs_json(head: &[Pattern], body: &Expr, out: &mut String) {
632 out.push_str("{\"abs\":{\"param\":");
633 head[0].write_json(out);
634 out.push_str(",\"body\":");
635 if head.len() == 1 {
636 body.write_json(out);
637 } else {
638 write_abs_json(&head[1..], body, out);
639 }
640 out.push_str("}}");
641}
642
643impl Pattern {
644 /// Serialize a pattern to a compact s-expression. `Bind`/`Discard` render as names;
645 /// structured patterns are tagged (e.g., `(list a ...rest)`).
646 fn write_sexp(&self, out: &mut String) {
647 match self {
648 Pattern::Discard => out.push('_'),
649 Pattern::Unit => out.push_str("(unit)"),
650 Pattern::Bind(name) => out.push_str(name),
651 Pattern::At(name, inner) => {
652 out.push_str("(at ");
653 out.push_str(name);
654 out.push(' ');
655 inner.write_sexp(out);
656 out.push(')');
657 }
658 Pattern::Equal(expr) => {
659 out.push_str("(eq ");
660 expr.write_sexp(out);
661 out.push(')');
662 }
663 Pattern::Less(expr) => {
664 out.push_str("(lt ");
665 expr.write_sexp(out);
666 out.push(')');
667 }
668 Pattern::Greater(expr) => {
669 out.push_str("(gt ");
670 expr.write_sexp(out);
671 out.push(')');
672 }
673 Pattern::Type(proto, inner) => {
674 out.push_str("(as ");
675 proto.write_sexp(out);
676 out.push(' ');
677 inner.write_sexp(out);
678 out.push(')');
679 }
680 Pattern::Unwrap { inner, .. } => {
681 out.push_str("(unwrap ");
682 inner.write_sexp(out);
683 out.push(')');
684 }
685 Pattern::Or(left, right) => {
686 out.push_str("(or ");
687 left.write_sexp(out);
688 out.push(' ');
689 right.write_sexp(out);
690 out.push(')');
691 }
692 Pattern::List { elems, rest } => {
693 out.push_str("(list");
694 for e in elems {
695 out.push(' ');
696 e.write_sexp(out);
697 }
698 write_rest_sexp(rest, out);
699 out.push(')');
700 }
701 Pattern::Map { entries, rest } => {
702 out.push_str("(map");
703 for (key, vpat) in entries {
704 out.push_str(" (entry ");
705 key.write_sexp(out);
706 out.push(' ');
707 vpat.write_sexp(out);
708 out.push(')');
709 }
710 write_rest_sexp(rest, out);
711 out.push(')');
712 }
713 // `(when <inner> <cond>)` for plain guards; `(when <inner> (bind <gpat> <cond>))`
714 // for pattern guards.
715 Pattern::When {
716 inner,
717 when: (guard, cond),
718 } => {
719 out.push_str("(when ");
720 inner.write_sexp(out);
721 out.push(' ');
722 match guard {
723 None => cond.write_sexp(out),
724 Some(gpat) => {
725 out.push_str("(bind ");
726 gpat.write_sexp(out);
727 out.push(' ');
728 cond.write_sexp(out);
729 out.push(')');
730 }
731 }
732 out.push(')');
733 }
734 }
735 }
736
737 fn write_json(&self, out: &mut String) {
738 match self {
739 Pattern::Discard => out.push_str("{\"discard\":true}"),
740 Pattern::Unit => out.push_str("{\"unit\":true}"),
741 Pattern::Bind(name) => {
742 out.push_str("{\"bind\":");
743 json_str(name, out);
744 out.push('}');
745 }
746 Pattern::At(name, inner) => {
747 out.push_str("{\"at\":[");
748 json_str(name, out);
749 out.push(',');
750 inner.write_json(out);
751 out.push_str("]}");
752 }
753 Pattern::Equal(expr) => {
754 out.push_str("{\"eq\":");
755 expr.write_json(out);
756 out.push('}');
757 }
758 Pattern::Less(expr) => {
759 out.push_str("{\"lt\":");
760 expr.write_json(out);
761 out.push('}');
762 }
763 Pattern::Greater(expr) => {
764 out.push_str("{\"gt\":");
765 expr.write_json(out);
766 out.push('}');
767 }
768 Pattern::Type(proto, inner) => {
769 out.push_str("{\"as\":[");
770 proto.write_json(out);
771 out.push(',');
772 inner.write_json(out);
773 out.push_str("]}");
774 }
775 Pattern::Unwrap { inner, .. } => {
776 out.push_str("{\"unwrap\":");
777 inner.write_json(out);
778 out.push('}');
779 }
780 Pattern::Or(left, right) => {
781 out.push_str("{\"or\":[");
782 left.write_json(out);
783 out.push(',');
784 right.write_json(out);
785 out.push_str("]}");
786 }
787 Pattern::List { elems, rest } => {
788 out.push_str("{\"listpat\":{\"elems\":[");
789 for (i, e) in elems.iter().enumerate() {
790 if i > 0 {
791 out.push(',');
792 }
793 e.write_json(out);
794 }
795 out.push_str("],\"rest\":");
796 write_rest_json(rest, out);
797 out.push_str("}}");
798 }
799 Pattern::Map { entries, rest } => {
800 out.push_str("{\"mappat\":{\"entries\":[");
801 for (i, (key, vpat)) in entries.iter().enumerate() {
802 if i > 0 {
803 out.push(',');
804 }
805 out.push('[');
806 key.write_json(out);
807 out.push(',');
808 vpat.write_json(out);
809 out.push(']');
810 }
811 out.push_str("],\"rest\":");
812 write_rest_json(rest, out);
813 out.push_str("}}");
814 }
815 Pattern::When {
816 inner,
817 when: (guard, cond),
818 } => {
819 out.push_str("{\"when\":{\"inner\":");
820 inner.write_json(out);
821 out.push_str(",\"guard\":");
822 match guard {
823 None => out.push_str("null"),
824 Some(gpat) => gpat.write_json(out),
825 }
826 out.push_str(",\"cond\":");
827 cond.write_json(out);
828 out.push_str("}}");
829 }
830 }
831 }
832}
833
834/// Renders the trailing rest as `...` followed by the pattern (if not `Discard`).
835fn write_rest_sexp(rest: &Option<Box<Pattern>>, out: &mut String) {
836 if let Some(p) = rest {
837 out.push_str(" ...");
838 if !matches!(**p, Pattern::Discard) {
839 p.write_sexp(out);
840 }
841 }
842}
843
844/// Renders `null` for closed patterns, otherwise the rest pattern's JSON.
845fn write_rest_json(rest: &Option<Box<Pattern>>, out: &mut String) {
846 match rest {
847 None => out.push_str("null"),
848 Some(p) => p.write_json(out),
849 }
850}
851
852impl MapKey {
853 fn write_sexp(&self, out: &mut String) {
854 match self {
855 // Lookup key: a plain expression.
856 MapKey::Lookup(expr) => expr.write_sexp(out),
857 // Capture key: matches the peeled key against a pattern.
858 MapKey::Capture(kpat) => {
859 out.push_str("(capture ");
860 kpat.write_sexp(out);
861 out.push(')');
862 }
863 }
864 }
865
866 fn write_json(&self, out: &mut String) {
867 match self {
868 MapKey::Lookup(expr) => {
869 out.push_str("{\"lookup\":");
870 expr.write_json(out);
871 out.push('}');
872 }
873 MapKey::Capture(kpat) => {
874 out.push_str("{\"capture\":");
875 kpat.write_json(out);
876 out.push('}');
877 }
878 }
879 }
880}
881
882/// Returns true if `n` is exactly representable as an `f64` (within [-2^53, 2^53]).
883fn fits_f64_exactly(n: &BigInt) -> bool {
884 const LIMIT: i64 = 1 << 53; // 2^53 == 9_007_199_254_740_992
885 *n >= BigInt::from(-LIMIT) && *n <= BigInt::from(LIMIT)
886}
887
888/// Writes `s` as a quoted and escaped JSON string literal.
889pub(crate) fn json_str(s: &str, out: &mut String) {
890 out.push('"');
891 for ch in s.chars() {
892 match ch {
893 '"' => out.push_str("\\\""),
894 '\\' => out.push_str("\\\\"),
895 '\n' => out.push_str("\\n"),
896 '\r' => out.push_str("\\r"),
897 '\t' => out.push_str("\\t"),
898 c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
899 c => out.push(c),
900 }
901 }
902 out.push('"');
903}