Skip to main content

elly_core/
code.rs

1//! Code in static chunks: the flat form of one compile (see
2//! `docs/done/2026-10-11_elly-ctx-chunks.md`).
3//!
4//! A [`StaticChunk`] holds a top module and the modules nested in it as flat
5//! tables of small `Copy` entries, which refer to each other by `u32` ids. The
6//! parser emits a chunk as it reads the Muon tree (`parse.rs`), and a chunk
7//! renders as s-expressions for the parse goldens and as tagged JSON for the
8//! playground.
9//!
10//! The tables are filled in post-order: emitting a node pushes its descendants
11//! and returns its own node unpushed, and the parent pushes its children's
12//! nodes as one run, then itself. Every operand then points below the entry
13//! that holds it, which is what makes a chunk valid by construction. Module
14//! entries are the exception: each is reserved when its walk starts and filled
15//! when it ends, so a module's parent comes before it and the top module is
16//! the first.
17//!
18//! In debug builds a barrier entry stands before each run and records its kind
19//! and length, and the run accessors assert that it matches. Indices therefore
20//! differ between debug and release builds.
21
22use alloc::boxed::Box;
23use alloc::format;
24use alloc::string::{String, ToString};
25use alloc::vec::Vec;
26use core::ops::Range;
27
28use num_bigint::BigInt;
29
30use crate::builtins::{TyKind, MODULES};
31use crate::module::{ImportSpec, ModuleName};
32use crate::source::{ChunkLocs, ChunkSpan, DocSource, DocsBuilder, Loc, Pos};
33use crate::{Builtin, Ctx, Sym};
34
35/// An owned, cheap-clone text leaf using hipstr's `Local` (`Rc`) backend.
36/// Short strings (≤ 23 bytes on 64-bit) are inlined; longer strings use a shared
37/// `Rc<str>` slice. This keeps chunks and `Value` `'static` and independent of
38/// the Muon source.
39pub type Text = hipstr::LocalHipStr<'static>;
40
41/// One compile, flat and immutable: see the [module documentation](self).
42#[derive(Debug)]
43pub struct StaticChunk {
44    /// The loaded module's name, `Elly` for the builtins, or a name the realm
45    /// made: `__eval_N` for an expression, `__module_N` for an unnamed module.
46    name: Text,
47    nodes: Box<[Node]>,
48    pats: Box<[Pat]>,
49    lambdas: Box<[Lambda]>,
50    captures: Box<[Capture]>,
51    modules: Box<[Module]>,
52    members: Box<[Member]>,
53    frame: Box<[Sym]>,
54    imports: Box<[ImportSpec]>,
55    ints: Box<[BigInt]>,
56    strs: Box<[Text]>,
57    /// Where the code came from, out of line: only errors and tools read it.
58    pub(crate) locs: Box<ChunkLocs>,
59}
60
61/// An expression: a tag and up to three `u32` operands, 16 bytes. Children are
62/// ids into the same chunk; a node with many children names a run.
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub enum Node {
65    /// An integer that fits in `i64`.
66    Int(i64),
67    /// Any other integer, in the chunk's `ints` table.
68    BigInt(IntId),
69    Unit,
70    /// A dotted symbol, stored without the dot.
71    Symbol(Sym),
72    Str(StrId),
73    /// A resolved local; `name` is kept for rendering.
74    Local {
75        name: Sym,
76        index: u32,
77    },
78    Builtin(Builtin),
79    /// A member of an enclosing module: `index` into its member space, past
80    /// `depth` module terminals.
81    ModItem {
82        index: u32,
83        depth: u32,
84        name: Sym,
85    },
86    Home {
87        leaf: HomeLeaf,
88        depth: u32,
89    },
90    /// The run is the callee, then the `len - 1` arguments.
91    App {
92        len: u32,
93        args: NodeId,
94    },
95    List {
96        len: u32,
97        elems: NodeId,
98    },
99    /// The run is the `len` keys, then the `len` values.
100    Map {
101        len: u32,
102        entries: NodeId,
103    },
104    /// `len` patterns from `binds` ([`Pat::None`] for a clause that binds
105    /// nothing) and `len` clause expressions from `exprs`.
106    Block {
107        len: u32,
108        binds: PatId,
109        exprs: NodeId,
110    },
111    /// `len` patterns from `conds`, and from `exprs` the `len` arm bodies
112    /// followed by the subject.
113    Case {
114        len: u32,
115        conds: PatId,
116        exprs: NodeId,
117    },
118    Abs(LambdaId),
119    /// A `recur` group: a nested module with items and a body.
120    Recur(ModId),
121    /// Stands before a run in debug builds.
122    #[cfg(debug_assertions)]
123    Barrier {
124        run: Run,
125        len: u32,
126    },
127}
128
129/// A pattern, 16 bytes like [`Node`].
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131pub enum Pat {
132    /// The slot of a block clause that binds nothing.
133    None,
134    Discard,
135    Unit,
136    Bind(Sym),
137    At(Sym, PatId),
138    Equal(NodeId),
139    Less(NodeId),
140    Greater(NodeId),
141    /// `<pat> as <Kind>`, a builtin prototype.
142    TypeKind(TyKind, PatId),
143    /// `<pat> as <Proto>`, any other prototype, evaluated at match time.
144    TypeRef(NodeId, PatId),
145    Unwrap {
146        depth: u32,
147        inner: PatId,
148    },
149    Or(PatId, PatId),
150    /// `rest` is [`PatId::NONE`] for an exact-length list.
151    List {
152        len: u32,
153        elems: PatId,
154        rest: PatId,
155    },
156    /// The run is the `len` key patterns, then the `len` value patterns. A key
157    /// is a [`Lookup`](Pat::Lookup) or a pattern that captures the key.
158    Map {
159        len: u32,
160        entries: PatId,
161        rest: PatId,
162    },
163    /// A map pattern's looked-up key.
164    Lookup(NodeId),
165    /// `gpat` is [`PatId::NONE`] for a plain guard.
166    When {
167        inner: PatId,
168        gpat: PatId,
169        cond: NodeId,
170    },
171    /// Stands before a run in debug builds.
172    #[cfg(debug_assertions)]
173    Barrier {
174        run: Run,
175        len: u32,
176    },
177}
178
179/// The code of an abstraction. Its arity is `head.len`.
180#[derive(Debug, Clone, Copy, PartialEq, Eq)]
181pub struct Lambda {
182    pub head: IdRange<PatId>,
183    pub body: NodeId,
184    pub captures: Plan,
185}
186
187/// A module of the chunk: the top module, or a nested one.
188#[derive(Debug, Clone, PartialEq, Eq)]
189pub struct Module {
190    /// The canonical name the host gave it, if any.
191    pub name: Option<ModuleName>,
192    /// The enclosing module, [`ModId::NONE`] for the top module.
193    pub parent: ModId,
194    /// The member index space: the items, then the locals, then the iotas,
195    /// each sorted by `Sym`.
196    pub members: IdRange<MemberId>,
197    /// How many of `members` are items; the locals follow them.
198    pub items: u32,
199    /// How many of `members` are locals; the iotas follow them.
200    pub locals: u32,
201    /// The names of the frame slots, imports first.
202    pub frame: IdRange<FrameId>,
203    /// The specs of the leading frame slots.
204    pub imports: IdRange<ImportId>,
205    /// A `recur` group's body or a source's `__main`, or [`NodeId::NONE`].
206    pub body: NodeId,
207}
208
209/// A module member. An iota has no body: its `node` is [`NodeId::NONE`].
210#[derive(Debug, Clone, Copy, PartialEq, Eq)]
211pub struct Member {
212    pub name: Sym,
213    pub node: NodeId,
214}
215
216/// Home-module leaf types: the module itself (`Self`), construction
217/// (`#Self(…)`), or payload read (`x.Self`). These share the same resolution
218/// machinery (one walk to the terminal) and differ only in their eval result.
219#[derive(Debug, Clone, Copy, PartialEq, Eq)]
220pub enum HomeLeaf {
221    /// `Self` — the home module as a `Value::Module`.
222    Module,
223    /// `#Self(…)` / `#Self …` — the object constructor for the home module.
224    /// Always the callee of an [`App`](Node::App). Renders as `(new …)`.
225    New,
226    /// `x.Self` — the payload reader for the home module. Always the callee
227    /// of an [`App`](Node::App). In patterns, spelled as [`Pat::Unwrap`].
228    Value,
229}
230
231impl HomeLeaf {
232    /// The name reported by the resolver for leaves outside a module body.
233    pub fn name(self) -> &'static str {
234        "Self"
235    }
236}
237
238/// A captured free variable: `outer` is the de Bruijn index in the enclosing
239/// activation at evaluation; `slot` is its position in the closure's frame.
240/// The plan is sorted by `outer` so the evaluator fills the frame in one
241/// linear walk of the environment.
242#[derive(Debug, Clone, Copy, PartialEq, Eq)]
243pub struct Capture {
244    pub outer: u32,
245    pub slot: u32,
246}
247
248/// An index into one of a chunk's tables (or, for [`ChunkId`], the realm's).
249pub trait Id: Copy {
250    /// The position in the table.
251    fn index(self) -> usize;
252    /// The id of position `index`.
253    fn at(index: usize) -> Self;
254}
255
256macro_rules! ids {
257    ($($(#[$doc:meta])* $name:ident;)*) => {$(
258        $(#[$doc])*
259        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
260        pub struct $name(u32);
261
262        impl $name {
263            /// No entry: an absent operand.
264            pub const NONE: Self = Self(u32::MAX);
265
266            /// The id, or `None` for [`NONE`](Self::NONE).
267            pub fn get(self) -> Option<Self> {
268                (self != Self::NONE).then_some(self)
269            }
270
271            /// The id `i` entries further on: entry `i` of a run that starts here.
272            #[inline]
273            pub fn nth(self, i: u32) -> Self {
274                Self(self.0 + i)
275            }
276        }
277
278        impl Id for $name {
279            fn index(self) -> usize {
280                self.0 as usize
281            }
282
283            fn at(index: usize) -> Self {
284                assert!(index < u32::MAX as usize, "a chunk table outgrew u32");
285                Self(index as u32)
286            }
287        }
288    )*};
289}
290
291ids! {
292    /// A chunk in its realm's table.
293    ChunkId;
294    /// An expression node.
295    NodeId;
296    /// A pattern.
297    PatId;
298    /// A lambda's head, body and capture plan.
299    LambdaId;
300    /// A module: the top module, or a nested one such as a `recur` group.
301    ModId;
302    /// A module member: an item, a local or an iota.
303    MemberId;
304    /// One entry of a lambda's capture plan.
305    CaptureId;
306    /// A big integer literal.
307    IntId;
308    /// A string literal.
309    StrId;
310    /// A frame slot's name.
311    FrameId;
312    /// An import's spec.
313    ImportId;
314}
315
316impl ChunkId {
317    /// The builtin modules, built by every realm first (see
318    /// [`Ctx::builtin_module`](crate::Ctx)).
319    pub const ELLY: ChunkId = ChunkId(0);
320}
321
322impl ModId {
323    /// The builtin module in cache slot `slot` of the [`ELLY`](ChunkId::ELLY)
324    /// chunk, whose top module holds nothing.
325    pub(crate) fn builtin(slot: usize) -> ModId {
326        ModId::at(slot + 1)
327    }
328}
329
330/// `len` entries of one table, starting at `first`. An empty range starts at
331/// the table's length when it was made.
332#[derive(Debug, Clone, Copy, PartialEq, Eq)]
333pub struct IdRange<I> {
334    pub first: I,
335    pub len: u32,
336}
337
338impl<I: Id> IdRange<I> {
339    fn indices(self) -> Range<usize> {
340        let first = self.first.index();
341        first..first + self.len as usize
342    }
343}
344
345/// What a run of nodes or patterns belongs to, recorded by its barrier in
346/// debug builds.
347#[derive(Debug, Clone, Copy, PartialEq, Eq)]
348pub enum Run {
349    /// An application: the callee, then the arguments.
350    App,
351    /// A list literal's elements.
352    List,
353    /// A map literal's keys, then its values.
354    Map,
355    /// A block's binding patterns, or its clause expressions.
356    Block,
357    /// A case's arm patterns, or its arm bodies followed by the subject.
358    Case,
359    /// A lambda's parameter patterns.
360    Head,
361    /// A list pattern's elements.
362    PatList,
363    /// A map pattern's key patterns, then its value patterns.
364    PatMap,
365}
366
367/// How a closure captures its environment.
368#[derive(Debug, Clone, Copy, PartialEq, Eq)]
369pub enum Plan {
370    /// Share the defining environment by reference. Used when the closure
371    /// is applied where it is made (a direct application, a builtin's thunk
372    /// argument), avoiding frame allocation.
373    Chain,
374    /// Copy the free variables into a fresh frame, the default for escaping
375    /// closures. An empty plan is a closed lambda.
376    Frame(IdRange<CaptureId>),
377}
378
379impl StaticChunk {
380    /// The chunk's name.
381    pub fn name(&self) -> &Text {
382        &self.name
383    }
384
385    /// The top module, the chunk's first.
386    pub fn top(&self) -> ModId {
387        ModId(0)
388    }
389
390    pub fn node(&self, id: NodeId) -> Node {
391        self.nodes[id.index()]
392    }
393
394    pub fn pat(&self, id: PatId) -> Pat {
395        self.pats[id.index()]
396    }
397
398    pub fn lambda(&self, id: LambdaId) -> &Lambda {
399        &self.lambdas[id.index()]
400    }
401
402    pub fn module(&self, id: ModId) -> &Module {
403        &self.modules[id.index()]
404    }
405
406    /// A module's member index space (see [`Module::members`]).
407    pub fn members(&self, module: &Module) -> &[Member] {
408        &self.members[module.members.indices()]
409    }
410
411    /// A module's frame slot names.
412    pub fn frame_names(&self, module: &Module) -> &[Sym] {
413        &self.frame[module.frame.indices()]
414    }
415
416    /// A module's import specs.
417    pub fn imports(&self, module: &Module) -> &[ImportSpec] {
418        &self.imports[module.imports.indices()]
419    }
420
421    /// A lambda's frame plan.
422    pub fn captures(&self, plan: IdRange<CaptureId>) -> &[Capture] {
423        &self.captures[plan.indices()]
424    }
425
426    pub fn int(&self, id: IntId) -> &BigInt {
427        &self.ints[id.index()]
428    }
429
430    pub fn str(&self, id: StrId) -> &Text {
431        &self.strs[id.index()]
432    }
433
434    /// The `len` nodes of a run of kind `run`, starting at `first`.
435    pub fn nodes(&self, run: Run, first: NodeId, len: u32) -> &[Node] {
436        let first = first.index();
437        #[cfg(debug_assertions)]
438        assert_eq!(
439            self.nodes[first - 1],
440            Node::Barrier { run, len },
441            "a run of nodes does not match its barrier"
442        );
443        let _ = run;
444        &self.nodes[first..first + len as usize]
445    }
446
447    /// The `len` patterns of a run of kind `run`, starting at `first`.
448    pub fn pats(&self, run: Run, first: PatId, len: u32) -> &[Pat] {
449        let first = first.index();
450        #[cfg(debug_assertions)]
451        assert_eq!(
452            self.pats[first - 1],
453            Pat::Barrier { run, len },
454            "a run of patterns does not match its barrier"
455        );
456        let _ = run;
457        &self.pats[first..first + len as usize]
458    }
459
460    /// The ids of the chunk's modules, the top module first.
461    pub fn module_ids(&self) -> impl Iterator<Item = ModId> + Clone {
462        (0..self.modules.len()).map(ModId::at)
463    }
464
465    /// The item or local at `index` of module `m`'s member space, or `None`
466    /// past them, where the iotas are.
467    #[inline]
468    pub fn member(&self, m: ModId, index: u32) -> Option<Member> {
469        let module = self.module(m);
470        if index >= module.items + module.locals {
471            return None;
472        }
473        Some(self.members[module.members.first.index() + index as usize])
474    }
475
476    /// How many items and locals module `m` has: the size of its id block,
477    /// and where its iotas start.
478    pub fn member_len(&self, m: ModId) -> u32 {
479        let module = self.module(m);
480        module.items + module.locals
481    }
482
483    /// The name of iota `index` of module `m`, counted from its first iota.
484    pub fn iota(&self, m: ModId, index: u32) -> Option<Sym> {
485        let module = self.module(m);
486        let iotas = &self.members(module)[(module.items + module.locals) as usize..];
487        iotas.get(index as usize).map(|member| member.name)
488    }
489
490    /// The index of module `m`'s item `name`, searched by `Sym`. Locals are
491    /// not searched: they are private.
492    pub fn index_of(&self, m: ModId, name: Sym) -> Option<u32> {
493        let module = self.module(m);
494        self.members(module)[..module.items as usize]
495            .binary_search_by(|member| member.name.cmp(&name))
496            .ok()
497            .map(|i| i as u32)
498    }
499
500    /// Module `m`'s item names, sorted by `Sym`: its public listing.
501    pub fn item_names(&self, m: ModId) -> impl Iterator<Item = Sym> + '_ {
502        let module = self.module(m);
503        self.members(module)[..module.items as usize]
504            .iter()
505            .map(|member| member.name)
506    }
507
508    /// Module `m`'s `local` names, sorted by `Sym`. Locals are unreachable
509    /// as `M.name` from outside; this is for host introspection.
510    pub fn local_names(&self, m: ModId) -> impl Iterator<Item = Sym> + '_ {
511        let module = self.module(m);
512        let start = module.items as usize;
513        self.members(module)[start..start + module.locals as usize]
514            .iter()
515            .map(|member| member.name)
516    }
517
518    /// Module `m`'s iota names, sorted by `Sym`.
519    pub fn iota_names(&self, m: ModId) -> impl Iterator<Item = Sym> + '_ {
520        let module = self.module(m);
521        self.members(module)[(module.items + module.locals) as usize..]
522            .iter()
523            .map(|member| member.name)
524    }
525}
526
527// ---------------------------------------------------------------------------
528// The builder
529// ---------------------------------------------------------------------------
530
531/// Builds one chunk. Nothing sees the chunk before [`finish`](Self::finish),
532/// so a partly filled module entry is the builder's affair.
533pub(crate) struct Builder {
534    nodes: Vec<Node>,
535    pats: Vec<Pat>,
536    lambdas: Vec<Lambda>,
537    captures: Vec<Capture>,
538    modules: Vec<Module>,
539    members: Vec<Member>,
540    frame: Vec<Sym>,
541    imports: Vec<ImportSpec>,
542    ints: Vec<BigInt>,
543    strs: Vec<Text>,
544    node_spans: Vec<ChunkSpan>,
545    pat_spans: Vec<ChunkSpan>,
546    lambda_spans: Vec<ChunkSpan>,
547    member_spans: Vec<ChunkSpan>,
548    pub(crate) docs: DocsBuilder,
549    /// Children's nodes and their spans waiting to be pushed as one run; each
550    /// emitter pushes above a mark and drains back down to it.
551    pub(crate) scratch: Vec<(Node, ChunkSpan)>,
552    /// The same for patterns.
553    pub(crate) pat_scratch: Vec<(Pat, ChunkSpan)>,
554    /// The same for the members of the modules being emitted.
555    pub(crate) member_scratch: Vec<(Member, ChunkSpan)>,
556    /// The module whose bodies are being emitted, the parent of a nested one.
557    current: ModId,
558}
559
560pub(crate) fn len32(len: usize) -> u32 {
561    u32::try_from(len).expect("a run outgrew u32")
562}
563
564impl Builder {
565    pub(crate) fn new() -> Self {
566        Builder {
567            nodes: Vec::new(),
568            pats: Vec::new(),
569            lambdas: Vec::new(),
570            captures: Vec::new(),
571            modules: Vec::new(),
572            members: Vec::new(),
573            frame: Vec::new(),
574            imports: Vec::new(),
575            ints: Vec::new(),
576            strs: Vec::new(),
577            node_spans: Vec::new(),
578            pat_spans: Vec::new(),
579            lambda_spans: Vec::new(),
580            member_spans: Vec::new(),
581            docs: DocsBuilder::default(),
582            scratch: Vec::new(),
583            pat_scratch: Vec::new(),
584            member_scratch: Vec::new(),
585            current: ModId::NONE,
586        }
587    }
588
589    /// The chunk, named `name`, its anonymous document numbered `made`.
590    pub(crate) fn finish(self, name: Text, made: Option<u32>) -> StaticChunk {
591        debug_assert!(self.scratch.is_empty() && self.pat_scratch.is_empty());
592        debug_assert!(self.member_scratch.is_empty());
593        debug_assert!(!self.modules.is_empty(), "a chunk has a top module");
594        let (docs, line_starts) = self.docs.finish(made);
595        StaticChunk {
596            name,
597            nodes: self.nodes.into_boxed_slice(),
598            pats: self.pats.into_boxed_slice(),
599            lambdas: self.lambdas.into_boxed_slice(),
600            captures: self.captures.into_boxed_slice(),
601            modules: self.modules.into_boxed_slice(),
602            members: self.members.into_boxed_slice(),
603            frame: self.frame.into_boxed_slice(),
604            imports: self.imports.into_boxed_slice(),
605            ints: self.ints.into_boxed_slice(),
606            strs: self.strs.into_boxed_slice(),
607            locs: Box::new(ChunkLocs {
608                node_spans: self.node_spans.into_boxed_slice(),
609                pat_spans: self.pat_spans.into_boxed_slice(),
610                lambda_spans: self.lambda_spans.into_boxed_slice(),
611                member_spans: self.member_spans.into_boxed_slice(),
612                docs,
613                line_starts,
614                base: Loc::NONE,
615            }),
616        }
617    }
618
619    /// Records the document `text` from `source`, `None` for an anonymous
620    /// one, and returns its first position in the chunk.
621    pub(crate) fn add_doc(&mut self, source: Option<DocSource>, text: &str) -> Pos {
622        self.docs.add(source, text)
623    }
624
625    pub(crate) fn push(&mut self, node: Node, span: ChunkSpan) -> NodeId {
626        let id = NodeId::at(self.nodes.len());
627        self.nodes.push(node);
628        self.node_spans.push(span);
629        id
630    }
631
632    pub(crate) fn push_pat(&mut self, pat: Pat, span: ChunkSpan) -> PatId {
633        let id = PatId::at(self.pats.len());
634        self.pats.push(pat);
635        self.pat_spans.push(span);
636        id
637    }
638
639    /// The node `id`, already pushed, and its span.
640    pub(crate) fn node(&self, id: NodeId) -> (Node, ChunkSpan) {
641        (self.nodes[id.index()], self.node_spans[id.index()])
642    }
643
644    /// Pushes `scratch[mark..]` as one run, behind a barrier in debug builds.
645    pub(crate) fn push_run(&mut self, run: Run, mark: usize) -> NodeId {
646        #[cfg(debug_assertions)]
647        {
648            let len = len32(self.scratch.len() - mark);
649            self.push(Node::Barrier { run, len }, ChunkSpan::NONE);
650        }
651        let _ = run;
652        let first = NodeId::at(self.nodes.len());
653        for (node, span) in self.scratch.drain(mark..) {
654            self.nodes.push(node);
655            self.node_spans.push(span);
656        }
657        first
658    }
659
660    /// Pushes `pat_scratch[mark..]` as one run, behind a barrier in debug builds.
661    pub(crate) fn push_pat_run(&mut self, run: Run, mark: usize) -> PatId {
662        #[cfg(debug_assertions)]
663        {
664            let len = len32(self.pat_scratch.len() - mark);
665            self.push_pat(Pat::Barrier { run, len }, ChunkSpan::NONE);
666        }
667        let _ = run;
668        let first = PatId::at(self.pats.len());
669        for (pat, span) in self.pat_scratch.drain(mark..) {
670            self.pats.push(pat);
671            self.pat_spans.push(span);
672        }
673        first
674    }
675
676    /// An integer literal: inline if it fits `i64`, else in the `ints` table.
677    pub(crate) fn int(&mut self, n: BigInt) -> Node {
678        match i64::try_from(&n) {
679            Ok(n) => Node::Int(n),
680            Err(_) => {
681                let id = IntId::at(self.ints.len());
682                self.ints.push(n);
683                Node::BigInt(id)
684            }
685        }
686    }
687
688    /// A string literal, in the `strs` table.
689    pub(crate) fn str(&mut self, s: Text) -> Node {
690        let id = StrId::at(self.strs.len());
691        self.strs.push(s);
692        Node::Str(id)
693    }
694
695    /// A lambda whose head is `pat_scratch[mark..]` and whose body is `body`,
696    /// capturing by `plan`: `None` for [`Plan::Chain`], else the frame plan.
697    /// The lambda's span is `span`, its whole `&`.
698    pub(crate) fn lambda(
699        &mut self,
700        mark: usize,
701        body: (Node, ChunkSpan),
702        plan: Option<&[Capture]>,
703        span: ChunkSpan,
704    ) -> Node {
705        let len = self.pat_scratch.len() - mark;
706        // A closure counts its applied arguments in a `u8`.
707        assert!(
708            len <= u8::MAX as usize,
709            "a lambda has at most 255 parameters"
710        );
711        let head = IdRange {
712            first: self.push_pat_run(Run::Head, mark),
713            len: len32(len),
714        };
715        let body = self.push(body.0, body.1);
716        let captures = match plan {
717            None => Plan::Chain,
718            Some(plan) => {
719                let first = CaptureId::at(self.captures.len());
720                self.captures.extend_from_slice(plan);
721                Plan::Frame(IdRange {
722                    first,
723                    len: len32(plan.len()),
724                })
725            }
726        };
727        let id = LambdaId::at(self.lambdas.len());
728        self.lambdas.push(Lambda {
729            head,
730            body,
731            captures,
732        });
733        self.lambda_spans.push(span);
734        Node::Abs(id)
735    }
736
737    /// Reserves the next module entry, with the current module as its parent,
738    /// and makes it current. Its fields are filled when its walk ends.
739    pub(crate) fn enter_module(&mut self) -> ModId {
740        let id = ModId::at(self.modules.len());
741        self.modules.push(Module {
742            name: None,
743            parent: self.current,
744            members: IdRange {
745                first: MemberId::NONE,
746                len: 0,
747            },
748            items: 0,
749            locals: 0,
750            frame: IdRange {
751                first: FrameId::NONE,
752                len: 0,
753            },
754            imports: IdRange {
755                first: ImportId::NONE,
756                len: 0,
757            },
758            body: NodeId::NONE,
759        });
760        self.current = id;
761        id
762    }
763
764    /// Fills the entry `id`, reserved by [`enter_module`](Self::enter_module),
765    /// and makes its parent current again. Its members are
766    /// `member_scratch[mark..]`: `items` items, then `locals` locals, then the
767    /// iotas.
768    #[allow(clippy::too_many_arguments)]
769    pub(crate) fn leave_module(
770        &mut self,
771        id: ModId,
772        name: Option<ModuleName>,
773        mark: usize,
774        items: u32,
775        locals: u32,
776        frame: &[Sym],
777        imports: Vec<ImportSpec>,
778        body: NodeId,
779    ) {
780        let members = IdRange {
781            first: MemberId::at(self.members.len()),
782            len: len32(self.member_scratch.len() - mark),
783        };
784        for (member, span) in self.member_scratch.drain(mark..) {
785            self.members.push(member);
786            self.member_spans.push(span);
787        }
788        let frame_range = IdRange {
789            first: FrameId::at(self.frame.len()),
790            len: len32(frame.len()),
791        };
792        self.frame.extend_from_slice(frame);
793        let imports_range = IdRange {
794            first: ImportId::at(self.imports.len()),
795            len: len32(imports.len()),
796        };
797        self.imports.extend(imports);
798        let parent = self.modules[id.index()].parent;
799        self.modules[id.index()] = Module {
800            name,
801            parent,
802            members,
803            items,
804            locals,
805            frame: frame_range,
806            imports: imports_range,
807            body,
808        };
809        self.current = parent;
810    }
811
812    /// The builtin modules: an empty top module, and under it one module per
813    /// cache slot, whose items are builtin leaves.
814    pub(crate) fn builtin_modules(&mut self) {
815        let top = self.enter_module();
816        for (slot, (op, _, items, iotas)) in MODULES.iter().enumerate() {
817            let id = self.enter_module();
818            debug_assert_eq!(id, ModId::builtin(slot));
819            let mark = self.member_scratch.len();
820            for (name, op) in items.iter() {
821                let node = self.push(Node::Builtin(*op), ChunkSpan::NONE);
822                let member = Member { name: *name, node };
823                self.member_scratch.push((member, ChunkSpan::NONE));
824            }
825            for name in iotas.iter() {
826                let member = Member {
827                    name: *name,
828                    node: NodeId::NONE,
829                };
830                self.member_scratch.push((member, ChunkSpan::NONE));
831            }
832            // A builtin module is named after itself, so `__Mod.name __Int`
833            // answers `"__Int"`.
834            let name = Some(Text::from_static(op.name()));
835            let items = len32(items.len());
836            self.leave_module(id, name, mark, items, 0, &[], Vec::new(), NodeId::NONE);
837        }
838        let mark = self.member_scratch.len();
839        self.leave_module(top, None, mark, 0, 0, &[], Vec::new(), NodeId::NONE);
840    }
841}
842
843// ---------------------------------------------------------------------------
844// Rendering
845// ---------------------------------------------------------------------------
846
847impl StaticChunk {
848    /// The s-expression of node `id`, e.g. `(app (name f) (name x))`.
849    pub fn to_sexp(&self, id: NodeId, ctx: &Ctx) -> String {
850        let mut out = String::new();
851        self.write_sexp(self.node(id), &mut out, ctx);
852        out
853    }
854
855    fn write_sexp(&self, node: Node, out: &mut String, ctx: &Ctx) {
856        match node {
857            Node::Int(n) => {
858                out.push_str("(int ");
859                out.push_str(&n.to_string());
860                out.push(')');
861            }
862            Node::BigInt(id) => {
863                out.push_str("(int ");
864                out.push_str(&self.int(id).to_string());
865                out.push(')');
866            }
867            Node::Local { name, .. } => {
868                out.push_str("(name ");
869                push_sym(name, out, ctx);
870                out.push(')');
871            }
872            Node::Builtin(op) => {
873                out.push_str("(name ");
874                out.push_str(op.name());
875                out.push(')');
876            }
877            Node::Home { leaf, .. } => match leaf {
878                HomeLeaf::Module => out.push_str("(self)"),
879                HomeLeaf::New => out.push_str("(new)"),
880                HomeLeaf::Value => out.push_str("(value)"),
881            },
882            Node::ModItem { name, .. } => {
883                out.push_str("(moditem ");
884                push_sym(name, out, ctx);
885                out.push(')');
886            }
887            // `(recur (bind a …) (bind b …) <body>)`, the bindings in text order.
888            Node::Recur(id) => {
889                let module = self.module(id);
890                out.push_str("(recur");
891                for member in self.items_by_text(module, ctx) {
892                    out.push_str(" (bind ");
893                    push_sym(member.name, out, ctx);
894                    out.push(' ');
895                    self.write_sexp(self.node(member.node), out, ctx);
896                    out.push(')');
897                }
898                out.push(' ');
899                self.write_sexp(self.node(module.body), out, ctx);
900                out.push(')');
901            }
902            Node::Symbol(s) => {
903                out.push_str("(sym .");
904                push_sym(s, out, ctx);
905                out.push(')');
906            }
907            Node::Str(id) => json_str(self.str(id), out),
908            Node::App { len, args } => {
909                let items = self.nodes(Run::App, args, len);
910                let (open, body) = match items[0] {
911                    Node::Home {
912                        leaf: HomeLeaf::New,
913                        ..
914                    } => ("(new", &items[1..]),
915                    Node::Home {
916                        leaf: HomeLeaf::Value,
917                        ..
918                    } => ("(value", &items[1..]),
919                    _ => ("(app", items),
920                };
921                out.push_str(open);
922                for &item in body {
923                    out.push(' ');
924                    self.write_sexp(item, out, ctx);
925                }
926                out.push(')');
927            }
928            Node::Abs(id) => {
929                let code = self.lambda(id);
930                let head = self.pats(Run::Head, code.head.first, code.head.len);
931                self.write_abs_sexp(head, code.body, out, ctx);
932            }
933            Node::Unit => out.push_str("(unit)"),
934            Node::List { len, elems } => {
935                out.push_str("(list");
936                for &elem in self.nodes(Run::List, elems, len) {
937                    out.push(' ');
938                    self.write_sexp(elem, out, ctx);
939                }
940                out.push(')');
941            }
942            Node::Map { len, entries } => {
943                let all = self.nodes(Run::Map, entries, 2 * len);
944                let (keys, values) = all.split_at(len as usize);
945                out.push_str("(map");
946                for (&key, &value) in keys.iter().zip(values) {
947                    out.push_str(" (entry ");
948                    self.write_sexp(key, out, ctx);
949                    out.push(' ');
950                    self.write_sexp(value, out, ctx);
951                    out.push(')');
952                }
953                out.push(')');
954            }
955            Node::Block { len, binds, exprs } => {
956                let binds = self.pats(Run::Block, binds, len);
957                let exprs = self.nodes(Run::Block, exprs, len);
958                out.push_str("(block");
959                for (&pat, &expr) in binds.iter().zip(exprs) {
960                    out.push(' ');
961                    if pat == Pat::None {
962                        self.write_sexp(expr, out, ctx);
963                    } else {
964                        out.push_str("(bind ");
965                        self.write_pat_sexp(pat, out, ctx);
966                        out.push(' ');
967                        self.write_sexp(expr, out, ctx);
968                        out.push(')');
969                    }
970                }
971                out.push(')');
972            }
973            Node::Case { len, conds, exprs } => {
974                let conds = self.pats(Run::Case, conds, len);
975                let exprs = self.nodes(Run::Case, exprs, len + 1);
976                let (bodies, subject) = exprs.split_at(len as usize);
977                out.push_str("(case ");
978                self.write_sexp(subject[0], out, ctx);
979                for (&pat, &body) in conds.iter().zip(bodies) {
980                    out.push_str(" (arm ");
981                    self.write_pat_sexp(pat, out, ctx);
982                    out.push(' ');
983                    self.write_sexp(body, out, ctx);
984                    out.push(')');
985                }
986                out.push(')');
987            }
988            #[cfg(debug_assertions)]
989            Node::Barrier { .. } => unreachable!("a barrier is not an operand"),
990        }
991    }
992
993    /// A head `[p0, p1, …]` over `body` as nested `(abs p0 (abs p1 body))`.
994    fn write_abs_sexp(&self, head: &[Pat], body: NodeId, out: &mut String, ctx: &Ctx) {
995        out.push_str("(abs ");
996        self.write_pat_sexp(head[0], out, ctx);
997        out.push(' ');
998        if head.len() == 1 {
999            self.write_sexp(self.node(body), out, ctx);
1000        } else {
1001            self.write_abs_sexp(&head[1..], body, out, ctx);
1002        }
1003        out.push(')');
1004    }
1005
1006    fn write_pat_sexp(&self, pat: Pat, out: &mut String, ctx: &Ctx) {
1007        match pat {
1008            Pat::None => unreachable!("only a block clause's slot binds nothing"),
1009            Pat::Discard => out.push('_'),
1010            Pat::Unit => out.push_str("(unit)"),
1011            Pat::Bind(name) => push_sym(name, out, ctx),
1012            Pat::At(name, inner) => {
1013                out.push_str("(at ");
1014                push_sym(name, out, ctx);
1015                out.push(' ');
1016                self.write_pat_sexp(self.pat(inner), out, ctx);
1017                out.push(')');
1018            }
1019            Pat::Equal(expr) => self.write_tagged_node("(eq ", expr, out, ctx),
1020            Pat::Less(expr) => self.write_tagged_node("(lt ", expr, out, ctx),
1021            Pat::Greater(expr) => self.write_tagged_node("(gt ", expr, out, ctx),
1022            Pat::TypeKind(kind, inner) => {
1023                out.push_str("(as ");
1024                out.push_str(kind.name());
1025                out.push(' ');
1026                self.write_pat_sexp(self.pat(inner), out, ctx);
1027                out.push(')');
1028            }
1029            Pat::TypeRef(proto, inner) => {
1030                out.push_str("(as ");
1031                self.write_sexp(self.node(proto), out, ctx);
1032                out.push(' ');
1033                self.write_pat_sexp(self.pat(inner), out, ctx);
1034                out.push(')');
1035            }
1036            Pat::Unwrap { inner, .. } => {
1037                out.push_str("(unwrap ");
1038                self.write_pat_sexp(self.pat(inner), out, ctx);
1039                out.push(')');
1040            }
1041            Pat::Or(left, right) => {
1042                out.push_str("(or ");
1043                self.write_pat_sexp(self.pat(left), out, ctx);
1044                out.push(' ');
1045                self.write_pat_sexp(self.pat(right), out, ctx);
1046                out.push(')');
1047            }
1048            Pat::List { len, elems, rest } => {
1049                out.push_str("(list");
1050                for &elem in self.pats(Run::PatList, elems, len) {
1051                    out.push(' ');
1052                    self.write_pat_sexp(elem, out, ctx);
1053                }
1054                self.write_rest_sexp(rest, out, ctx);
1055                out.push(')');
1056            }
1057            Pat::Map { len, entries, rest } => {
1058                let all = self.pats(Run::PatMap, entries, 2 * len);
1059                let (keys, values) = all.split_at(len as usize);
1060                out.push_str("(map");
1061                for (&key, &value) in keys.iter().zip(values) {
1062                    out.push_str(" (entry ");
1063                    match key {
1064                        Pat::Lookup(expr) => self.write_sexp(self.node(expr), out, ctx),
1065                        key => {
1066                            out.push_str("(capture ");
1067                            self.write_pat_sexp(key, out, ctx);
1068                            out.push(')');
1069                        }
1070                    }
1071                    out.push(' ');
1072                    self.write_pat_sexp(value, out, ctx);
1073                    out.push(')');
1074                }
1075                self.write_rest_sexp(rest, out, ctx);
1076                out.push(')');
1077            }
1078            Pat::Lookup(_) => unreachable!("a lookup is only a map pattern's key"),
1079            // `(when <inner> <cond>)`, or `(when <inner> (bind <gpat> <cond>))`.
1080            Pat::When { inner, gpat, cond } => {
1081                out.push_str("(when ");
1082                self.write_pat_sexp(self.pat(inner), out, ctx);
1083                out.push(' ');
1084                match gpat.get() {
1085                    None => self.write_sexp(self.node(cond), out, ctx),
1086                    Some(gpat) => {
1087                        out.push_str("(bind ");
1088                        self.write_pat_sexp(self.pat(gpat), out, ctx);
1089                        out.push(' ');
1090                        self.write_sexp(self.node(cond), out, ctx);
1091                        out.push(')');
1092                    }
1093                }
1094                out.push(')');
1095            }
1096            #[cfg(debug_assertions)]
1097            Pat::Barrier { .. } => unreachable!("a barrier is not an operand"),
1098        }
1099    }
1100
1101    fn write_tagged_node(&self, open: &str, expr: NodeId, out: &mut String, ctx: &Ctx) {
1102        out.push_str(open);
1103        self.write_sexp(self.node(expr), out, ctx);
1104        out.push(')');
1105    }
1106
1107    /// ` ...` and the rest pattern unless it is `_`; nothing for an exact one.
1108    fn write_rest_sexp(&self, rest: PatId, out: &mut String, ctx: &Ctx) {
1109        if let Some(rest) = rest.get() {
1110            out.push_str(" ...");
1111            let rest = self.pat(rest);
1112            if rest != Pat::Discard {
1113                self.write_pat_sexp(rest, out, ctx);
1114            }
1115        }
1116    }
1117}
1118
1119impl StaticChunk {
1120    /// The tagged JSON of node `id`, e.g. `{"app":[{"name":"f"},{"name":"x"}]}`.
1121    pub fn to_json(&self, id: NodeId, ctx: &Ctx) -> String {
1122        let mut out = String::new();
1123        self.write_json(self.node(id), &mut out, ctx);
1124        out
1125    }
1126
1127    fn write_json(&self, node: Node, out: &mut String, ctx: &Ctx) {
1128        match node {
1129            // A bare number if it fits in f64 [-2^53, 2^53] exactly; otherwise
1130            // a tagged decimal string to avoid precision loss.
1131            Node::Int(n) if (-(1 << 53)..=(1 << 53)).contains(&n) => {
1132                out.push_str(&n.to_string());
1133            }
1134            Node::Int(n) => write_tagged_int(&n.to_string(), out),
1135            Node::BigInt(id) => write_tagged_int(&self.int(id).to_string(), out),
1136            Node::Local { name, .. } => {
1137                out.push_str("{\"name\":");
1138                json_sym(name, out, ctx);
1139                out.push('}');
1140            }
1141            Node::Builtin(op) => {
1142                out.push_str("{\"name\":");
1143                json_str(op.name(), out);
1144                out.push('}');
1145            }
1146            // Depth is derived and not rendered.
1147            Node::Home { leaf, .. } => match leaf {
1148                HomeLeaf::Module => out.push_str("{\"self\":true}"),
1149                HomeLeaf::New => out.push_str("{\"new\":true}"),
1150                HomeLeaf::Value => out.push_str("{\"value\":true}"),
1151            },
1152            Node::ModItem { name, .. } => {
1153                out.push_str("{\"moditem\":");
1154                json_sym(name, out, ctx);
1155                out.push('}');
1156            }
1157            Node::Recur(id) => {
1158                let module = self.module(id);
1159                out.push_str("{\"recur\":[");
1160                for (i, member) in self.items_by_text(module, ctx).into_iter().enumerate() {
1161                    if i > 0 {
1162                        out.push(',');
1163                    }
1164                    out.push('[');
1165                    json_sym(member.name, out, ctx);
1166                    out.push(',');
1167                    self.write_json(self.node(member.node), out, ctx);
1168                    out.push(']');
1169                }
1170                out.push_str("],\"body\":");
1171                self.write_json(self.node(module.body), out, ctx);
1172                out.push('}');
1173            }
1174            Node::Symbol(s) => {
1175                let mut dotted = String::from(".");
1176                ctx.with_str(s, |s| dotted.push_str(s));
1177                out.push_str("{\"sym\":");
1178                json_str(&dotted, out);
1179                out.push('}');
1180            }
1181            Node::Str(id) => {
1182                out.push_str("{\"str\":");
1183                json_str(self.str(id), out);
1184                out.push('}');
1185            }
1186            // The call spine renders flat, preserving its stored arity.
1187            Node::App { len, args } => {
1188                let items = self.nodes(Run::App, args, len);
1189                let (tag, body) = match items[0] {
1190                    Node::Home {
1191                        leaf: HomeLeaf::New,
1192                        ..
1193                    } => ("new", &items[1..]),
1194                    Node::Home {
1195                        leaf: HomeLeaf::Value,
1196                        ..
1197                    } => ("value", &items[1..]),
1198                    _ => ("app", items),
1199                };
1200                out.push_str("{\"");
1201                out.push_str(tag);
1202                out.push_str("\":");
1203                self.write_json_list(body, out, ctx);
1204                out.push('}');
1205            }
1206            Node::Abs(id) => {
1207                let code = self.lambda(id);
1208                let head = self.pats(Run::Head, code.head.first, code.head.len);
1209                self.write_abs_json(head, code.body, out, ctx);
1210            }
1211            Node::Unit => out.push_str("{\"unit\":true}"),
1212            Node::List { len, elems } => {
1213                out.push_str("{\"list\":");
1214                self.write_json_list(self.nodes(Run::List, elems, len), out, ctx);
1215                out.push('}');
1216            }
1217            Node::Map { len, entries } => {
1218                let all = self.nodes(Run::Map, entries, 2 * len);
1219                let (keys, values) = all.split_at(len as usize);
1220                out.push_str("{\"map\":[");
1221                for (i, (&key, &value)) in keys.iter().zip(values).enumerate() {
1222                    if i > 0 {
1223                        out.push(',');
1224                    }
1225                    out.push('[');
1226                    self.write_json(key, out, ctx);
1227                    out.push(',');
1228                    self.write_json(value, out, ctx);
1229                    out.push(']');
1230                }
1231                out.push_str("]}");
1232            }
1233            // `{"block":[<clause>…]}`: a binding is
1234            // `{"bind":{"pat":…,"value":…}}`, any other clause its expression.
1235            Node::Block { len, binds, exprs } => {
1236                let binds = self.pats(Run::Block, binds, len);
1237                let exprs = self.nodes(Run::Block, exprs, len);
1238                out.push_str("{\"block\":[");
1239                for (i, (&pat, &expr)) in binds.iter().zip(exprs).enumerate() {
1240                    if i > 0 {
1241                        out.push(',');
1242                    }
1243                    if pat == Pat::None {
1244                        self.write_json(expr, out, ctx);
1245                    } else {
1246                        out.push_str("{\"bind\":{\"pat\":");
1247                        self.write_pat_json(pat, out, ctx);
1248                        out.push_str(",\"value\":");
1249                        self.write_json(expr, out, ctx);
1250                        out.push_str("}}");
1251                    }
1252                }
1253                out.push_str("]}");
1254            }
1255            // `{"case":{"subject":…,"arms":[{"pat":…,"body":…}, …]}}`.
1256            Node::Case { len, conds, exprs } => {
1257                let conds = self.pats(Run::Case, conds, len);
1258                let exprs = self.nodes(Run::Case, exprs, len + 1);
1259                let (bodies, subject) = exprs.split_at(len as usize);
1260                out.push_str("{\"case\":{\"subject\":");
1261                self.write_json(subject[0], out, ctx);
1262                out.push_str(",\"arms\":[");
1263                for (i, (&pat, &body)) in conds.iter().zip(bodies).enumerate() {
1264                    if i > 0 {
1265                        out.push(',');
1266                    }
1267                    out.push_str("{\"pat\":");
1268                    self.write_pat_json(pat, out, ctx);
1269                    out.push_str(",\"body\":");
1270                    self.write_json(body, out, ctx);
1271                    out.push('}');
1272                }
1273                out.push_str("]}}");
1274            }
1275            #[cfg(debug_assertions)]
1276            Node::Barrier { .. } => unreachable!("a barrier is not an operand"),
1277        }
1278    }
1279
1280    /// `[e0,e1,…]`.
1281    fn write_json_list(&self, nodes: &[Node], out: &mut String, ctx: &Ctx) {
1282        out.push('[');
1283        for (i, &node) in nodes.iter().enumerate() {
1284            if i > 0 {
1285                out.push(',');
1286            }
1287            self.write_json(node, out, ctx);
1288        }
1289        out.push(']');
1290    }
1291
1292    /// A head over `body` as nested `{"abs":{"param":…,"body":…}}`.
1293    fn write_abs_json(&self, head: &[Pat], body: NodeId, out: &mut String, ctx: &Ctx) {
1294        out.push_str("{\"abs\":{\"param\":");
1295        self.write_pat_json(head[0], out, ctx);
1296        out.push_str(",\"body\":");
1297        if head.len() == 1 {
1298            self.write_json(self.node(body), out, ctx);
1299        } else {
1300            self.write_abs_json(&head[1..], body, out, ctx);
1301        }
1302        out.push_str("}}");
1303    }
1304
1305    fn write_pat_json(&self, pat: Pat, out: &mut String, ctx: &Ctx) {
1306        match pat {
1307            Pat::None => unreachable!("only a block clause's slot binds nothing"),
1308            Pat::Discard => out.push_str("{\"discard\":true}"),
1309            Pat::Unit => out.push_str("{\"unit\":true}"),
1310            Pat::Bind(name) => {
1311                out.push_str("{\"bind\":");
1312                json_sym(name, out, ctx);
1313                out.push('}');
1314            }
1315            Pat::At(name, inner) => {
1316                out.push_str("{\"at\":[");
1317                json_sym(name, out, ctx);
1318                out.push(',');
1319                self.write_pat_json(self.pat(inner), out, ctx);
1320                out.push_str("]}");
1321            }
1322            Pat::Equal(expr) => self.write_tagged_json("{\"eq\":", expr, out, ctx),
1323            Pat::Less(expr) => self.write_tagged_json("{\"lt\":", expr, out, ctx),
1324            Pat::Greater(expr) => self.write_tagged_json("{\"gt\":", expr, out, ctx),
1325            Pat::TypeKind(kind, inner) => {
1326                out.push_str("{\"as\":[");
1327                json_str(kind.name(), out);
1328                out.push(',');
1329                self.write_pat_json(self.pat(inner), out, ctx);
1330                out.push_str("]}");
1331            }
1332            Pat::TypeRef(proto, inner) => {
1333                out.push_str("{\"as\":[");
1334                self.write_json(self.node(proto), out, ctx);
1335                out.push(',');
1336                self.write_pat_json(self.pat(inner), out, ctx);
1337                out.push_str("]}");
1338            }
1339            Pat::Unwrap { inner, .. } => {
1340                out.push_str("{\"unwrap\":");
1341                self.write_pat_json(self.pat(inner), out, ctx);
1342                out.push('}');
1343            }
1344            Pat::Or(left, right) => {
1345                out.push_str("{\"or\":[");
1346                self.write_pat_json(self.pat(left), out, ctx);
1347                out.push(',');
1348                self.write_pat_json(self.pat(right), out, ctx);
1349                out.push_str("]}");
1350            }
1351            Pat::List { len, elems, rest } => {
1352                out.push_str("{\"listpat\":{\"elems\":[");
1353                for (i, &elem) in self.pats(Run::PatList, elems, len).iter().enumerate() {
1354                    if i > 0 {
1355                        out.push(',');
1356                    }
1357                    self.write_pat_json(elem, out, ctx);
1358                }
1359                out.push_str("],\"rest\":");
1360                self.write_rest_json(rest, out, ctx);
1361                out.push_str("}}");
1362            }
1363            Pat::Map { len, entries, rest } => {
1364                let all = self.pats(Run::PatMap, entries, 2 * len);
1365                let (keys, values) = all.split_at(len as usize);
1366                out.push_str("{\"mappat\":{\"entries\":[");
1367                for (i, (&key, &value)) in keys.iter().zip(values).enumerate() {
1368                    if i > 0 {
1369                        out.push(',');
1370                    }
1371                    out.push('[');
1372                    match key {
1373                        Pat::Lookup(expr) => self.write_tagged_json("{\"lookup\":", expr, out, ctx),
1374                        key => {
1375                            out.push_str("{\"capture\":");
1376                            self.write_pat_json(key, out, ctx);
1377                            out.push('}');
1378                        }
1379                    }
1380                    out.push(',');
1381                    self.write_pat_json(value, out, ctx);
1382                    out.push(']');
1383                }
1384                out.push_str("],\"rest\":");
1385                self.write_rest_json(rest, out, ctx);
1386                out.push_str("}}");
1387            }
1388            Pat::Lookup(_) => unreachable!("a lookup is only a map pattern's key"),
1389            Pat::When { inner, gpat, cond } => {
1390                out.push_str("{\"when\":{\"inner\":");
1391                self.write_pat_json(self.pat(inner), out, ctx);
1392                out.push_str(",\"guard\":");
1393                match gpat.get() {
1394                    None => out.push_str("null"),
1395                    Some(gpat) => self.write_pat_json(self.pat(gpat), out, ctx),
1396                }
1397                out.push_str(",\"cond\":");
1398                self.write_json(self.node(cond), out, ctx);
1399                out.push_str("}}");
1400            }
1401            #[cfg(debug_assertions)]
1402            Pat::Barrier { .. } => unreachable!("a barrier is not an operand"),
1403        }
1404    }
1405
1406    fn write_tagged_json(&self, open: &str, expr: NodeId, out: &mut String, ctx: &Ctx) {
1407        out.push_str(open);
1408        self.write_json(self.node(expr), out, ctx);
1409        out.push('}');
1410    }
1411
1412    /// `null` for an exact pattern, otherwise the rest pattern's JSON.
1413    fn write_rest_json(&self, rest: PatId, out: &mut String, ctx: &Ctx) {
1414        match rest.get() {
1415            None => out.push_str("null"),
1416            Some(rest) => self.write_pat_json(self.pat(rest), out, ctx),
1417        }
1418    }
1419
1420    /// A `recur` group's items in the order of their text, as a rendering
1421    /// lists them.
1422    fn items_by_text(&self, module: &Module, ctx: &Ctx) -> Vec<&Member> {
1423        let mut items: Vec<&Member> = self.members(module)[..module.items as usize]
1424            .iter()
1425            .collect();
1426        items.sort_by_cached_key(|member| ctx.text(member.name));
1427        items
1428    }
1429}
1430
1431/// `{"Int":"<decimal>"}`: an integer that JSON numbers cannot hold exactly.
1432fn write_tagged_int(decimal: &str, out: &mut String) {
1433    out.push_str("{\"Int\":");
1434    json_str(decimal, out);
1435    out.push('}');
1436}
1437
1438/// Writes the text of `sym`.
1439pub(crate) fn push_sym(sym: Sym, out: &mut String, ctx: &Ctx) {
1440    ctx.with_str(sym, |s| out.push_str(s));
1441}
1442
1443/// Writes the text of `sym` as a JSON string literal.
1444fn json_sym(sym: Sym, out: &mut String, ctx: &Ctx) {
1445    ctx.with_str(sym, |s| json_str(s, out));
1446}
1447
1448/// Writes `s` as a quoted and escaped JSON string literal.
1449pub(crate) fn json_str(s: &str, out: &mut String) {
1450    out.push('"');
1451    for ch in s.chars() {
1452        match ch {
1453            '"' => out.push_str("\\\""),
1454            '\\' => out.push_str("\\\\"),
1455            '\n' => out.push_str("\\n"),
1456            '\r' => out.push_str("\\r"),
1457            '\t' => out.push_str("\\t"),
1458            c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
1459            c => out.push(c),
1460        }
1461    }
1462    out.push('"');
1463}
1464
1465#[cfg(test)]
1466mod tests {
1467    use alloc::vec;
1468
1469    use super::*;
1470
1471    /// The builtins are the first chunk; an expression and an unnamed module
1472    /// take a name from one count, and a loaded module keeps its own.
1473    #[test]
1474    fn chunks_are_named_by_what_made_them() {
1475        let ctx = Ctx::new();
1476        let ids = [
1477            ChunkId::ELLY,
1478            ctx.parse("1").expect("parses"),
1479            ctx.compile_module("x = 1").expect("compiles"),
1480            ctx.parse("1").expect("parses"),
1481            ctx.compile_module_named("x = 1", &[], Some(Text::from("Foo")))
1482                .expect("compiles"),
1483        ];
1484        let names: Vec<String> = ids
1485            .iter()
1486            .map(|id| ctx.chunk(*id).name().to_string())
1487            .collect();
1488        assert_eq!(names, ["Elly", "__eval_0", "__module_1", "__eval_2", "Foo"]);
1489    }
1490
1491    /// A source that fails to parse adds no chunk and takes no name.
1492    #[test]
1493    fn a_failed_parse_adds_no_chunk() {
1494        let ctx = Ctx::new();
1495        assert!(ctx.parse("f x").is_err());
1496        assert!(ctx.compile_module("x = y").is_err());
1497        let id = ctx.parse("1").expect("parses");
1498        assert_eq!(id, ChunkId::at(1));
1499        assert_eq!(ctx.chunk(id).name().as_str(), "__eval_0");
1500    }
1501
1502    /// A `recur` group nested in another and in a module item: every module
1503    /// entry comes after its parent's, and the members keep their order.
1504    #[test]
1505    fn a_nested_module_comes_after_its_parent() {
1506        let ctx = Ctx::new();
1507        let src = "f = recur (g = recur (h = 1) h) g\nlocal l = 2\nconst c\n__main = f";
1508        let id = ctx.compile_module(src).expect("compiles");
1509        let chunk = ctx.chunk(id);
1510        let parents: Vec<ModId> = chunk.modules.iter().map(|m| m.parent).collect();
1511        assert_eq!(parents, vec![ModId::NONE, ModId(0), ModId(1)]);
1512        let top = chunk.module(chunk.top());
1513        let names: Vec<String> = chunk
1514            .members(top)
1515            .iter()
1516            .map(|m| ctx.text(m.name).to_string())
1517            .collect();
1518        assert_eq!(names, ["f", "l", "c"]);
1519        assert_eq!((top.items, top.locals), (1, 1));
1520        assert!(
1521            chunk.members(top)[2].node.get().is_none(),
1522            "an iota has no body"
1523        );
1524        assert_eq!(
1525            chunk.to_sexp(top.body, &ctx),
1526            "(moditem f)",
1527            "__main is the body"
1528        );
1529    }
1530}