beck_core/
core.rs

1//! `Core` — the load-bearing IR, and the evaluator that runs it.
2//!
3//! [`docs/04-compiler-architecture.md`](../../../../../docs/04-compiler-architecture.md) §4.2:
4//! "Typed ANF/SSA hybrid; explicit closures, explicit effect operations, explicit tier annotation
5//! per node; `Query` sub-language kept *symbolic*. Typechecked semantics, placement, splitting,
6//! optimisation. The load-bearing IR."
7//!
8//! Two things stay symbolic here, exactly as §4.2 demands, and for the same stated reason —
9//! lowering them early would foreclose Phase 3:
10//!
11//! * **Signal and stream operations** are [`Prim`]s ([`Prim::Fold`], [`Prim::Durable`],
12//!   [`Prim::SignalMap`], …), not loops. A fold that has already become a loop cannot be compiled
13//!   to an incremental dataflow plan.
14//! * **UI trees** are `Html` *values* built by [`Prim::HtmlEl`] and friends, not DOM mutation
15//!   calls, so the same value can be server-side rendered, diffed, or (Phase 3) compiled for the
16//!   client.
17//!
18//! # On the backend
19//!
20//! The roadmap names Cranelift as Phase 1's server backend. What is here instead is a **`Core`
21//! evaluator**: the "engine-in-Rust with the language as its configuration" route that
22//! [`docs/00-original-idea.md`](../../../../../docs/00-original-idea.md) names as one of the three
23//! that work for a GC'd functional language on a Rust host (Materialize's shape). It is the
24//! deliberately-bad-but-complete option, and it keeps the `Core → Target` seam narrow, which §5.2
25//! says is what lets a backend slot in later. The Phase 1 report says plainly that native codegen
26//! is not done.
27
28use std::collections::BTreeSet;
29use std::fmt;
30use std::sync::Arc;
31
32use beck_diag::Span;
33
34use crate::html::Html;
35use crate::pmap::PMap;
36use crate::seq::Seq;
37use crate::ty::{Effect, Tier, Ty};
38
39pub type VarId = u32;
40
41/// A primitive operation. Everything the standard library provides in Phase 1.
42#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
43pub enum Prim {
44    Add,
45    Sub,
46    Mul,
47    Div,
48    Rem,
49    Neg,
50    /// `abs`, `sqrt` and `Int` → `Float`, which are the three the numeric tower needs before any
51    /// of SICP §1.1.7 will run (`docs/27` §27.2). `Abs` is resolved from its operand the way the
52    /// arithmetic operators are; the other two are monomorphic.
53    Abs,
54    Sqrt,
55    Sin,
56    Cos,
57    Trunc,
58    ToFloat,
59    Eq,
60    Ne,
61    Lt,
62    Le,
63    Gt,
64    Ge,
65    And,
66    Or,
67    Not,
68    ToStr,
69    StrTrim,
70    StrToInt,
71    // ---- strings (docs/27 §? — Wave 2's string half). One primitive each, because a string is
72    // where a language's host has to be asked and there is nothing to express in Beck itself.
73    StrLen,
74    StrSlice,
75    StrSplit,
76    StrJoin,
77    StrContains,
78    StrStartsWith,
79    StrEndsWith,
80    StrUpper,
81    StrLower,
82    StrReplace,
83    StrIndexOf,
84    StrRepeat,
85    StrChars,
86    StrIsEmpty,
87    ListLen,
88    ListIsEmpty,
89    ListMin,
90    ListMax,
91    ListSum,
92    ListUnique,
93    // ---- collections
94    ListGet,
95    ListSlice,
96    ListReverse,
97    ListTake,
98    ListDrop,
99    ListContains,
100    ListIndexOf,
101    ListFold,
102    ListAll,
103    ListAny,
104    ListFlatMap,
105    ListZip,
106    ListAppend,
107    MapKeys,
108    MapMerge,
109    // ---- JSON, and the standard library's first fallible function.
110    //
111    // `json_parse` raises rather than returning a `Result`, which is the shape
112    // [`docs/27`](../../../../../docs/27-the-walls-come-down-report.md) settled and the reason §8.5.3's trap 2
113    // said the library had to wait for it: a caller that wants a `Result` writes `try:`, and a
114    // caller inside something already fallible writes nothing at all.
115    JsonParse,
116    JsonRender,
117    // ---- time. `now()` gives milliseconds; these two are the civil calendar over them.
118    TimeFormat,
119    TimeParse,
120    // ---- digests, encodings and identifiers (`crate::digest`). A hash is a table and base64 is a
121    // grammar, so both are the host's half of `lib/README.md`'s division; a digest is also a *pure*
122    // function, which is what separates this from the other things a crypto library offers.
123    Digest,
124    /// A message authentication code: the one primitive whose input is a `secret[Str]` and whose
125    /// output is a `Str`. Charged `cap.sign` rather than left free, so a view cannot mint one —
126    /// `docs/adr/0014` is the record of the decision and §3.5 is what it is measured against.
127    DigestKeyed,
128    /// Constant-time equality, for the caller comparing a digest against one that arrived.
129    DigestEq,
130    HexEncode,
131    HexDecode,
132    Base64Encode,
133    Base64Decode,
134    /// Validates and *normalises*: two spellings of one identifier must not be two map keys.
135    UuidParse,
136    UuidVersion,
137    // ---- the outbound call. The *second* primitive whose row is a function of its argument
138    // (`Raise` is the first): the host it is given is the `net.out(host)` atom it performs, which
139    // is why that argument has to be written at the call site rather than computed.
140    HttpFetch,
141    MapList,
142    FilterList,
143    ConcatLists,
144    SortBy,
145    MapGet,
146    MapInsert,
147    MapRemove,
148    MapValues,
149    MapContains,
150    MapLen,
151    OptionIsSome,
152    OptionUnwrapOr,
153    HtmlEl,
154    HtmlText,
155    HtmlAttr,
156    HtmlOn,
157    HtmlKey,
158    /// Mints a fresh id. Nondeterministic, so §3.7 forbids it inside a fold; the client mints
159    /// entity ids instead, which is "the small tell that browsers here are replicas, not
160    /// terminals".
161    NewUuid,
162    /// Reads the wall clock. The other half of §3.7's rule: "time is data on the envelope".
163    Now,
164    /// Reads a secret from the process environment, yielding a `secret[Str]` (§3.5).
165    SecretEnv,
166    /// `raise e` — fail with a value.
167    ///
168    /// The atom it performs is `raises(T)`, which depends on the *type* of its argument, so the
169    /// checker attaches it where that type is known rather than [`Prim::effects`] declaring it.
170    /// This is the first primitive whose row is not a constant, and it is why that table's doc
171    /// says "the atoms this primitive performs *itself*".
172    Raise,
173    /// `try: block` — run a thunk, and turn a raise of the named type into an `Err`.
174    ///
175    /// Two arguments: the thunk, and the name of the error type this handler catches. The name is
176    /// what stops a handler from catching a failure it cannot type — a caller's function may raise
177    /// something this `try` never heard of, and that has to keep travelling.
178    Try,
179    /// `parallel: block` — run the scope's children, then its tail with their results bound.
180    ///
181    /// The arguments are the children's thunks followed by the continuation, so a child cannot
182    /// outlive the scope: there is no handle, and the only thing that can read a child's result is
183    /// the one lambda the scope built. That is [`docs/38`](../../../../../docs/38-literature-survey.md)
184    /// §38.4's "spawn/await as effect operations, the scope as their handler" with the handler as
185    /// the *only* form — the operations are not separately reachable.
186    ///
187    /// The children are independent by construction (none of them can name another) and no child
188    /// may perform an effect another child could observe, so the scope's answer does not depend on
189    /// the order they ran in. A backend may therefore run them together; running them in the order
190    /// they are written is a correct implementation of that, and is what the tree-walker does.
191    Parallel,
192    /// Wraps a value as `internal[T]`: storable, never Sendable.
193    InternalOf,
194    /// Unwraps one. Performs `cap.internal`, so only the authority chokepoint can do it.
195    Reveal,
196    // ---- the symbolic signal vocabulary (§3.7) ----
197    MergeClients,
198    /// `presence()` — who is connected now, as a signal that is not a function of the log.
199    ///
200    /// D6's last row. It performs `cap.presence` rather than an atom of its own, which is F16
201    /// ([`docs/14`](../../../../../docs/14-review-findings.md)) taken literally: "presence signals
202    /// leak who-is-online; gate behind a capability like any other view". The capability is also
203    /// what places it — no tier below the server discharges a `cap.*` — so a fold cannot read the
204    /// roster and a view reaches it across a declared edge.
205    Presence,
206    /// `awareness(f)` — what every subscriber contributes, keyed by actor.
207    ///
208    /// Presence with a payload, and the same three rules for the same reasons: a non-log input to
209    /// a view, bounded because the key is a name the client chooses (`docs/82` §82.5), and refused
210    /// at the chokepoint because an event whose existence depended on where somebody's cursor was
211    /// would not survive a replay. It carries `cap.presence` rather than a capability of its own —
212    /// a roster with payloads discloses strictly more than a roster, and gating the smaller
213    /// disclosure while leaving the larger one open would be the wrong way round.
214    ///
215    /// `f` is a function of the **`Session`**, which is what the server already holds for every
216    /// connection. `docs/104` §104.8 has the half that needs more than that and why it waits.
217    Awareness,
218    /// `freshness()` — whether the page being rendered is the confirmed state or a guess.
219    ///
220    /// §3.7's "`Signal[T]` carries a freshness dimension (`confirmed | pending(n)`) that UI code
221    /// can render (\"saving…\") — staleness is typed, not pretended away". It is the mirror image
222    /// of [`Prim::Presence`]: presence is a fact the *server* holds about its sockets and cannot
223    /// reach a Mode B client, and freshness is a fact the *client* holds about its own guesses and
224    /// is `Confirmed` everywhere else. No capability, because nothing is disclosed by it — a client
225    /// counting its own unacknowledged commands is reading itself.
226    Freshness,
227    /// `gestures(step, init)` — the non-durable fold
228    /// [`docs/10`](../../../../../docs/10-decisions.md) D30 decides the shape of.
229    ///
230    /// The mirror of [`Prim::MergeClients`] on the other side of the wire: that one is every
231    /// client's proposals arriving at the one place time enters, and this is one client's gestures
232    /// arriving nowhere else at all. **Nothing here is proposed, validated or recorded** — a
233    /// gesture is a movement of the interface, which is why it is neither a `Command` nor an
234    /// `Event`, the two words that already mean something about the log.
235    ///
236    /// It carries `dom`, and that is the whole of its placement: no tier but the client discharges
237    /// it ([`crate::ty::Tier::discharges`]), so this is client-placed by machinery that was already
238    /// there, and `durable` is on a tier the client cannot reach. D3's invariant is untouched
239    /// rather than weakened — replay reproduces everything that was ever in the log, and no gesture
240    /// ever was.
241    Gestures,
242    StreamFilterMap,
243    Fold,
244    Durable,
245    SignalMap,
246    SignalMap2,
247    /// §3.8's per-session view: `todos.map(filter_by(session.user))`. First-class because "the
248    /// fanout cost becomes a first-class engineering concern".
249    PerSession,
250    /// The authority chokepoint: the sole consumer of ingress, holding the accumulator so that
251    /// first-writer-wins and ownership can be decided (§3.7, F2).
252    Decide,
253}
254
255impl Prim {
256    pub fn name(self) -> &'static str {
257        use Prim::*;
258        match self {
259            Add => "+",
260            Sub => "-",
261            Mul => "*",
262            Div => "/",
263            Rem => "%",
264            Neg => "negate",
265            Raise => "raise",
266            Try => "try",
267            Parallel => "parallel",
268            Abs => "abs",
269            Sqrt => "sqrt",
270            Sin => "sin",
271            Cos => "cos",
272            Trunc => "trunc",
273            ToFloat => "float",
274            Eq => "==",
275            Ne => "!=",
276            Lt => "<",
277            Le => "<=",
278            Gt => ">",
279            Ge => ">=",
280            And => "and",
281            Or => "or",
282            Not => "not",
283            ToStr => "str",
284            StrTrim => "str_trim",
285            StrToInt => "str_to_int",
286            StrLen => "str_len",
287            StrSlice => "str_slice",
288            StrSplit => "str_split",
289            StrJoin => "str_join",
290            StrContains => "str_contains",
291            StrStartsWith => "str_starts_with",
292            StrEndsWith => "str_ends_with",
293            StrUpper => "str_upper",
294            StrLower => "str_lower",
295            StrReplace => "str_replace",
296            StrIndexOf => "str_index_of",
297            StrRepeat => "str_repeat",
298            StrChars => "str_chars",
299            ListGet => "list_get",
300            ListSlice => "list_slice",
301            ListReverse => "list_reverse",
302            ListTake => "list_take",
303            ListDrop => "list_drop",
304            ListContains => "list_contains",
305            ListIndexOf => "list_index_of",
306            ListFold => "list_fold",
307            ListAll => "list_all",
308            ListAny => "list_any",
309            ListFlatMap => "list_flat_map",
310            ListZip => "list_zip_with",
311            ListAppend => "list_append",
312            MapKeys => "map_keys",
313            MapMerge => "map_merge",
314            JsonParse => "json_parse",
315            JsonRender => "json_render",
316            TimeFormat => "time_format",
317            TimeParse => "time_parse",
318            Digest => "digest",
319            DigestKeyed => "digest_keyed",
320            DigestEq => "digest_eq",
321            HexEncode => "hex_encode",
322            HexDecode => "hex_decode",
323            Base64Encode => "base64_encode",
324            Base64Decode => "base64_decode",
325            UuidParse => "uuid_parse",
326            UuidVersion => "uuid_version",
327            HttpFetch => "http_fetch",
328            StrIsEmpty => "str_is_empty",
329            ListLen => "list_len",
330            ListIsEmpty => "list_is_empty",
331            ListMin => "list_min",
332            ListMax => "list_max",
333            ListSum => "list_sum",
334            ListUnique => "list_unique",
335            MapList => "map_list",
336            FilterList => "filter_list",
337            ConcatLists => "concat_lists",
338            SortBy => "sort_by",
339            MapGet => "map_get",
340            MapInsert => "map_insert",
341            MapRemove => "map_remove",
342            MapValues => "map_values",
343            MapContains => "map_contains",
344            MapLen => "map_len",
345            OptionIsSome => "is_some",
346            OptionUnwrapOr => "unwrap_or",
347            HtmlEl => "html_el",
348            HtmlText => "html_text",
349            HtmlAttr => "html_attr",
350            HtmlOn => "html_on",
351            HtmlKey => "html_key",
352            NewUuid => "uuid",
353            Now => "now",
354            SecretEnv => "secret_env",
355            InternalOf => "internal_of",
356            Reveal => "reveal",
357            MergeClients => "merge_clients",
358            Presence => "presence",
359            Awareness => "awareness",
360            Freshness => "freshness",
361            Gestures => "gestures",
362            StreamFilterMap => "filter_map",
363            Fold => "fold",
364            Durable => "durable",
365            SignalMap => "signal_map",
366            SignalMap2 => "map2",
367            PerSession => "per_session",
368            Decide => "decide",
369        }
370    }
371
372    /// The atoms this primitive performs *itself*.
373    ///
374    /// The polymorphic half of a primitive's row — `map_list`'s `e`, which is whatever its function
375    /// argument does — lives in the scheme in [`crate::prelude`], because a row variable is not a
376    /// constant. A test holds the two in agreement.
377    pub fn effects(self) -> Vec<Effect> {
378        match self {
379            // "Every connected client's send!s, interleaved. Arbitrary order — this is the
380            // nondeterminism; there is exactly one of these."
381            Prim::MergeClients => vec![Effect::Ingress],
382            // Who is connected is not a function of the log, so this is the second source of
383            // nondeterminism in a Beck program — and the only one a *view* may read. The atom is a
384            // capability rather than a new label: F16 asks for exactly that, and `cap.*` is what
385            // keeps it off the tiers that would make it unreplayable (§3.3).
386            Prim::Presence | Prim::Awareness => {
387                vec![Effect::Cap(std::sync::Arc::from("presence"))]
388            }
389            Prim::Durable => vec![Effect::Durable],
390            // D30: ephemerality is a property of the stream. `dom` is what says so — the client is
391            // the only tier that discharges it, so the state folded from this stream is on the
392            // client, and `durable` is on a tier the client cannot reach.
393            Prim::Gestures => vec![Effect::Dom],
394            Prim::NewUuid | Prim::Now => vec![Effect::Nondet],
395            // The scope performs `spawn` itself; what its children perform is charged by the
396            // checker, from each child's own row, because a thunk's effects belong to the thunk's
397            // type and this is the form that calls them.
398            Prim::Parallel => vec![Effect::Spawn],
399            Prim::SecretEnv => vec![Effect::Env],
400            // Wrapping is free; *reading* is the privileged half, and the capability is what stops
401            // a view unwrapping one to render it.
402            Prim::Reveal => vec![Effect::Cap(std::sync::Arc::from("internal"))],
403            // The standard library's fallible pair. Unlike `Prim::Raise`, whose atom depends on
404            // its argument's type, these two raise a type the prelude declares — so the row *is* a
405            // constant and belongs here, where a test holds it against the scheme.
406            Prim::JsonParse => vec![Effect::Raises(std::sync::Arc::from("JsonError"))],
407            Prim::TimeParse => vec![Effect::Raises(std::sync::Arc::from("TimeError"))],
408            Prim::HexDecode | Prim::Base64Decode => {
409                vec![Effect::Raises(std::sync::Arc::from("EncodingError"))]
410            }
411            Prim::UuidParse | Prim::UuidVersion => {
412                vec![Effect::Raises(std::sync::Arc::from("UuidError"))]
413            }
414            // The declassifier. `digest` and the encodings are pure; this one reads a
415            // `secret[Str]`, so it is held where `reveal` is held — behind a capability no client
416            // tier discharges, which is what stops a view minting a token (`docs/adr/0014`).
417            Prim::DigestKeyed => vec![Effect::Cap(std::sync::Arc::from("sign"))],
418            // Half of `http_fetch`'s row is a constant and half is its first argument. The
419            // constant half is here; the `net.out(host)` half is added by [`Core::effects`],
420            // which can see the argument, and by the checker, which is where it is charged.
421            Prim::HttpFetch => vec![Effect::Raises(std::sync::Arc::from("HttpError"))],
422            _ => Vec::new(),
423        }
424    }
425}
426
427#[derive(Clone, Debug, PartialEq)]
428pub enum Const {
429    Unit,
430    Bool(bool),
431    Int(i64),
432    Float(f64),
433    Str(Arc<str>),
434}
435
436/// One arm of a `match`. Patterns are shallow — a constructor and its named field binders — which
437/// is what §3.1's exhaustiveness check needs and no more.
438#[derive(Clone, Debug)]
439pub struct Arm {
440    pub pattern: Pattern,
441    /// `case Circle(r) if r > 0:` — a condition on the arm, in the scope of what the pattern bound.
442    ///
443    /// A guard that fails falls through to the next arm, which is what makes it a guard rather
444    /// than an `if` in the body.
445    pub guard: Option<Core>,
446    pub body: Core,
447    pub span: Span,
448}
449
450impl Arm {
451    /// Every expression this arm holds, in the order they run.
452    ///
453    /// One method rather than `&a.body` at each of fourteen call sites, for
454    /// [`Pattern::binders`]'s reason and with its history: a guard added as a field those sites
455    /// did not know about would be a `Core` that liveness never marks, that `frames` never counts
456    /// a slot for, and that the plan's free-variable analysis never sees — none of which is a
457    /// compile error, and all of which are wrong on a program that uses one.
458    pub fn exprs(&self) -> impl Iterator<Item = &Core> {
459        self.guard.iter().chain(std::iter::once(&self.body))
460    }
461
462    pub fn exprs_mut(&mut self) -> impl Iterator<Item = &mut Core> {
463        self.guard.iter_mut().chain(std::iter::once(&mut self.body))
464    }
465}
466
467#[derive(Clone, Debug)]
468pub enum Pattern {
469    Wildcard,
470    Bind(VarId),
471    Const(Const),
472    /// `Added(id, text)` — `variant` names the constructor, and each field carries the pattern
473    /// matched against it.
474    ///
475    /// A field's pattern is usually a [`Pattern::Bind`] or a [`Pattern::Wildcard`], which is what
476    /// `Added(id, text)` and `Added(_)` mean. It may be any pattern: `Some(Added(id, text))` is a
477    /// `Ctor` whose one field is a `Ctor`.
478    Ctor {
479        variant: Arc<str>,
480        binds: Vec<(Arc<str>, Pattern)>,
481    },
482    /// `whole @ Circle(r)` — a name for the value, and a pattern that takes it apart.
483    ///
484    /// The binder is irrefutable, so whether this matches is entirely `inner`'s question.
485    At {
486        var: VarId,
487        inner: Box<Pattern>,
488    },
489    /// `Circle(r) | Square(r)` — one of several, and every alternative binds the same names.
490    ///
491    /// The checker unifies the alternatives' binders onto one set of variables, so the body reads
492    /// `r` without knowing which alternative matched. That is the rule that makes an or-pattern a
493    /// pattern rather than two arms sharing a body.
494    Or(Vec<Pattern>),
495    /// `[]`, `[x]`, `[a, b]`, `[first, *rest]` — a list, taken apart.
496    ///
497    /// `items` is one pattern per fixed element and `rest` is the optional tail binder. A pattern
498    /// with no `rest` matches a list of exactly `items.len()` elements; one with a `rest` matches
499    /// any list at least that long.
500    ///
501    /// The tail is a binder rather than a pattern, and deliberately: `[a, *[b, c]]` is `[a, b, c]`
502    /// written twice over, so what it would add is a second spelling rather than a shape.
503    List {
504        items: Vec<Pattern>,
505        rest: Option<Option<VarId>>,
506    },
507}
508
509impl Pattern {
510    /// Every variable this pattern binds, at any depth.
511    ///
512    /// One method rather than a `match` at each of the three call sites, because those three were
513    /// `Bind`/`Ctor`/`_ => {}` — and a new pattern kind falling into the `_` would have been a
514    /// silent miscount in the splitter's variable supply and a false *free* variable in the plan's
515    /// analysis. Neither would have failed a test until a program used one (`docs/27` §27.3).
516    pub fn binders(&self) -> Vec<VarId> {
517        let mut out = Vec::new();
518        self.collect_binders(&mut out);
519        out
520    }
521
522    fn collect_binders(&self, out: &mut Vec<VarId>) {
523        match self {
524            Pattern::Wildcard | Pattern::Const(_) => {}
525            Pattern::Bind(v) => out.push(*v),
526            Pattern::At { var, inner } => {
527                out.push(*var);
528                inner.collect_binders(out);
529            }
530            Pattern::Ctor { binds, .. } => {
531                for (_, p) in binds {
532                    p.collect_binders(out);
533                }
534            }
535            Pattern::List { items, rest } => {
536                for p in items {
537                    p.collect_binders(out);
538                }
539                out.extend(rest.iter().filter_map(|b| *b));
540            }
541            // Every alternative binds the same variables, so one would do; all of them, deduped,
542            // is what stays right if the checker's unification is ever wrong about that.
543            Pattern::Or(alts) => {
544                for p in alts {
545                    p.collect_binders(out);
546                }
547                out.sort_unstable();
548                out.dedup();
549            }
550        }
551    }
552
553    /// Whether this pattern matches every value of its type, so that no arm after it can run.
554    ///
555    /// Only a binder and a wildcard do — and a list pattern of nothing but a tail, `[*rest]`,
556    /// which is the one refutable-looking shape that refuses nothing.
557    pub fn irrefutable(&self) -> bool {
558        match self {
559            Pattern::Wildcard | Pattern::Bind(_) => true,
560            Pattern::List { items, rest } => items.is_empty() && rest.is_some(),
561            Pattern::Or(alts) => alts.iter().any(Pattern::irrefutable),
562            Pattern::At { inner, .. } => inner.irrefutable(),
563            Pattern::Const(_) | Pattern::Ctor { .. } => false,
564        }
565    }
566}
567
568#[derive(Clone, Debug)]
569pub enum CoreKind {
570    Const(Const),
571    Var(VarId),
572    /// A reference to a top-level definition.
573    Global(Arc<str>),
574    Lam {
575        /// Shared for the same reason `body` is: evaluating a `lam` hands the list to a closure,
576        /// and a refcount bump is cheaper than copying it once per call.
577        params: Arc<[VarId]>,
578        /// Shared, not owned: a closure is built every time a `lam` node is *evaluated*, and a
579        /// `Box` meant deep-copying the whole function body each time. `docs/70` §70.3 measured
580        /// 20,000 calls to a function whose executed path never changed costing 42 ms, 227 ms and
581        /// 606 ms as the *unexecuted* part of its body grew.
582        body: Arc<Core>,
583    },
584    App {
585        func: Box<Core>,
586        args: Vec<Core>,
587    },
588    Prim {
589        op: Prim,
590        args: Vec<Core>,
591    },
592    Let {
593        var: VarId,
594        value: Box<Core>,
595        body: Box<Core>,
596    },
597    If {
598        cond: Box<Core>,
599        then: Box<Core>,
600        alt: Box<Core>,
601    },
602    Match {
603        scrutinee: Box<Core>,
604        arms: Vec<Arm>,
605    },
606    /// Construct a union variant or a model record.
607    Make {
608        ty: Arc<str>,
609        variant: Option<Arc<str>>,
610        fields: Vec<(Arc<str>, Core)>,
611    },
612    Field {
613        base: Box<Core>,
614        name: Arc<str>,
615    },
616    /// `t.with(done=not t.done)` — a functional record update. The sketch's
617    /// `(set t :done (not t.done))`, and the reason `Todo` never needs a mutable binding.
618    With {
619        base: Box<Core>,
620        fields: Vec<(Arc<str>, Core)>,
621    },
622    ListLit(Vec<Core>),
623    MapLit(Vec<(Core, Core)>),
624}
625
626#[derive(Clone, Debug)]
627pub struct Core {
628    pub kind: CoreKind,
629    pub ty: Ty,
630    /// Which tier this node runs on. §4.2: "explicit tier annotation per node".
631    pub tier: Tier,
632    pub span: Span,
633    /// Set on a [`CoreKind::Var`] whose value this expression is the **last** reader of, so a
634    /// backend may move the binding rather than copy it. [`crate::liveness`] is what sets it and
635    /// what the guarantee means; `false` is always safe.
636    pub last_use: bool,
637    /// Set on a [`CoreKind::Make`]: which written field belongs at each position of the record it
638    /// builds, packed four bits per field. [`crate::fields`] is what sets it and what the packing
639    /// means; [`crate::fields::UNORDERED`] is always safe and means "sort at run time".
640    ///
641    /// It costs nothing: a `u32` here fits in the padding `last_use` already leaves, so `Core` is
642    /// 160 bytes either way.
643    pub order: u32,
644    /// Set on a [`CoreKind::Lam`]: how many bindings its body makes, so a call can reserve room
645    /// for them in one frame instead of allocating a scope per `let`. [`crate::frames`] is what
646    /// sets it and what the count means; `0` is always safe and means "chain a scope, as before".
647    pub locals: u32,
648}
649
650/// The string this expression *is*, when it is written as one.
651///
652/// Deliberately not an evaluator and deliberately not a constant folder: the one caller is the
653/// host of an outbound call, and "the host is written at the call site" is the rule that makes an
654/// egress policy derivable. A `let` that happens to bind a literal is not written at the call
655/// site, and reading through one would make the rule depend on how much folding the compiler
656/// currently does.
657pub fn literal_str(c: &Core) -> Option<Arc<str>> {
658    match &c.kind {
659        CoreKind::Const(Const::Str(s)) => Some(s.clone()),
660        _ => None,
661    }
662}
663
664impl Core {
665    pub fn new(kind: CoreKind, ty: Ty, span: Span) -> Core {
666        Core {
667            kind,
668            ty,
669            tier: Tier::Any,
670            span,
671            last_use: false,
672            order: crate::fields::UNORDERED,
673            locals: 0,
674        }
675    }
676
677    /// Every effect this expression can perform, by walking what it calls.
678    ///
679    /// Phase 1 used this as *the* effect analysis. Phase 2 does not: rows are inferred during
680    /// checking, where a call's latent row is known and a mere *reference* to a function performs
681    /// nothing. What survives is this syntactic over-approximation, used in one place where that is
682    /// the right answer — asking what a fold's function body could reach, including through a
683    /// function value it was handed.
684    pub fn effects(&self, globals: &dyn Fn(&str) -> Vec<Effect>, out: &mut Vec<Effect>) {
685        match &self.kind {
686            CoreKind::Prim { op, args } => {
687                for e in op.effects() {
688                    if !out.contains(&e) {
689                        out.push(e);
690                    }
691                }
692                // `http_fetch`'s host is its first argument and a literal, so this walk can read
693                // it. It matters here rather than only in the checker because this is the oracle
694                // `testing::performs_itself` asks — a definition that makes the call is the one a
695                // `stub net.out(host)` replaces, and a definition that merely calls it is not.
696                if let (Prim::HttpFetch, Some(host)) = (op, args.first().and_then(literal_str)) {
697                    let atom = Effect::NetOut(host);
698                    if !out.contains(&atom) {
699                        out.push(atom);
700                    }
701                }
702                for a in args {
703                    a.effects(globals, out);
704                }
705            }
706            CoreKind::Global(name) => {
707                for e in globals(name) {
708                    if !out.contains(&e) {
709                        out.push(e);
710                    }
711                }
712            }
713            CoreKind::Const(_) | CoreKind::Var(_) => {}
714            CoreKind::Lam { body, .. } => body.effects(globals, out),
715            CoreKind::App { func, args } => {
716                func.effects(globals, out);
717                for a in args {
718                    a.effects(globals, out);
719                }
720            }
721            CoreKind::Let { value, body, .. } => {
722                value.effects(globals, out);
723                body.effects(globals, out);
724            }
725            CoreKind::If { cond, then, alt } => {
726                cond.effects(globals, out);
727                then.effects(globals, out);
728                alt.effects(globals, out);
729            }
730            CoreKind::Match { scrutinee, arms } => {
731                scrutinee.effects(globals, out);
732                for e in arms.iter().flat_map(|a| a.exprs()) {
733                    e.effects(globals, out);
734                }
735            }
736            CoreKind::Make { fields, .. } => {
737                for (_, f) in fields {
738                    f.effects(globals, out);
739                }
740            }
741            CoreKind::Field { base, .. } => base.effects(globals, out),
742            CoreKind::With { base, fields } => {
743                base.effects(globals, out);
744                for (_, f) in fields {
745                    f.effects(globals, out);
746                }
747            }
748            CoreKind::ListLit(xs) => {
749                for x in xs {
750                    x.effects(globals, out);
751                }
752            }
753            CoreKind::MapLit(kvs) => {
754                for (k, v) in kvs {
755                    k.effects(globals, out);
756                    v.effects(globals, out);
757                }
758            }
759        }
760    }
761
762    /// Set the tier on this node and everything under it.
763    pub fn place(&mut self, tier: Tier) {
764        self.tier = tier;
765        match &mut self.kind {
766            CoreKind::Lam { body, .. } => Arc::make_mut(body).place(tier),
767            CoreKind::App { func, args } => {
768                func.place(tier);
769                for a in args {
770                    a.place(tier);
771                }
772            }
773            CoreKind::Prim { args, .. } => {
774                for a in args {
775                    a.place(tier);
776                }
777            }
778            CoreKind::Let { value, body, .. } => {
779                value.place(tier);
780                body.place(tier);
781            }
782            CoreKind::If { cond, then, alt } => {
783                cond.place(tier);
784                then.place(tier);
785                alt.place(tier);
786            }
787            CoreKind::Match { scrutinee, arms } => {
788                scrutinee.place(tier);
789                for e in arms.iter_mut().flat_map(|a| a.exprs_mut()) {
790                    e.place(tier);
791                }
792            }
793            CoreKind::Make { fields, .. } => {
794                for (_, f) in fields {
795                    f.place(tier);
796                }
797            }
798            CoreKind::Field { base, .. } => base.place(tier),
799            CoreKind::With { base, fields } => {
800                base.place(tier);
801                for (_, f) in fields {
802                    f.place(tier);
803                }
804            }
805            CoreKind::ListLit(xs) => {
806                for x in xs {
807                    x.place(tier);
808                }
809            }
810            CoreKind::MapLit(kvs) => {
811                for (k, v) in kvs {
812                    k.place(tier);
813                    v.place(tier);
814                }
815            }
816            CoreKind::Const(_) | CoreKind::Var(_) | CoreKind::Global(_) => {}
817        }
818    }
819}
820
821// ---------------------------------------------------------------------------------------------
822// Values
823// ---------------------------------------------------------------------------------------------
824
825/// A runtime value.
826///
827/// `Map` and a record's fields are both *ordered* on purpose and for the same reason Phase 0 chose
828/// `BTreeMap`: iteration order is part of the rendered view, and replay must reproduce the *patch
829/// stream* bit for bit, not merely the set of values.
830///
831/// `Map` is a [`PMap`], not an `Arc<BTreeMap>`, because it is the fold's accumulator: an update
832/// must not copy it. See [`crate::pmap`] for why that structure and not another.
833#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
834pub enum Value {
835    Unit,
836    Bool(bool),
837    Int(i64),
838    /// A real, stored as an **order-preserving** key rather than as `f64::to_bits`, so that the
839    /// derived `Ord` is the numeric one.
840    ///
841    /// A total order is not optional here — a map key and a component of the state digest need one
842    /// — and `to_bits` supplies one that disagrees with arithmetic: `-1.0` has a larger bit pattern
843    /// than `1.0`, so `<` answered backwards for every negative number and `sort_by` sorted the
844    /// negatives in reverse. [`Value::float`] applies the standard monotone transform instead
845    /// (flip the sign bit for a positive, invert every bit for a negative), which makes the two
846    /// orders the same order. `docs/27` §27.8.
847    Float(u64),
848    /// Text, with the two facts a character-indexed language needs about it: how many characters
849    /// there are, and whether a character index is a byte index. [`Text`] is why.
850    Str(Arc<Text>),
851    /// A list, in one of [`crate::seq::Seq`]'s two layouts.
852    ///
853    /// Behind the `Arc` rather than in the `Value`, which is what keeps a `Value` 16 bytes: the
854    /// layout enum costs a word inside the allocation a list already had, and nothing that is not
855    /// a list pays for it.
856    List(Arc<Seq>),
857    Map(PMap<Value, Value>),
858    /// A model instance or a union variant — see [`Record`].
859    ///
860    /// Behind one pointer rather than inline, and that is a size decision rather than a style one:
861    /// the three fields inline made **every** `Value` 48 bytes, so a list of a million integers
862    /// carried 32 bytes of nothing per element and a call frame paid for the widest variant it did
863    /// not hold. One `Arc` makes a `Value` 16.
864    Data(Arc<Record>),
865    Html(Arc<Html>),
866    /// An attribute waiting to be attached to an element.
867    Attr(Arc<AttrValue>),
868    Closure(Arc<Closure>),
869}
870
871/// A model instance or a union variant. `variant` is `None` for a plain record.
872///
873/// Split out of [`Value::Data`] so that a `Value` is a discriminant and a pointer. Records are the
874/// widest thing the language has and the rarest thing in a hot loop, which is exactly the shape
875/// that should be behind an indirection.
876#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
877pub struct Record {
878    pub ty: Arc<str>,
879    pub variant: Option<Arc<str>>,
880    pub fields: Fields,
881}
882
883/// A record's fields, sorted by name.
884///
885/// This was a `BTreeMap`, and a record is the wrong size for one: three to eight entries, built
886/// once and read many times. A B-tree pays a node allocation and a pointer chase per level to buy
887/// an asymptotic advantage that never arrives at that size, and profiling `awfy/havlak.beck` put
888/// a fifth of the process inside its search, its insert and the `memcmp` underneath them.
889///
890/// Sorted by name and searched linearly: one allocation for the whole record, the names lie next
891/// to each other in cache, and `get` compares lengths before bytes because it wants equality
892/// rather than order. Iteration is in name order, so the value order, the state digest and the
893/// wire format ([`crate::repr`]) are all bit-for-bit what the `BTreeMap` gave — which is what
894/// makes this a representation change and not a semantic one.
895#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
896pub struct Fields(Vec<(Arc<str>, Value)>);
897
898impl Fields {
899    pub fn new() -> Fields {
900        Fields(Vec::new())
901    }
902
903    pub fn with_capacity(n: usize) -> Fields {
904        Fields(Vec::with_capacity(n))
905    }
906
907    pub fn get(&self, name: &str) -> Option<&Value> {
908        self.0
909            .iter()
910            .find(|(k, _)| same_name(k, name))
911            .map(|(_, v)| v)
912    }
913
914    /// Set `name`, keeping the order by name. Answers the value that was there.
915    ///
916    /// The search is by **equality** and not by order, which is the whole difference: `==` on two
917    /// `str`s compares their lengths first and reaches `memcmp` only for a pair that could match,
918    /// where a binary search has to order every probe it makes. A record has three to eight
919    /// fields, so a scan makes at most as many comparisons as a binary search and nearly all of
920    /// them are an integer test. Only a field that is genuinely new pays for the ordered insert,
921    /// and `with` — which is what calls this in a loop — never has one.
922    pub fn insert(&mut self, name: Arc<str>, value: Value) -> Option<Value> {
923        if let Some(slot) = self.0.iter_mut().find(|(k, _)| same_name(k, &name)) {
924            return Some(std::mem::replace(&mut slot.1, value));
925        }
926        let at = self
927            .0
928            .partition_point(|(k, _)| cmp_name(k, &name) == std::cmp::Ordering::Less);
929        self.0.insert(at, (name, value));
930        None
931    }
932
933    /// Build from fields in **any** order, sorting once.
934    ///
935    /// This is how a record literal is built, and it is a separate entry point from `insert` in a
936    /// loop because the two cost differently: `sort_unstable_by` on a handful of elements is an
937    /// insertion sort, which makes `n - 1` comparisons and moves nothing when the fields already
938    /// arrive in order — as a record literal's usually do.
939    pub fn from_pairs(mut pairs: Vec<(Arc<str>, Value)>) -> Fields {
940        pairs.sort_unstable_by(|(a, _), (b, _)| cmp_name(a, b));
941        pairs.dedup_by(|(a, _), (b, _)| same_name(a, b));
942        Fields(pairs)
943    }
944
945    /// Build from fields the caller has **already** put in order.
946    ///
947    /// The caller is [`crate::fields`], which decided the order once at compile time — a record
948    /// literal's field names are written in the source, so sorting them once per record built is
949    /// work with a known answer. Nothing else should use this: the order is the `Map` iteration,
950    /// the state digest and the patch stream, so getting it wrong is a wire-format bug rather than
951    /// a slow lookup.
952    pub fn from_sorted(pairs: Vec<(Arc<str>, Value)>) -> Fields {
953        debug_assert!(
954            pairs
955                .windows(2)
956                .all(|w| cmp_name(&w[0].0, &w[1].0) == std::cmp::Ordering::Less),
957            "from_sorted was given fields that are not sorted and distinct"
958        );
959        Fields(pairs)
960    }
961
962    pub fn len(&self) -> usize {
963        self.0.len()
964    }
965
966    pub fn is_empty(&self) -> bool {
967        self.0.is_empty()
968    }
969
970    pub fn iter(&self) -> std::slice::Iter<'_, (Arc<str>, Value)> {
971        self.0.iter()
972    }
973
974    pub fn values(&self) -> impl Iterator<Item = &Value> {
975        self.0.iter().map(|(_, v)| v)
976    }
977}
978
979impl FromIterator<(Arc<str>, Value)> for Fields {
980    fn from_iter<I: IntoIterator<Item = (Arc<str>, Value)>>(it: I) -> Fields {
981        Fields::from_pairs(it.into_iter().collect())
982    }
983}
984
985impl<'a> IntoIterator for &'a Fields {
986    type Item = &'a (Arc<str>, Value);
987    type IntoIter = std::slice::Iter<'a, (Arc<str>, Value)>;
988    fn into_iter(self) -> Self::IntoIter {
989        self.0.iter()
990    }
991}
992
993/// A string that knows its own length in **characters**, and whether it is ASCII.
994///
995/// Beck indexes text by character everywhere or nowhere (`docs/46` §46.6), and a `String` counts
996/// bytes — so `str_len` used to be `chars().count()` and `str_slice` used to `skip()` its way to
997/// the start. Both are `O(n)` in the *string* rather than in the answer, which makes the ordinary
998/// way to walk one — `while i < str_len(s)` reading `str_slice(s, i, 1)` — quadratic. Measured at
999/// ×2.7 per doubling in [`70`](../../../../../docs/70-the-evaluator-gets-fast-report.md) §70.2.
1000///
1001/// Both facts are computed once, when the string is built, which is work the construction was
1002/// already doing: it had to copy the bytes, and `is_ascii` is a scan of the same bytes that answers
1003/// `chars` for free when it is true. Everything downstream is then `O(1)` or `O(answer)`.
1004///
1005/// The `String` rather than a `Box<str>` is the other half: it has spare capacity, so `a + b` can
1006/// push into `a` when the last-use analysis proves nobody else holds it ([`crate::liveness`]).
1007#[derive(Clone, Debug)]
1008pub struct Text {
1009    bytes: String,
1010    /// Characters, not bytes. `str_len`'s answer.
1011    chars: usize,
1012    /// Every character is one byte, so character index == byte index and a slice is a byte range.
1013    ascii: bool,
1014    /// For text that is *not* ASCII: the byte offset of every 32nd character.
1015    ///
1016    /// Chunked rather than one entry per character, because the point is to stop paying `O(n)` per
1017    /// slice and a jump to the nearest 32 does that for a thirty-second of the memory — `n / 8`
1018    /// bytes, and only for text that needs it, since an ASCII character index *is* a byte index.
1019    ///
1020    /// Built eagerly, in the pass that counts the characters, rather than cached on first use. A
1021    /// lazy one would be interior mutability inside a `Value`, and a `Value` is a `Map` key: the
1022    /// cache would be invisible to `Ord` and `Hash` and therefore harmless, but "harmless interior
1023    /// mutability in a key" is a sentence every reader and `clippy::mutable_key_type` would have to
1024    /// re-check. One pass and an eighth of the bytes is the cheaper answer.
1025    index: Box<[u32]>,
1026}
1027
1028/// How many characters one entry of [`Text`]'s index skips.
1029const INDEX_STRIDE: usize = 32;
1030
1031impl Text {
1032    pub fn new(bytes: String) -> Text {
1033        let ascii = bytes.is_ascii();
1034        // ASCII answers both questions from the scan `is_ascii` already did, and needs no index at
1035        // all. Anything else pays one more pass — once, here — rather than paying it on every
1036        // `str_len` and every `str_slice`.
1037        if ascii {
1038            let chars = bytes.len();
1039            return Text {
1040                bytes,
1041                chars,
1042                ascii,
1043                index: Box::new([]),
1044            };
1045        }
1046        let mut chars = 0usize;
1047        let mut index = Vec::with_capacity(bytes.len() / INDEX_STRIDE + 1);
1048        for (at, _) in bytes.char_indices() {
1049            if chars.is_multiple_of(INDEX_STRIDE) {
1050                index.push(at as u32);
1051            }
1052            chars += 1;
1053        }
1054        Text {
1055            bytes,
1056            chars,
1057            ascii,
1058            index: index.into_boxed_slice(),
1059        }
1060    }
1061
1062    /// The byte offset of character `i`, in constant time for ASCII text and in at most
1063    /// one index stride for anything else. Past the end it answers the end, which is what a
1064    /// clamping slice wants.
1065    pub fn byte_offset(&self, i: usize) -> usize {
1066        if self.ascii {
1067            return i.min(self.bytes.len());
1068        }
1069        if i >= self.chars {
1070            return self.bytes.len();
1071        }
1072        let chunk = i / INDEX_STRIDE;
1073        let from = self.index.get(chunk).copied().unwrap_or(0) as usize;
1074        match self.bytes[from..]
1075            .char_indices()
1076            .nth(i - chunk * INDEX_STRIDE)
1077        {
1078            Some((at, _)) => from + at,
1079            None => self.bytes.len(),
1080        }
1081    }
1082
1083    /// The length in characters, in constant time.
1084    pub fn chars_len(&self) -> usize {
1085        self.chars
1086    }
1087
1088    pub fn as_str(&self) -> &str {
1089        &self.bytes
1090    }
1091
1092    /// Append, consuming: the caller has established sole ownership, so this is a `push_str` and
1093    /// not a copy of both sides.
1094    pub fn appended(mut self, other: &str) -> Text {
1095        let before = self.chars;
1096        let start = self.bytes.len();
1097        let other_ascii = other.is_ascii();
1098        self.bytes.push_str(other);
1099
1100        // Everything here is `O(other)`, never `O(self)`, which is the property the whole change
1101        // exists for: appending in a loop has to stay linear in the total.
1102        if self.ascii && other_ascii {
1103            self.chars = self.bytes.len();
1104            return self;
1105        }
1106        if self.ascii {
1107            // The left half was ASCII, so its character numbers *are* its byte offsets and its
1108            // share of the index can be written down rather than walked for.
1109            let mut index = Vec::with_capacity(self.bytes.len() / INDEX_STRIDE + 1);
1110            let mut at = 0;
1111            while at < before {
1112                index.push(at as u32);
1113                at += INDEX_STRIDE;
1114            }
1115            self.index = index.into_boxed_slice();
1116            self.ascii = false;
1117        }
1118        let mut index = std::mem::take(&mut self.index).into_vec();
1119        let mut chars = before;
1120        for (at, _) in self.bytes[start..].char_indices() {
1121            if chars.is_multiple_of(INDEX_STRIDE) {
1122                index.push((start + at) as u32);
1123            }
1124            chars += 1;
1125        }
1126        self.chars = chars;
1127        self.index = index.into_boxed_slice();
1128        self
1129    }
1130}
1131
1132impl std::ops::Deref for Text {
1133    type Target = str;
1134    fn deref(&self) -> &str {
1135        &self.bytes
1136    }
1137}
1138
1139impl From<&str> for Text {
1140    fn from(s: &str) -> Text {
1141        Text::new(s.to_string())
1142    }
1143}
1144
1145/// Text compares, orders and hashes **as its characters**, so that adding the two cached facts
1146/// cannot change what a program means: a `Map` keyed by strings keeps its order, and so does the
1147/// state digest that a replay has to reproduce.
1148impl PartialEq for Text {
1149    fn eq(&self, other: &Self) -> bool {
1150        self.bytes == other.bytes
1151    }
1152}
1153impl Eq for Text {}
1154impl PartialOrd for Text {
1155    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1156        Some(self.cmp(other))
1157    }
1158}
1159impl Ord for Text {
1160    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1161        self.bytes.cmp(&other.bytes)
1162    }
1163}
1164impl std::hash::Hash for Text {
1165    fn hash<H: std::hash::Hasher>(&self, h: &mut H) {
1166        self.bytes.hash(h)
1167    }
1168}
1169
1170#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
1171pub enum AttrValue {
1172    Plain(Arc<str>, Arc<str>),
1173    On(Arc<str>, Value),
1174    Key(Arc<str>),
1175}
1176
1177#[derive(Debug)]
1178pub struct Closure {
1179    pub params: Arc<[VarId]>,
1180    /// The same `Arc` the [`CoreKind::Lam`] node holds, so building a closure is a refcount bump
1181    /// rather than a copy of the code.
1182    pub body: Arc<Core>,
1183    /// Behind an `Arc` so that *calling* the closure clones a pointer rather than the environment.
1184    pub env: Arc<Env>,
1185    /// How many bindings the body makes, copied off the [`CoreKind::Lam`] node so that a call can
1186    /// size one frame for the parameters and all of them. [`crate::frames`] is what counts it.
1187    pub locals: u32,
1188}
1189
1190impl PartialEq for Closure {
1191    /// Closures compare by identity of their code position. Two closures are never equal unless
1192    /// they came from the same lambda with the same captured frame, which is all any program
1193    /// should rely on.
1194    fn eq(&self, other: &Self) -> bool {
1195        self.params == other.params
1196            && self.body.span == other.body.span
1197            && self.env.frame == other.env.frame
1198    }
1199}
1200impl Eq for Closure {}
1201impl PartialOrd for Closure {
1202    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1203        Some(self.cmp(other))
1204    }
1205}
1206impl Ord for Closure {
1207    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1208        self.params
1209            .cmp(&other.params)
1210            .then_with(|| self.body.span.start.cmp(&other.body.span.start))
1211    }
1212}
1213
1214/// A lexical environment: a persistent chain of frames, so a closure can capture cheaply.
1215#[derive(Clone, Debug, Default, PartialEq, Eq)]
1216pub struct Env {
1217    /// `Arc<[T]>` rather than `Arc<Vec<T>>`: the second is two allocations and two hops to reach a
1218    /// binding.
1219    ///
1220    /// A call sizes this for the parameters **and** for every binding the body will make, so a
1221    /// `let` writes into a slot that is already there rather than allocating a scope of its own.
1222    /// The unwritten tail is filled with [`TOMBSTONE`], which no variable is named.
1223    frame: Arc<[(VarId, Value)]>,
1224    /// How much of `frame` holds a binding. Everything from here up is reserved and empty.
1225    used: u32,
1226    parent: Option<Arc<Env>>,
1227}
1228
1229impl Env {
1230    pub fn new() -> Env {
1231        Env::default()
1232    }
1233
1234    pub fn extend(&self, bindings: Vec<(VarId, Value)>) -> Env {
1235        Env {
1236            used: bindings.len() as u32,
1237            frame: bindings.into(),
1238            parent: Some(Arc::new(self.clone())),
1239        }
1240    }
1241
1242    /// The frame a call runs in: the parameters, then `locals` reserved slots for the bindings the
1243    /// body is going to make.
1244    ///
1245    /// One allocation. `Map<Range, _>` has a length the compiler can trust, so collecting it into
1246    /// an `Arc<[_]>` sizes the allocation once — which is why the parameters and the reserved tail
1247    /// are produced by one iterator rather than a vector that is then converted.
1248    pub fn call_frame(
1249        parent: &Arc<Env>,
1250        params: &[VarId],
1251        args: impl Iterator<Item = Value>,
1252        locals: u32,
1253    ) -> Env {
1254        let n = params.len();
1255        let mut given = args;
1256        let frame = (0..n + locals as usize)
1257            .map(|i| match given.next() {
1258                Some(v) => (params[i], v),
1259                None => (TOMBSTONE, Value::Unit),
1260            })
1261            .collect();
1262        Env {
1263            frame,
1264            used: n as u32,
1265            parent: Some(Arc::clone(parent)),
1266        }
1267    }
1268
1269    /// Bind `bindings` into this frame's reserved tail, if there is room for all of them and
1270    /// nobody else is holding the frame.
1271    ///
1272    /// Answers whether it did. `false` means the caller must fall back to [`Env::extend`] and
1273    /// chain a scope — which happens when a closure has captured this environment (its clone holds
1274    /// the frame, so `Arc::get_mut` refuses), when the reservation was too small, or when the
1275    /// program was built by something that never ran the reservation pass at all.
1276    ///
1277    /// The safety argument is the refusal: a closure that captured this environment can see the
1278    /// slots this would write, and `Arc::get_mut` is what proves that has not happened. Every
1279    /// binding gets a slot of its own, so nothing a closure captured is ever overwritten.
1280    pub fn put(&mut self, bindings: &mut Vec<(VarId, Value)>) -> bool {
1281        let at = self.used as usize;
1282        let n = bindings.len();
1283        if at + n > self.frame.len() {
1284            return false;
1285        }
1286        let Some(frame) = Arc::get_mut(&mut self.frame) else {
1287            return false;
1288        };
1289        for (i, b) in bindings.drain(..).enumerate() {
1290            frame[at + i] = b;
1291        }
1292        self.used = (at + n) as u32;
1293        true
1294    }
1295
1296    /// [`Env::put`] for a single binding, which is what a `let` is — and a `let` is much the most
1297    /// common of the two, so it does not build a vector to hand over. Answers the value back when
1298    /// there is no room for it.
1299    pub fn put_one(&mut self, var: VarId, value: Value) -> Result<(), Value> {
1300        let at = self.used as usize;
1301        if at >= self.frame.len() {
1302            return Err(value);
1303        }
1304        match Arc::get_mut(&mut self.frame) {
1305            Some(frame) => {
1306                frame[at] = (var, value);
1307                self.used += 1;
1308                Ok(())
1309            }
1310            None => Err(value),
1311        }
1312    }
1313
1314    /// The part of the frame that holds bindings, without the reserved tail.
1315    #[inline]
1316    fn bound(&self) -> &[(VarId, Value)] {
1317        &self.frame[..self.used as usize]
1318    }
1319
1320    /// An environment with nothing in it, behind an `Arc`, shared by every top-level definition.
1321    pub fn empty_shared() -> Arc<Env> {
1322        Arc::new(Env::new())
1323    }
1324
1325    pub fn get(&self, v: VarId) -> Option<&Value> {
1326        let mut env = self;
1327        loop {
1328            if let Some((_, value)) = env.bound().iter().rev().find(|(id, _)| *id == v) {
1329                return Some(value);
1330            }
1331            match &env.parent {
1332                Some(p) => env = p,
1333                None => return None,
1334            }
1335        }
1336    }
1337
1338    /// Read `v`, and **move** it out of the frame when three things hold: the caller says no later
1339    /// evaluation reads it, this environment is the only holder of the frame it lives in, and the
1340    /// value is one whose copy costs something.
1341    ///
1342    /// The third condition is not an optimisation of an optimisation — it is what keeps the other
1343    /// two from costing more than they save. Moving is strictly more work than cloning at the point
1344    /// of the read: a clone of an `Int` is a copy of eight bytes and a clone of a container is one
1345    /// atomic increment, where a move has to find the slot, prove the frame is unshared and empty
1346    /// it. It pays only when somebody downstream can then *use* the sole ownership — which today is
1347    /// `list_append` pushing in place and `with` rebuilding a record's fields — and measuring it
1348    /// without this condition showed every benchmark in the tree 6–13% slower, because the reads
1349    /// that dominate a real program are of `Int`s and nothing was gained by moving one
1350    /// ([`docs/46`](../../../../../docs/46-standard-library-report.md) §46.14).
1351    ///
1352    /// The caller must have established that no later evaluation reads `v` — [`crate::liveness`]
1353    /// is what establishes it, and `last_use` is the flag. What this adds is the second half of the
1354    /// safety argument: a frame is emptied only when nothing else holds it, so an environment
1355    /// captured by a closure or shared with an inner scope is read from rather than emptied, and a
1356    /// caller that is wrong about liveness gets an unbound-variable error rather than somebody
1357    /// else's missing binding.
1358    pub fn read(&mut self, v: VarId, may_move: bool) -> Option<Value> {
1359        // The overwhelmingly common read is not a last use, and it needs none of the machinery
1360        // below: no scope on the way to the binding has to be proved unshared, because nothing is
1361        // going to be taken out of one. That proof costs **two atomic loads per scope level**
1362        // (`strong_count` and `weak_count`) plus an `Arc::get_mut`, and it was being paid on every
1363        // variable a program reads. `get` is a plain walk.
1364        if !may_move {
1365            return self.get(v).cloned();
1366        }
1367        let mut env = self;
1368        loop {
1369            if let Some(i) = env.bound().iter().rposition(|(id, _)| *id == v) {
1370                if may_move && worth_moving(&env.frame[i].1) {
1371                    if let Some(frame) = Arc::get_mut(&mut env.frame) {
1372                        // Tombstoned rather than removed: `Vec::remove` shifts every binding above
1373                        // it, and this runs on the hottest path there is. The slot keeps its place
1374                        // under a name no variable has, so a later read of `v` misses it and says
1375                        // so instead of finding a neighbour.
1376                        frame[i].0 = TOMBSTONE;
1377                        return Some(std::mem::replace(&mut frame[i].1, Value::Unit));
1378                    }
1379                }
1380                return Some(env.frame[i].1.clone());
1381            }
1382            let shared_parent = env
1383                .parent
1384                .as_ref()
1385                .is_some_and(|p| Arc::strong_count(p) > 1 || Arc::weak_count(p) > 0);
1386            if shared_parent {
1387                return env.parent.as_ref().and_then(|p| p.get(v)).cloned();
1388            }
1389            match env.parent.as_mut() {
1390                Some(p) => match Arc::get_mut(p) {
1391                    Some(p) => env = p,
1392                    None => return None,
1393                },
1394                None => return None,
1395            }
1396        }
1397    }
1398}
1399
1400/// Every subexpression of this one.
1401///
1402/// The read-only twin of `children_mut`, and it exists for the same reason: a pass that walks
1403/// the whole tree should not restate the shape of `CoreKind`, because the day a variant gains a
1404/// child every hand-written walk is silently incomplete ([`docs/90`](../../../../../docs/90-pattern-matching-report.md)
1405/// §90.5 is that failure, at fourteen sites).
1406pub fn children(c: &Core) -> Vec<&Core> {
1407    match &c.kind {
1408        CoreKind::Const(_) | CoreKind::Var(_) | CoreKind::Global(_) => Vec::new(),
1409        CoreKind::Lam { body, .. } => vec![body],
1410        CoreKind::App { func, args } => std::iter::once(&**func).chain(args).collect(),
1411        CoreKind::Let { value, body, .. } => vec![value, body],
1412        CoreKind::If { cond, then, alt } => vec![cond, then, alt],
1413        CoreKind::Match { scrutinee, arms } => std::iter::once(&**scrutinee)
1414            .chain(arms.iter().flat_map(|a| a.exprs()))
1415            .collect(),
1416        CoreKind::Prim { args, .. } => args.iter().collect(),
1417        CoreKind::Make { fields, .. } => fields.iter().map(|(_, f)| f).collect(),
1418        CoreKind::Field { base, .. } => vec![base],
1419        CoreKind::With { base, fields } => std::iter::once(&**base)
1420            .chain(fields.iter().map(|(_, f)| f))
1421            .collect(),
1422        CoreKind::ListLit(items) => items.iter().collect(),
1423        CoreKind::MapLit(kvs) => kvs.iter().flat_map(|(k, v)| [k, v]).collect(),
1424    }
1425}
1426
1427/// Every subexpression of this one, to be rewritten in place.
1428///
1429/// The walk two passes over the finished program share — [`crate::frames`] and [`crate::fields`].
1430/// A lambda's body is behind an `Arc` because a closure shares it rather than copying it
1431/// (`docs/70`), so reaching into one is a `make_mut`: it runs once, on a program nothing else
1432/// holds yet, so nothing is actually cloned.
1433pub(crate) fn children_mut(c: &mut Core) -> Vec<&mut Core> {
1434    match &mut c.kind {
1435        CoreKind::Const(_) | CoreKind::Var(_) | CoreKind::Global(_) => Vec::new(),
1436        CoreKind::Lam { body, .. } => vec![Arc::make_mut(body)],
1437        CoreKind::App { func, args } => std::iter::once(&mut **func).chain(args).collect(),
1438        CoreKind::Let { value, body, .. } => vec![&mut **value, &mut **body],
1439        CoreKind::If { cond, then, alt } => vec![&mut **cond, &mut **then, &mut **alt],
1440        CoreKind::Match { scrutinee, arms } => std::iter::once(&mut **scrutinee)
1441            .chain(arms.iter_mut().flat_map(|a| a.exprs_mut()))
1442            .collect(),
1443        CoreKind::Prim { args, .. } => args.iter_mut().collect(),
1444        CoreKind::Make { fields, .. } => fields.iter_mut().map(|(_, f)| f).collect(),
1445        CoreKind::Field { base, .. } => vec![&mut **base],
1446        CoreKind::With { base, fields } => std::iter::once(&mut **base)
1447            .chain(fields.iter_mut().map(|(_, f)| f))
1448            .collect(),
1449        CoreKind::ListLit(items) => items.iter_mut().collect(),
1450        CoreKind::MapLit(kvs) => kvs.iter_mut().flat_map(|(k, v)| [k, v]).collect(),
1451    }
1452}
1453
1454/// Are these the same field name?
1455///
1456/// Length, then first byte, then the rest. `str`'s own `==` checks the length and hands the bytes
1457/// to `memcmp`, and a `memcmp` call is dear next to what it decides here: field names are short,
1458/// there are three to eight of them in a record, and two that share a length almost never share a
1459/// first letter. Profiling `awfy/richards.beck` put 6% of the process inside `memcmp`, nearly all
1460/// of it deciding between `kind` and `link`.
1461#[inline]
1462fn same_name(a: &str, b: &str) -> bool {
1463    let (x, y) = (a.as_bytes(), b.as_bytes());
1464    x.len() == y.len() && (x.is_empty() || x[0] == y[0]) && x == y
1465}
1466
1467/// The order a record keeps its fields in.
1468///
1469/// [`Fields`] is sorted by name and that order is load-bearing far outside this module — it is a
1470/// `Map`'s iteration, the state digest, the patch stream and the order `Ord` compares two records
1471/// in. A backend that lays a record out in memory has to lay it out in *this* order or its `<`
1472/// disagrees with the evaluator's, so the order is published rather than reimplemented.
1473pub fn field_order(a: &str, b: &str) -> std::cmp::Ordering {
1474    cmp_name(a, b)
1475}
1476
1477/// Order two field names, deciding on the first byte where it can.
1478///
1479/// `<[u8]>::cmp` calls `memcmp` over the common prefix before it looks at the lengths, so it pays
1480/// a call to distinguish `id` from `kind`. Sorting a record's fields is the other half of what
1481/// `same_name` documents.
1482#[inline]
1483pub(crate) fn cmp_name(a: &str, b: &str) -> std::cmp::Ordering {
1484    let (x, y) = (a.as_bytes(), b.as_bytes());
1485    match (x.first(), y.first()) {
1486        (Some(p), Some(q)) if p != q => p.cmp(q),
1487        _ => x.cmp(y),
1488    }
1489}
1490
1491/// The name a moved-out binding takes, which no variable has: `VarId`s are handed out from zero by
1492/// the checker, and a program with four billion of them has lost to `MAX_NESTING` long before.
1493const TOMBSTONE: VarId = VarId::MAX;
1494
1495/// Whether moving this value out of a frame can save anything downstream.
1496///
1497/// A `List` can be pushed into by `list_append`, a `Str` by `+`, and a record's fields can be
1498/// rebuilt in place by `with` — each only when nobody else holds them. Everything else is either a copy of a few bytes
1499/// or an atomic increment, and moving it costs more than it saves — `Env::read` is the measurement.
1500fn worth_moving(v: &Value) -> bool {
1501    matches!(v, Value::List(_) | Value::Str(_) | Value::Data(_))
1502}
1503
1504/// The monotone `f64` → `u64` transform: for a non-negative float flip the sign bit, for a
1505/// negative one invert every bit. `a < b` as reals iff `order_key(a) < order_key(b)` as integers,
1506/// with `-inf` at the bottom and NaN above `+inf`.
1507const SIGN: u64 = 1 << 63;
1508
1509fn order_key(f: f64) -> u64 {
1510    let bits = f.to_bits();
1511    if bits & SIGN != 0 {
1512        !bits
1513    } else {
1514        bits ^ SIGN
1515    }
1516}
1517
1518fn from_order_key(key: u64) -> f64 {
1519    f64::from_bits(if key & SIGN != 0 { key ^ SIGN } else { !key })
1520}
1521
1522impl Value {
1523    /// Make a real, canonicalising the two IEEE values that would otherwise break `Eq`.
1524    ///
1525    /// `-0.0` becomes `0.0` and every NaN becomes one NaN, because [`Value`] is `Eq` and `Ord` and
1526    /// a fold's accumulator is compared, hashed and used as a map key. IEEE 754 says `NaN != NaN`
1527    /// and `-0.0 == 0.0`; both are irreconcilable with a total order, and the total order is the
1528    /// one §3.7 needs. So Beck's `==` on reals is *structural*, and `docs/27` §27.8 says so where
1529    /// somebody porting numeric code will read it.
1530    pub fn float(f: f64) -> Value {
1531        let f = if f.is_nan() {
1532            f64::NAN
1533        } else if f == 0.0 {
1534            0.0
1535        } else {
1536            f
1537        };
1538        Value::Float(order_key(f))
1539    }
1540
1541    pub fn as_f64(&self) -> Option<f64> {
1542        match self {
1543            Value::Float(key) => Some(from_order_key(*key)),
1544            _ => None,
1545        }
1546    }
1547
1548    pub fn str_(s: impl AsRef<str>) -> Value {
1549        Value::Str(Arc::new(Text::from(s.as_ref())))
1550    }
1551
1552    /// The same from a `String` that is already owned, which is most of the string primitives:
1553    /// they build one and would otherwise copy it again on the way in.
1554    pub fn text(s: String) -> Value {
1555        Value::Str(Arc::new(Text::new(s)))
1556    }
1557
1558    pub fn as_str(&self) -> Option<&str> {
1559        match self {
1560            Value::Str(s) => Some(s),
1561            _ => None,
1562        }
1563    }
1564
1565    pub fn as_int(&self) -> Option<i64> {
1566        match self {
1567            Value::Int(i) => Some(*i),
1568            _ => None,
1569        }
1570    }
1571
1572    pub fn as_bool(&self) -> Option<bool> {
1573        match self {
1574            Value::Bool(b) => Some(*b),
1575            _ => None,
1576        }
1577    }
1578
1579    /// A list of these elements, in whatever layout they fit ([`crate::seq::Seq::pack`]).
1580    ///
1581    /// The one constructor, so that the choice of layout is made in one place rather than at
1582    /// seventy call sites — and so that turning it off turns it off everywhere.
1583    pub fn list(values: Vec<Value>) -> Value {
1584        Value::List(Arc::new(Seq::pack(values)))
1585    }
1586
1587    /// A list of elements already in a layout — what a primitive that *preserved* one hands back.
1588    ///
1589    /// [`Value::list`] chooses a layout; this keeps the one the caller has, which is the difference
1590    /// between `list_append` on a column costing a `push` and costing a re-examination of the whole
1591    /// list.
1592    pub fn of_seq(seq: Seq) -> Value {
1593        Value::List(Arc::new(seq))
1594    }
1595
1596    pub fn as_list(&self) -> Option<&Seq> {
1597        match self {
1598            Value::List(xs) => Some(xs),
1599            _ => None,
1600        }
1601    }
1602
1603    pub fn as_map(&self) -> Option<&PMap<Value, Value>> {
1604        match self {
1605            Value::Map(m) => Some(m),
1606            _ => None,
1607        }
1608    }
1609
1610    pub fn as_html(&self) -> Option<&Html> {
1611        match self {
1612            Value::Html(h) => Some(h),
1613            _ => None,
1614        }
1615    }
1616
1617    /// Build a record or a union variant. The one constructor, so that the `Arc` and the map are
1618    /// allocated in one place rather than at every call site.
1619    pub fn data(ty: impl Into<Arc<str>>, variant: Option<Arc<str>>, fields: Fields) -> Value {
1620        Value::Data(Arc::new(Record {
1621            ty: ty.into(),
1622            variant,
1623            fields,
1624        }))
1625    }
1626
1627    /// The same from a list of pairs, which is how most call sites have them.
1628    pub fn record<const N: usize>(
1629        ty: impl Into<Arc<str>>,
1630        variant: Option<&str>,
1631        fields: [(&str, Value); N],
1632    ) -> Value {
1633        Value::data(
1634            ty,
1635            variant.map(Arc::from),
1636            fields.into_iter().map(|(k, v)| (Arc::from(k), v)).collect(),
1637        )
1638    }
1639
1640    pub fn field(&self, name: &str) -> Option<&Value> {
1641        match self {
1642            Value::Data(d) => d.fields.get(name),
1643            _ => None,
1644        }
1645    }
1646
1647    pub fn variant(&self) -> Option<&str> {
1648        match self {
1649            Value::Data(d) => d.variant.as_deref(),
1650            _ => None,
1651        }
1652    }
1653
1654    pub fn some(v: Value) -> Value {
1655        Value::record(Ty::OPTION, Some("Some"), [("value", v)])
1656    }
1657
1658    pub fn none() -> Value {
1659        Value::record(Ty::OPTION, Some("None"), [])
1660    }
1661
1662    pub fn ok(v: Value) -> Value {
1663        Value::record(Ty::RESULT, Some("Ok"), [("value", v)])
1664    }
1665
1666    pub fn err(v: Value) -> Value {
1667        Value::record(Ty::RESULT, Some("Err"), [("error", v)])
1668    }
1669
1670    /// How `str(x)` renders a value, and how a value becomes a `Map` key's printed form.
1671    pub fn display(&self) -> String {
1672        match self {
1673            Value::Unit => "unit".into(),
1674            Value::Bool(b) => b.to_string(),
1675            Value::Int(i) => i.to_string(),
1676            Value::Float(_) => format!("{}", self.as_f64().unwrap_or(0.0)),
1677            Value::Str(s) => s.to_string(),
1678            Value::List(xs) => {
1679                let mut parts: Vec<String> = Vec::with_capacity(xs.len());
1680                xs.for_each(|x| parts.push(x.display()));
1681                format!("[{}]", parts.join(", "))
1682            }
1683            Value::Map(m) => {
1684                let parts: Vec<String> = m
1685                    .iter()
1686                    .map(|(k, v)| format!("{}: {}", k.display(), v.display()))
1687                    .collect();
1688                format!("{{{}}}", parts.join(", "))
1689            }
1690            Value::Data(d) => {
1691                // A newtype wrapping one field prints as that field — `Id(uuid)` reads as the uuid,
1692                // which is what a key attribute and a rendered list want.
1693                if d.variant.is_none() && d.fields.len() == 1 {
1694                    if let Some(v) = d.fields.values().next() {
1695                        return v.display();
1696                    }
1697                }
1698                let name = d.variant.as_deref().unwrap_or(&d.ty);
1699                if d.fields.is_empty() {
1700                    return name.to_string();
1701                }
1702                let parts: Vec<String> = d
1703                    .fields
1704                    .iter()
1705                    .map(|(k, v)| format!("{k}: {}", v.display()))
1706                    .collect();
1707                format!("{name}{{{}}}", parts.join(", "))
1708            }
1709            Value::Html(h) => h.render(),
1710            Value::Attr(_) => "<attr>".into(),
1711            Value::Closure(_) => "<fn>".into(),
1712        }
1713    }
1714
1715    /// The wire form, used for command payloads carried by handlers and for the log.
1716    pub fn to_json(&self) -> serde_json::Value {
1717        use serde_json::{Map as JMap, Value as J};
1718        match self {
1719            Value::Unit => J::Null,
1720            Value::Bool(b) => J::Bool(*b),
1721            Value::Int(i) => J::Number((*i).into()),
1722            Value::Float(_) => serde_json::Number::from_f64(self.as_f64().unwrap_or(0.0))
1723                .map(J::Number)
1724                .unwrap_or(J::Null),
1725            Value::Str(s) => J::String(s.to_string()),
1726            Value::List(xs) => {
1727                let mut out = Vec::with_capacity(xs.len());
1728                xs.for_each(|x| out.push(x.to_json()));
1729                J::Array(out)
1730            }
1731            Value::Map(m) => {
1732                let mut obj = JMap::new();
1733                for (k, v) in m.iter() {
1734                    obj.insert(k.display(), v.to_json());
1735                }
1736                J::Object(obj)
1737            }
1738            Value::Data(d) => {
1739                if d.variant.is_none() && d.fields.len() == 1 {
1740                    if let Some(v) = d.fields.values().next() {
1741                        return v.to_json();
1742                    }
1743                }
1744                let mut obj = JMap::new();
1745                if let Some(v) = &d.variant {
1746                    obj.insert("c".into(), J::String(v.to_string()));
1747                }
1748                for (k, v) in d.fields.iter() {
1749                    obj.insert(k.to_string(), v.to_json());
1750                }
1751                J::Object(obj)
1752            }
1753            Value::Html(h) => h.to_wire(),
1754            Value::Attr(_) | Value::Closure(_) => J::Null,
1755        }
1756    }
1757}
1758
1759impl fmt::Display for Value {
1760    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1761        f.write_str(&self.display())
1762    }
1763}
1764
1765/// A value that cannot be written to a log.
1766///
1767/// [`Value`] has four consumers with different requirements — the evaluator, the log, the wire, and
1768/// the digest — and three of its variants exist only for the first. Encoding one of those is not a
1769/// value to be lowered; it is a program that should not have compiled.
1770#[derive(Clone, Debug, PartialEq, Eq)]
1771pub struct NotStorable {
1772    /// Which variant, by name.
1773    pub kind: &'static str,
1774}
1775
1776impl fmt::Display for NotStorable {
1777    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1778        write!(
1779            f,
1780            "a {} cannot be written to the log: it is {}, not data",
1781            self.kind,
1782            match self.kind {
1783                "closure" => "code",
1784                "attribute" => "part of a view",
1785                _ => "a view",
1786            }
1787        )
1788    }
1789}
1790
1791impl std::error::Error for NotStorable {}
1792
1793/// A lossless encoding of a [`Value`], for the log and for snapshots.
1794///
1795/// [`Value::to_json`] is the *wire* form: it drops the type name of a record and unwraps a
1796/// newtype, because that is what a browser wants. The log needs the opposite — a record that can
1797/// be read back as exactly the value that was written, because replay compares digests. Hence two
1798/// encodings, and a test that says why.
1799///
1800/// # Why this returns a `Result`
1801///
1802/// It used to encode `Html`, `Attr` and `Closure` as `unit` on the grounds that "neither can appear
1803/// in a log". That grounds was an assumption, not a check: nothing stops a program declaring
1804/// `model State: cached: Html`, and the encoding would then write `unit` into the *durable* path,
1805/// silently, and replay would rebuild a different state. A system whose correctness argument is
1806/// "replay is exact" cannot have a lossy branch in the function that makes the log.
1807///
1808/// Until placement can prove such a type never reaches `durable` (Phase 2's effect rows), refusing
1809/// at the boundary is the honest position: the append fails, the process aborts by the same rule as
1810/// any other failed append (§18.5 item 6), and nothing unreadable is ever committed.
1811pub fn value_to_repr(v: &Value) -> Result<serde_json::Value, NotStorable> {
1812    use serde_json::{json, Map as JMap, Value as J};
1813    Ok(match v {
1814        Value::Unit => json!({"$": "unit"}),
1815        Value::Bool(b) => json!({"$": "bool", "v": b}),
1816        Value::Int(i) => json!({"$": "int", "v": i}),
1817        Value::Float(bits) => json!({"$": "float", "v": bits.to_string()}),
1818        Value::Str(s) => json!({"$": "str", "v": s.as_str()}),
1819        Value::List(xs) => {
1820            let mut items = Vec::with_capacity(xs.len());
1821            xs.try_for_each(|x| value_to_repr(x).map(|v| items.push(v)))?;
1822            json!({"$": "list", "v": items})
1823        }
1824        Value::Map(m) => {
1825            let mut pairs = Vec::with_capacity(m.len());
1826            for (k, val) in m.iter() {
1827                pairs.push(json!([value_to_repr(k)?, value_to_repr(val)?]));
1828            }
1829            json!({"$": "map", "v": pairs})
1830        }
1831        Value::Data(d) => {
1832            let mut f = JMap::new();
1833            for (k, val) in d.fields.iter() {
1834                f.insert(k.to_string(), value_to_repr(val)?);
1835            }
1836            json!({
1837                "$": "data",
1838                "t": d.ty.as_ref(),
1839                "c": d.variant.as_deref(),
1840                "f": J::Object(f)
1841            })
1842        }
1843        Value::Html(_) => return Err(NotStorable { kind: "view" }),
1844        Value::Attr(_) => return Err(NotStorable { kind: "attribute" }),
1845        Value::Closure(_) => return Err(NotStorable { kind: "closure" }),
1846    })
1847}
1848
1849pub fn value_from_repr(j: &serde_json::Value) -> Option<Value> {
1850    let tag = j.get("$")?.as_str()?;
1851    Some(match tag {
1852        "unit" => Value::Unit,
1853        "bool" => Value::Bool(j.get("v")?.as_bool()?),
1854        "int" => Value::Int(j.get("v")?.as_i64()?),
1855        "float" => Value::Float(j.get("v")?.as_str()?.parse().ok()?),
1856        "str" => Value::str_(j.get("v")?.as_str()?),
1857        "list" => Value::list(
1858            j.get("v")?
1859                .as_array()?
1860                .iter()
1861                .map(value_from_repr)
1862                .collect::<Option<Vec<_>>>()?,
1863        ),
1864        "map" => {
1865            let mut m = PMap::new();
1866            for pair in j.get("v")?.as_array()? {
1867                let pair = pair.as_array()?;
1868                m = m.insert(
1869                    value_from_repr(pair.first()?)?,
1870                    value_from_repr(pair.get(1)?)?,
1871                );
1872            }
1873            Value::Map(m)
1874        }
1875        "data" => {
1876            let mut fields = Fields::new();
1877            for (k, val) in j.get("f")?.as_object()? {
1878                fields.insert(Arc::from(k.as_str()), value_from_repr(val)?);
1879            }
1880            Value::data(
1881                Arc::from(j.get("t")?.as_str()?),
1882                j.get("c").and_then(|c| c.as_str()).map(Arc::from),
1883                fields,
1884            )
1885        }
1886        _ => return None,
1887    })
1888}
1889
1890/// Structural digest of a value — the replay-determinism oracle (§4.8).
1891///
1892/// A property of the *value*, not of whoever produced it: two backends that disagree are
1893/// detected by comparing digests, so the digest cannot live in either of them.
1894pub fn digest(v: &Value) -> [u8; 32] {
1895    let mut hasher = blake3::Hasher::new();
1896    hash_into(v, &mut hasher);
1897    *hasher.finalize().as_bytes()
1898}
1899
1900fn hash_into(v: &Value, h: &mut blake3::Hasher) {
1901    match v {
1902        Value::Unit => h.update(&[0]),
1903        Value::Bool(b) => h.update(&[1, *b as u8]),
1904        Value::Int(i) => {
1905            h.update(&[2]);
1906            h.update(&i.to_le_bytes())
1907        }
1908        Value::Float(bits) => {
1909            h.update(&[3]);
1910            h.update(&bits.to_le_bytes())
1911        }
1912        Value::Str(s) => {
1913            h.update(&[4]);
1914            h.update(&(s.len() as u64).to_le_bytes());
1915            h.update(s.as_bytes())
1916        }
1917        Value::List(xs) => {
1918            h.update(&[5]);
1919            h.update(&(xs.len() as u64).to_le_bytes());
1920            xs.for_each(|x| {
1921                hash_into(x, h);
1922            });
1923            h
1924        }
1925        Value::Map(m) => {
1926            h.update(&[6]);
1927            h.update(&(m.len() as u64).to_le_bytes());
1928            for (k, val) in m.iter() {
1929                hash_into(k, h);
1930                hash_into(val, h);
1931            }
1932            h
1933        }
1934        Value::Data(d) => {
1935            h.update(&[7]);
1936            h.update(d.ty.as_bytes());
1937            h.update(d.variant.as_deref().unwrap_or("").as_bytes());
1938            h.update(&(d.fields.len() as u64).to_le_bytes());
1939            for (k, val) in d.fields.iter() {
1940                h.update(k.as_bytes());
1941                hash_into(val, h);
1942            }
1943            h
1944        }
1945        Value::Html(html) => {
1946            h.update(&[8]);
1947            h.update(html.render().as_bytes())
1948        }
1949        Value::Attr(_) => h.update(&[9]),
1950        Value::Closure(_) => h.update(&[10]),
1951    };
1952}
1953
1954/// Every variable an expression reads and does not itself bind.
1955///
1956/// One implementation, because two passes need the same answer for different reasons:
1957/// [`crate::plan`] asks it of a signal function to decide what a dataflow operator has to be
1958/// handed, and a native backend asks it of a `lam` to decide what a closure has to *carry* — a
1959/// second walk that disagreed about one construct would give a compiled closure a field the
1960/// evaluator's environment does not have.
1961pub fn free_vars(c: &Core, bound: &mut BTreeSet<VarId>, out: &mut BTreeSet<VarId>) {
1962    match &c.kind {
1963        CoreKind::Var(v) => {
1964            if !bound.contains(v) {
1965                out.insert(*v);
1966            }
1967        }
1968        CoreKind::Const(_) | CoreKind::Global(_) => {}
1969        CoreKind::Lam { params, body } => {
1970            let added: Vec<VarId> = params
1971                .iter()
1972                .copied()
1973                .filter(|p| bound.insert(*p))
1974                .collect();
1975            free_vars(body, bound, out);
1976            for p in added {
1977                bound.remove(&p);
1978            }
1979        }
1980        CoreKind::App { func, args } => {
1981            free_vars(func, bound, out);
1982            for a in args {
1983                free_vars(a, bound, out);
1984            }
1985        }
1986        CoreKind::Prim { args, .. } => {
1987            for a in args {
1988                free_vars(a, bound, out);
1989            }
1990        }
1991        CoreKind::Let { var, value, body } => {
1992            free_vars(value, bound, out);
1993            let added = bound.insert(*var);
1994            free_vars(body, bound, out);
1995            if added {
1996                bound.remove(var);
1997            }
1998        }
1999        CoreKind::If { cond, then, alt } => {
2000            free_vars(cond, bound, out);
2001            free_vars(then, bound, out);
2002            free_vars(alt, bound, out);
2003        }
2004        CoreKind::Match { scrutinee, arms } => {
2005            free_vars(scrutinee, bound, out);
2006            for a in arms {
2007                let added: Vec<VarId> = a
2008                    .pattern
2009                    .binders()
2010                    .into_iter()
2011                    .filter(|p| bound.insert(*p))
2012                    .collect();
2013                for e in a.exprs() {
2014                    free_vars(e, bound, out);
2015                }
2016                for p in added {
2017                    bound.remove(&p);
2018                }
2019            }
2020        }
2021        CoreKind::Make { fields, .. } => {
2022            for (_, f) in fields {
2023                free_vars(f, bound, out);
2024            }
2025        }
2026        CoreKind::Field { base, .. } => free_vars(base, bound, out),
2027        CoreKind::With { base, fields } => {
2028            free_vars(base, bound, out);
2029            for (_, f) in fields {
2030                free_vars(f, bound, out);
2031            }
2032        }
2033        CoreKind::ListLit(items) => {
2034            for i in items {
2035                free_vars(i, bound, out);
2036            }
2037        }
2038        CoreKind::MapLit(pairs) => {
2039            for (k, v) in pairs {
2040                free_vars(k, bound, out);
2041                free_vars(v, bound, out);
2042            }
2043        }
2044    }
2045}
2046
2047#[cfg(test)]
2048mod tests {
2049    use super::*;
2050
2051    /// The property `Value::Float`'s representation exists for: the order the fold uses and the
2052    /// order arithmetic uses are one order.
2053    ///
2054    /// `f64::to_bits` does not have it — `(-1.0).to_bits()` is larger than `(1.0).to_bits()`
2055    /// because the sign bit is the top one — and that is the defect docs/27 §27.8 records. This is
2056    /// the test that would have caught it, checked across the sign, the zeroes and the infinities
2057    /// rather than on one example.
2058    #[test]
2059    fn reals_compare_as_reals_and_round_trip_through_their_key() {
2060        let ladder = [
2061            f64::NEG_INFINITY,
2062            -1e308,
2063            -1.5,
2064            -1.0,
2065            -f64::MIN_POSITIVE,
2066            0.0,
2067            f64::MIN_POSITIVE,
2068            1.0,
2069            1.5,
2070            1e308,
2071            f64::INFINITY,
2072        ];
2073        for w in ladder.windows(2) {
2074            let (a, b) = (Value::float(w[0]), Value::float(w[1]));
2075            assert!(a < b, "{} should order below {}", w[0], w[1]);
2076            assert_eq!(a.as_f64(), Some(w[0]), "and survive the round trip");
2077        }
2078
2079        // The two IEEE values that would otherwise break `Eq`, canonicalised.
2080        assert_eq!(
2081            Value::float(-0.0),
2082            Value::float(0.0),
2083            "`-0.0` and `0.0` are one value, because `Ord` cannot have two of them"
2084        );
2085        assert_eq!(
2086            Value::float(f64::NAN),
2087            Value::float(-f64::NAN),
2088            "and every NaN is one NaN — including a negative one — for the same reason"
2089        );
2090        assert!(
2091            Value::float(f64::NAN) > Value::float(f64::INFINITY),
2092            "NaN has to go somewhere, and above every number is somewhere"
2093        );
2094
2095        // The digest is a function of the value, and a different real is a different digest.
2096        assert_eq!(digest(&Value::float(1.5)), digest(&Value::float(1.5)));
2097        assert_ne!(digest(&Value::float(1.5)), digest(&Value::float(-1.5)));
2098    }
2099
2100    #[test]
2101    fn the_log_encoding_round_trips_exactly() {
2102        // The wire encoding deliberately loses information the browser does not need; the log
2103        // encoding cannot, because replay compares digests of what it reads back.
2104        let v = Value::data(
2105            Arc::from("State"),
2106            None,
2107            Fields::from_iter([(
2108                Arc::from("todos"),
2109                Value::Map(PMap::from_iter([(
2110                    Value::str_("k"),
2111                    Value::data(
2112                        Arc::from("Todo"),
2113                        None,
2114                        Fields::from_iter([
2115                            (Arc::from("done"), Value::Bool(true)),
2116                            (Arc::from("n"), Value::Int(-3)),
2117                        ]),
2118                    ),
2119                )])),
2120            )]),
2121        );
2122        assert_eq!(
2123            value_from_repr(&value_to_repr(&v).unwrap()),
2124            Some(v.clone())
2125        );
2126        let evt = Value::data(
2127            Arc::from("Event"),
2128            Some(Arc::from("Toggled")),
2129            Fields::from_iter([(Arc::from("id"), Value::str_("x"))]),
2130        );
2131        assert_eq!(value_from_repr(&value_to_repr(&evt).unwrap()), Some(evt));
2132    }
2133
2134    #[test]
2135    fn a_value_the_log_cannot_hold_is_refused_rather_than_flattened() {
2136        // This used to encode as `unit`, on the assumption that a view never reaches the log.
2137        // Nothing checked the assumption: `model State: cached: Html` compiles today, and the
2138        // durable path would have written `unit` and replayed a different state — silently, in the
2139        // one place this system's correctness argument does not permit silence.
2140        let view = Value::Html(Arc::new(crate::html::Html::text("hello")));
2141        let err = value_to_repr(&view).expect_err("a view is not data");
2142        assert!(
2143            err.to_string().contains("cannot be written to the log"),
2144            "{err}"
2145        );
2146
2147        // …and nesting does not launder it: a record holding one is refused too.
2148        let state = Value::data(
2149            Arc::from("State"),
2150            None,
2151            Fields::from_iter([(Arc::from("cached"), view.clone())]),
2152        );
2153        assert!(
2154            value_to_repr(&state).is_err(),
2155            "a record holding a view is not data"
2156        );
2157        assert!(
2158            value_to_repr(&Value::list(vec![view.clone()])).is_err(),
2159            "a list holding a view is not data"
2160        );
2161        assert!(
2162            value_to_repr(&Value::Map(PMap::new().insert(Value::str_("k"), view))).is_err(),
2163            "a map holding a view is not data"
2164        );
2165    }
2166
2167    #[test]
2168    fn maps_order_by_key_so_rendering_is_deterministic() {
2169        let m = PMap::new()
2170            .insert(Value::str_("b"), Value::Int(2))
2171            .insert(Value::str_("a"), Value::Int(1));
2172        let v = Value::Map(m);
2173        assert_eq!(v.display(), "{a: 1, b: 2}");
2174    }
2175
2176    #[test]
2177    fn a_newtype_renders_as_its_payload() {
2178        let id = Value::data(
2179            Arc::from("Id"),
2180            None,
2181            Fields::from_iter([(Arc::from("value"), Value::str_("u-1"))]),
2182        );
2183        assert_eq!(id.display(), "u-1");
2184        assert_eq!(id.to_json(), serde_json::json!("u-1"));
2185    }
2186
2187    #[test]
2188    fn a_command_serialises_with_its_variant_tag() {
2189        let cmd = Value::data(
2190            Arc::from("Command"),
2191            Some(Arc::from("Toggle")),
2192            Fields::from_iter([(Arc::from("id"), Value::str_("x"))]),
2193        );
2194        assert_eq!(cmd.to_json(), serde_json::json!({"c": "Toggle", "id": "x"}));
2195    }
2196
2197    #[test]
2198    fn environments_shadow_innermost_first() {
2199        let base = Env::new().extend(vec![(1, Value::Int(1))]);
2200        let inner = base.extend(vec![(1, Value::Int(2))]);
2201        assert_eq!(inner.get(1), Some(&Value::Int(2)));
2202        assert_eq!(base.get(1), Some(&Value::Int(1)));
2203        assert_eq!(inner.get(9), None);
2204    }
2205
2206    #[test]
2207    fn only_the_impure_primitives_carry_atoms_of_their_own() {
2208        assert_eq!(Prim::MergeClients.effects(), vec![Effect::Ingress]);
2209        assert_eq!(Prim::Durable.effects(), vec![Effect::Durable]);
2210        assert_eq!(Prim::NewUuid.effects(), vec![Effect::Nondet]);
2211        assert_eq!(Prim::Now.effects(), vec![Effect::Nondet]);
2212        assert_eq!(Prim::SecretEnv.effects(), vec![Effect::Env]);
2213        assert!(Prim::Add.effects().is_empty());
2214        assert!(
2215            Prim::Fold.effects().is_empty(),
2216            "a fold is pure; `durable` is the effect"
2217        );
2218        assert!(
2219            Prim::MapList.effects().is_empty(),
2220            "`map_list` performs nothing of its own — it performs its argument's row, which is a \
2221             variable and lives in the scheme"
2222        );
2223    }
2224}