1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
//! The blue runtime — **one** definition of what a blue program runs against.
//!
//! Before this crate existed, every consumer hand-rolled its own
//! `Interpreter::new()` + `install_primitives(...)` pair — three of them, in
//! three crates — and they had silently drifted: none of them loaded the Lisp
//! stdlib. So `6 % 3` lowered correctly to `(mod 6 3)`, `mod` was genuinely
//! defined, and the program still died with `unbound symbol: mod`, because
//! the definition lived in a stdlib nobody loaded.
//!
//! That is the duplication tax in its usual shape: the bug is not in any one
//! copy, it is in there *being* copies. One function now owns the answer.
//!
//! ## Layers
//!
//! A blue interpreter is built in two layers, and both are required:
//!
//! 1. **Rust primitives** — arithmetic, comparison, list ops, I/O.
//! 2. **The full tatara stdlib** — primitives, higher-order functions
//! (`map`/`filter`/`fold`), maps, channels, fibers, type-check, and
//! everything tatara defines in tatara-lisp itself
//! (`mod`, `rem`, `first`, `inc`, `even?`, the actor and transducer
//! helpers, …). Loading it is not optional garnish: blue's own operator
//! lowering depends on it.
//! 3. **blue's own core** ([`stdlib`]) — strings and number conversion, which
//! tatara-lisp does not have in any form. A Ruby-surface language without
//! `length` or `upcase` is not usable, and the semantics are Ruby's
//! (characters, not bytes), which is why they are blue's and not the
//! substrate's.
pub use erase_types;
pub use ;
pub use ;
pub use install_blue_stdlib;
use ;
/// Build an interpreter with the complete blue runtime installed.
///
/// This is the *only* sanctioned way to obtain one. A caller that builds an
/// `Interpreter` directly gets a partial runtime, and the failure shows up as
/// an unbound symbol at the far end of a program.
/// The prebuilt hostless substrate, built once per process.
///
/// **Measured 2026-08-01, and this is the entire reason the fork exists.**
/// Profiling a trivial blue program found the run dominated by ONE thing:
///
/// ```text
/// parse 9.6 µs
/// check 1.6 µs
/// interpreter_hostless() 5 820 µs <- 98.4% of the run
/// ```
///
/// Splitting that further: `Interpreter::new()` is 24 µs and
/// `install_full_stdlib_with` is the rest — the stdlib was being rebuilt from
/// scratch on every single `run()`, and blue's whole surface (`map`, `filter`,
/// string ops) lives in it, so no program could avoid the cost.
///
/// `fork` already existed upstream for exactly this, and blue simply was not
/// using it. tatara-lisp-eval's own `fork_cost.rs` opens with *"cheapness is
/// the entire reason it exists"* — the capability was built, documented and
/// guarded, and the consumer kept paying full price beside it.
///
/// | | per call |
/// |---|---|
/// | rebuild (`Interpreter::new` + `install_full_stdlib_with`) | **9.03 ms** |
/// | `fork()` of a prebuilt base | **0.102 ms** |
///
/// **88× on the dominant cost.** This is purgatory's computation-layer clause
/// made real: the stdlib is the most-reused computation in the language, and
/// it was being discarded and recomputed rather than resurrected.
///
/// Isolation is `fork`'s contract, not an assumption made here — its
/// correctness gates live upstream in `fork.rs`, and `fork_cost.rs` guards
/// against a future change that deep-copies (which would keep every
/// correctness test green while silently restoring the cost this removes).
static HOSTLESS_BASE: LazyLock =
new;
/// The common host-free case.
///
/// Forks the process-wide base rather than rebuilding the stdlib. Falls back to
/// a full build if the lock is poisoned: a poisoned lock means another thread
/// panicked mid-fork, and answering a correct-but-slow interpreter beats
/// propagating someone else's panic into an unrelated caller.
/// Lift blue's emitted forms into what the evaluator eats. **The one door.**
///
/// `Interpreter::eval_program` takes `&[Spanned]`; blue's stages produce
/// `Sexp`. Two callers bridged that gap by *printing the tree and reading it
/// back* — `forms.map(ToString::to_string).join("\n")` into
/// `tatara_lisp::read_spanned`. Both now call this instead, and the round trip
/// is gone.
///
/// **Why it had to go.** Printing a tree we already hold and re-parsing it puts
/// the reader's lexer between blue and its own output, for nothing — and the
/// printer and the reader are **not inverses**. `Atom::Str`'s `Display` escapes
/// its payload and its own docs explain at length why; the `Atom::Symbol` arm
/// is a bare `write_str`. So a symbol whose text carries a separator prints as
/// several tokens and reads back as several symbols: a well-formed tree with a
/// different meaning, no error raised. Measured 2026-08-02 —
/// `pipeline::tests::the_round_trip_is_not_the_identity_in_general` pins which
/// separators are silent and which are loud. The only thing that kept this from
/// biting was blue happening not to emit those bytes. A stage that never
/// serialises cannot be mis-read.
///
/// **What is given up: spans.** The old path's spans pointed into the
/// re-printed lisp text, a buffer no human ever wrote and no diagnostic could
/// usefully cite — they were positions in blue's own output, not in the
/// author's source. `Span::synthetic` says the same thing honestly. Carrying
/// real blue-source spans through erasure is a separate piece of work and
/// would start from `Spanned::from_sexp_at`, not from a printer.