@cotal-ai/lang 0.24.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +11 -0
  2. package/dist/engine/bridge.d.ts +71 -0
  3. package/dist/engine/bridge.d.ts.map +1 -0
  4. package/dist/engine/bridge.js +277 -0
  5. package/dist/engine/bridge.js.map +1 -0
  6. package/dist/engine/ctx.d.ts +140 -0
  7. package/dist/engine/ctx.d.ts.map +1 -0
  8. package/dist/engine/ctx.js +834 -0
  9. package/dist/engine/ctx.js.map +1 -0
  10. package/dist/engine/frame.d.ts +69 -0
  11. package/dist/engine/frame.d.ts.map +1 -0
  12. package/dist/engine/frame.js +105 -0
  13. package/dist/engine/frame.js.map +1 -0
  14. package/dist/engine/host.d.ts +77 -0
  15. package/dist/engine/host.d.ts.map +1 -0
  16. package/dist/engine/host.js +134 -0
  17. package/dist/engine/host.js.map +1 -0
  18. package/dist/engine/worker-entry.d.ts +26 -0
  19. package/dist/engine/worker-entry.d.ts.map +1 -0
  20. package/dist/engine/worker-entry.js +175 -0
  21. package/dist/engine/worker-entry.js.map +1 -0
  22. package/dist/engine/worker.d.ts +156 -0
  23. package/dist/engine/worker.d.ts.map +1 -0
  24. package/dist/engine/worker.js +123 -0
  25. package/dist/engine/worker.js.map +1 -0
  26. package/dist/errors.d.ts +44 -0
  27. package/dist/errors.d.ts.map +1 -1
  28. package/dist/errors.js +97 -0
  29. package/dist/errors.js.map +1 -1
  30. package/dist/index.d.ts +10 -1
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +10 -1
  33. package/dist/index.js.map +1 -1
  34. package/dist/interpret.d.ts +7 -38
  35. package/dist/interpret.d.ts.map +1 -1
  36. package/dist/interpret.js +45 -1003
  37. package/dist/interpret.js.map +1 -1
  38. package/dist/journal.d.ts.map +1 -1
  39. package/dist/journal.js +75 -1
  40. package/dist/journal.js.map +1 -1
  41. package/dist/library.d.ts.map +1 -1
  42. package/dist/library.js +13 -1
  43. package/dist/library.js.map +1 -1
  44. package/dist/perform.d.ts +138 -0
  45. package/dist/perform.d.ts.map +1 -0
  46. package/dist/perform.js +1052 -0
  47. package/dist/perform.js.map +1 -0
  48. package/dist/pins.d.ts +28 -8
  49. package/dist/pins.d.ts.map +1 -1
  50. package/dist/pins.js +31 -11
  51. package/dist/pins.js.map +1 -1
  52. package/dist/sim.d.ts +15 -1
  53. package/dist/sim.d.ts.map +1 -1
  54. package/dist/sim.js.map +1 -1
  55. package/dist/transform/emit.d.ts +23 -0
  56. package/dist/transform/emit.d.ts.map +1 -0
  57. package/dist/transform/emit.js +934 -0
  58. package/dist/transform/emit.js.map +1 -0
  59. package/dist/transform/index.d.ts +34 -0
  60. package/dist/transform/index.d.ts.map +1 -0
  61. package/dist/transform/index.js +31 -0
  62. package/dist/transform/index.js.map +1 -0
  63. package/dist/transform/scope.d.ts +58 -0
  64. package/dist/transform/scope.d.ts.map +1 -0
  65. package/dist/transform/scope.js +500 -0
  66. package/dist/transform/scope.js.map +1 -0
  67. package/dist/transform/seam.d.ts +78 -0
  68. package/dist/transform/seam.d.ts.map +1 -0
  69. package/dist/transform/seam.js +111 -0
  70. package/dist/transform/seam.js.map +1 -0
  71. package/dist/values.d.ts +15 -0
  72. package/dist/values.d.ts.map +1 -1
  73. package/dist/values.js +79 -0
  74. package/dist/values.js.map +1 -1
  75. package/package.json +9 -4
@@ -0,0 +1,934 @@
1
+ /**
2
+ * The emitter: one rule per admitted node type, and every rule that carries a law routes it through
3
+ * the seam rather than restating it.
4
+ *
5
+ * The walk order here is the walker's walk order, deliberately and line by line: a journal's step
6
+ * keys are occurrence counters allocated at the call, so two engines agree on a journal only if they
7
+ * agree on the ORDER effects are reached in. Where this file departs from the shape of
8
+ * `interpret.ts` it is because native JavaScript already has the meaning (block scoping, `switch`
9
+ * selection, `try`/`finally` completions) and re-deriving it would be a second implementation to
10
+ * keep in step.
11
+ */
12
+ import { BUILTINS, EVENT_CONSTRUCTORS, PRIMITIVES, PURE_PRIMITIVES, VALUE_NAMES } from "../primitives.js";
13
+ import { analyze } from "./scope.js";
14
+ import { stripPositions } from "../interpret.js";
15
+ import { pickNames } from "./seam.js";
16
+ /** Free names that are VALUES as well as callees: the walker declares each as a binding (`installGlobals`). */
17
+ const FREE_VALUES = new Set([
18
+ ...BUILTINS,
19
+ ...Object.keys(EVENT_CONSTRUCTORS),
20
+ ...Object.keys(PURE_PRIMITIVES),
21
+ ]);
22
+ const q = (s) => JSON.stringify(s);
23
+ /** `undefined` is a GLOBAL binding; the emitted module has no unbound references, so it spells the value. */
24
+ const VOID = "void 0";
25
+ /** Every identifier the source spells, so the emitter's own names can be picked around them. */
26
+ function identifiers(node, out) {
27
+ if (Array.isArray(node)) {
28
+ for (const n of node)
29
+ identifiers(n, out);
30
+ return;
31
+ }
32
+ if (node === null || typeof node !== "object")
33
+ return;
34
+ const n = node;
35
+ if (n.type === "Identifier" && typeof n.name === "string")
36
+ out.add(n.name);
37
+ for (const [k, v] of Object.entries(n)) {
38
+ if (k === "start" || k === "end" || k === "loc" || k === "range")
39
+ continue;
40
+ identifiers(v, out);
41
+ }
42
+ }
43
+ /** Every Identifier a declaration pattern binds, in source order. */
44
+ function declaredNames(pattern, out = []) {
45
+ switch (pattern.type) {
46
+ case "Identifier":
47
+ out.push(pattern);
48
+ break;
49
+ case "AssignmentPattern":
50
+ declaredNames(pattern.left, out);
51
+ break;
52
+ case "RestElement":
53
+ declaredNames(pattern.argument, out);
54
+ break;
55
+ case "ObjectPattern":
56
+ for (const p of pattern.properties ?? []) {
57
+ declaredNames((p.type === "RestElement" ? p.argument : p.value), out);
58
+ }
59
+ break;
60
+ case "ArrayPattern":
61
+ for (const el of pattern.elements ?? [])
62
+ if (el !== null && el !== undefined)
63
+ declaredNames(el, out);
64
+ break;
65
+ default:
66
+ break;
67
+ }
68
+ return out;
69
+ }
70
+ class Emitter {
71
+ a;
72
+ n;
73
+ sites = new Map();
74
+ proposed = new Set();
75
+ tempTop = 0;
76
+ tempMax = 0;
77
+ labels = 0;
78
+ conts = 0;
79
+ /** Cells whose record was created at the top of a block, so the declaration only writes into it. */
80
+ hoisted = new Set();
81
+ /** The innermost loop's break and continue labels, and the innermost switch's break label. */
82
+ breakTo = null;
83
+ continueTo = null;
84
+ constructor(a, n) {
85
+ this.a = a;
86
+ this.n = n;
87
+ }
88
+ // ---- bookkeeping ------------------------------------------------------------------------------
89
+ seam(member, args) {
90
+ this.sites.set(member, (this.sites.get(member) ?? 0) + 1);
91
+ return `${this.n.ctx}.${member}(${args})`;
92
+ }
93
+ /**
94
+ * Reach a member that is SURFACED but not yet in the contract. Empty once `callee` was granted as
95
+ * member 14, and kept because it is the only route into `TransformMeta.proposed`: a member added
96
+ * here shows up as measured debt, where one added straight to {@link seam} is caught instead by
97
+ * the suite's unruled-member check, loudly, but after the fact.
98
+ */
99
+ propose(member, args) {
100
+ this.proposed.add(member);
101
+ return this.seam(member, args);
102
+ }
103
+ temp() {
104
+ const name = `${this.n.temp}${this.tempTop}`;
105
+ this.tempTop += 1;
106
+ if (this.tempTop > this.tempMax)
107
+ this.tempMax = this.tempTop;
108
+ return name;
109
+ }
110
+ /** A temp's life is its own expression, so a sibling may reuse the slot; a parent's stays live. */
111
+ scoped(fn) {
112
+ const save = this.tempTop;
113
+ const r = fn();
114
+ this.tempTop = save;
115
+ return r;
116
+ }
117
+ label(kind) {
118
+ this.labels += 1;
119
+ return `${this.n.label}${kind}${this.labels}`;
120
+ }
121
+ // ---- names ------------------------------------------------------------------------------------
122
+ binding(node) {
123
+ return this.a.bindingOf.get(node);
124
+ }
125
+ /** Read a name: a native binding, a cell's field, or a free name. */
126
+ readName(node) {
127
+ const b = this.binding(node);
128
+ const name = node.name;
129
+ // A CELL READ CARRIES THE BINDING'S NAME (the dead-zone rule). An absent own `v` is a read
130
+ // before the declaration ran, and the host answers L2004 for the binding it names: the code the
131
+ // walker gives, and one a program can catch and read, where a native binding gives a host
132
+ // ReferenceError that `caught` can only report as L4000/host.
133
+ if (b !== undefined)
134
+ return b.cell ? this.seam("get", `${name}, ${q("v")}, ${q(b.name)}`) : name;
135
+ if (VALUE_NAMES.includes(name))
136
+ return VOID;
137
+ // A builtin is a BINDING in this language, so it has to be readable as a value: `const f = trim`,
138
+ // `map(xs, upper)`, and `json.stringify(x)` (whose callee's object is the free name `json`).
139
+ // The contract pins the read form as `free(name)` with the arguments OMITTED - the host answers
140
+ // it in the program's convention, so a native call on what comes back is correct.
141
+ if (FREE_VALUES.has(name))
142
+ return this.seam("free", q(name));
143
+ // An effect primitive is not a binding the walker ever declares: `const t = turn` is admitted by
144
+ // the validator and answers L2001 at run time (measured at 9dc154f8). The host answers the same
145
+ // way for a name that is not a free builtin, so the reference is emitted as the call that asks.
146
+ if (PRIMITIVES[name] !== undefined)
147
+ return `(await ${this.seam("free", `${q(name)}, []`)})`;
148
+ throw new Error(`transform: ${name} resolves to nothing, which a validated program cannot contain`);
149
+ }
150
+ // ---- the module -------------------------------------------------------------------------------
151
+ module(program) {
152
+ const body = this.block(program, true);
153
+ const decls = this.tempMax === 0 ? "" : `let ${Array.from({ length: this.tempMax }, (_, i) => `${this.n.temp}${i}`).join(", ")};\n`;
154
+ return `(${this.n.ctx}) => async () => {\n${decls}${this.fuel()}${body}}\n`;
155
+ }
156
+ fuel() {
157
+ return `await ${this.seam("fuel", "")};\n`;
158
+ }
159
+ // ---- statements -------------------------------------------------------------------------------
160
+ /** A block's statements. `bare` emits them without braces (the program body, a function body). */
161
+ block(node, bare = false) {
162
+ const stmts = node.body ?? [];
163
+ // THE CELLS FIRST: a closure written before the declaration can read the binding, so the record
164
+ // has to exist before that closure is made. See {@link hoistCells}.
165
+ const head = this.hoistCells(stmts);
166
+ const body = stmts.map((s) => this.stmt(s)).join("");
167
+ return bare ? head + body : `{\n${head}${body}}\n`;
168
+ }
169
+ /**
170
+ * The cell RECORDS a statement list declares, created empty at its top.
171
+ *
172
+ * The dead-zone rule's shape: `born({})` at the top of the block, `set(cell, "v", init)` where
173
+ * the declaration is, and every read through `get(cell, "v", name)`. The record must exist before
174
+ * the closures that capture it, and its `v` must be ABSENT until the declaration runs. That
175
+ * absence IS the dead zone, and it is what lets the host answer L2004 by name instead of a
176
+ * native ReferenceError. `v: undefined` is a present key, so the host asks `hasOwn`, not truth.
177
+ *
178
+ * A `for (let ...)` head is not here: its carrier gives each iteration its own record, and a
179
+ * binding declared in a loop head cannot be read before that head has run.
180
+ */
181
+ hoistCells(stmts) {
182
+ let out = "";
183
+ for (const s of stmts) {
184
+ if (s.type !== "VariableDeclaration")
185
+ continue;
186
+ for (const d of s.declarations ?? []) {
187
+ for (const id of declaredNames(d.id)) {
188
+ const b = this.binding(id);
189
+ // ONLY THE DEAD-ZONE CLASS. A binding that is a cell for the write rule alone cannot be
190
+ // read before its declaration, so its record is still built where it is declared, with
191
+ // its value already in it: one seam call rather than two on a path that runs.
192
+ if (b?.deadZone !== true || this.hoisted.has(b))
193
+ continue;
194
+ this.hoisted.add(b);
195
+ out += `const ${id.name} = ${this.seam("born", "{}")};\n`;
196
+ }
197
+ }
198
+ }
199
+ return out;
200
+ }
201
+ stmt(node) {
202
+ return this.scoped(() => this.statement(node));
203
+ }
204
+ statement(node) {
205
+ switch (node.type) {
206
+ case "ExpressionStatement": {
207
+ const e = node.expression;
208
+ // A destructuring assignment as a STATEMENT needs no value, so it emits as statements rather
209
+ // than as the async IIFE the expression form needs.
210
+ if (e.type === "AssignmentExpression" && (e.left.type === "ObjectPattern" || e.left.type === "ArrayPattern")) {
211
+ const t = this.temp();
212
+ return `${t} = ${this.expr(e.right)};\n${this.bindPattern(e.left, t, "assign")}`;
213
+ }
214
+ return `${this.expr(e)};\n`;
215
+ }
216
+ case "VariableDeclaration": {
217
+ const kind = node.kind === "let" ? "let" : "const";
218
+ let out = "";
219
+ for (const d of node.declarations ?? []) {
220
+ const init = d.init === null || d.init === undefined ? VOID : this.expr(d.init);
221
+ out += this.bindPattern(d.id, init, kind);
222
+ }
223
+ return out;
224
+ }
225
+ case "FunctionDeclaration":
226
+ return `${this.fn(node, node.id !== null && node.id !== undefined ? node.id.name : null)}\n`;
227
+ case "BlockStatement":
228
+ return this.block(node);
229
+ case "IfStatement": {
230
+ const alt = node.alternate === null || node.alternate === undefined ? "" : ` else ${this.wrap(node.alternate)}`;
231
+ return `if (${this.expr(node.test)}) ${this.wrap(node.consequent)}${alt}\n`;
232
+ }
233
+ case "WhileStatement": {
234
+ const b = this.label("b");
235
+ const c = this.label("c");
236
+ const test = this.expr(node.test);
237
+ const body = this.loopBody(node.body, b, c);
238
+ return `${b}: for (;;) {\n${this.fuel()}if (!(${test})) break ${b};\n${c}: {\n${body}}\n}\n`;
239
+ }
240
+ case "ForStatement":
241
+ return this.forStatement(node);
242
+ case "ForOfStatement":
243
+ return this.forOfStatement(node);
244
+ case "ReturnStatement":
245
+ return `return ${node.argument === null || node.argument === undefined ? VOID : this.expr(node.argument)};\n`;
246
+ case "BreakStatement":
247
+ if (this.breakTo === null)
248
+ throw new Error("transform: `break` outside a loop or switch");
249
+ return `break ${this.breakTo};\n`;
250
+ case "ContinueStatement":
251
+ if (this.continueTo === null)
252
+ throw new Error("transform: `continue` outside a loop");
253
+ return `break ${this.continueTo};\n`;
254
+ case "ThrowStatement":
255
+ return `throw ${this.expr(node.argument)};\n`;
256
+ case "TryStatement":
257
+ return this.tryStatement(node);
258
+ case "SwitchStatement":
259
+ return this.switchStatement(node);
260
+ case "EmptyStatement":
261
+ return ";\n";
262
+ default:
263
+ throw new Error(`transform: no rule for statement ${node.type}`);
264
+ }
265
+ }
266
+ /** A statement in a position that must hold exactly one: `if (x) y;` becomes `if (x) { y; }`. */
267
+ wrap(node) {
268
+ return node.type === "BlockStatement" ? this.block(node) : `{\n${this.stmt(node)}}`;
269
+ }
270
+ /** A loop body, with `break`/`continue` aimed at this loop's labels. */
271
+ loopBody(node, breakTo, continueTo) {
272
+ const b = this.breakTo;
273
+ const c = this.continueTo;
274
+ this.breakTo = breakTo;
275
+ this.continueTo = continueTo;
276
+ const out = node.type === "BlockStatement" ? this.block(node, true) : this.stmt(node);
277
+ this.breakTo = b;
278
+ this.continueTo = c;
279
+ return out;
280
+ }
281
+ /**
282
+ * `for`, in the shape the specification and the walker both give it.
283
+ *
284
+ * A `for (let ...)` head gives EACH ITERATION its own binding, and the copy happens after the body
285
+ * and before the update, so a closure made in one iteration keeps that iteration's value, and the
286
+ * update applies to the NEXT iteration's binding. Native `for (let ...)` does exactly this, but a
287
+ * cell (see scope.ts) is a record, not a binding, and copying a reference per iteration would let
288
+ * every closure watch the counter move. The carrier form below reproduces the walker's order for
289
+ * both, so one shape covers cells and natives alike.
290
+ */
291
+ forStatement(node) {
292
+ const b = this.label("b");
293
+ const c = this.label("c");
294
+ const init = node.init;
295
+ const perIteration = init !== null && init !== undefined && init.type === "VariableDeclaration" && init.kind === "let";
296
+ if (!perIteration) {
297
+ let head = "";
298
+ if (init !== null && init !== undefined)
299
+ head = init.type === "VariableDeclaration" ? this.stmt(init) : `${this.expr(init)};\n`;
300
+ const test = node.test === null || node.test === undefined ? "true" : this.expr(node.test);
301
+ const body = this.loopBody(node.body, b, c);
302
+ const update = node.update === null || node.update === undefined ? "" : `${this.expr(node.update)};\n`;
303
+ return `{\n${head}${b}: for (;;) {\n${this.fuel()}if (!(${test})) break ${b};\n${c}: {\n${body}}\n${update}}\n}\n`;
304
+ }
305
+ // THE CARRIERS ARE ALLOCATED FIRST, before any expression inside the loop is emitted. They are
306
+ // the one place a temporary outlives its own expression (they hold the binding's value ACROSS
307
+ // an iteration), so an expression emitted before them would take their slots and the loop would
308
+ // overwrite its own counter. Measured, before this order: `for (let i = 0; i < 3; i = i + 1)`
309
+ // never terminated and the run died on the step budget.
310
+ const first = this.temp();
311
+ const decls = init.declarations ?? [];
312
+ const carriers = [];
313
+ for (const d of decls) {
314
+ const id = d.id;
315
+ if (id.type !== "Identifier")
316
+ throw new Error("transform: a per-iteration `for (let ...)` head takes a name, not a pattern");
317
+ carriers.push({ carrier: this.temp(), id });
318
+ }
319
+ let head = `${first} = true;\n`;
320
+ for (let i = 0; i < decls.length; i += 1) {
321
+ const d = decls[i];
322
+ head += `${carriers[i].carrier} = ${d.init === null || d.init === undefined ? VOID : this.expr(d.init)};\n`;
323
+ }
324
+ let open = "";
325
+ let close = "";
326
+ for (const { carrier, id } of carriers) {
327
+ open += this.bindPattern(id, carrier, "let");
328
+ close += `${carrier} = ${this.readName(id)};\n`;
329
+ }
330
+ // The update runs on the NEXT iteration's binding, which is where the specification and the
331
+ // walker both put it: the per-iteration copy happens after the body and before the increment.
332
+ const update = node.update === null || node.update === undefined ? "" : `${this.expr(node.update)};\n`;
333
+ const test = node.test === null || node.test === undefined ? "true" : this.expr(node.test);
334
+ const body = this.loopBody(node.body, b, c);
335
+ return (`{\n${head}${b}: for (;;) {\n${open}` +
336
+ `if (!${first}) {\n${update}}\n${first} = false;\n` +
337
+ `${this.fuel()}if (!(${test})) break ${b};\n${c}: {\n${body}}\n${close}}\n}\n`);
338
+ }
339
+ forOfStatement(node) {
340
+ const b = this.label("b");
341
+ const c = this.label("c");
342
+ const items = this.temp();
343
+ const item = this.temp();
344
+ const right = this.expr(node.right);
345
+ const left = node.left;
346
+ const target = left.type === "VariableDeclaration" ? left.declarations[0].id : left;
347
+ const mode = left.type === "VariableDeclaration" ? (left.kind === "let" ? "let" : "const") : "assign";
348
+ const bind = this.bindPattern(target, item, mode);
349
+ const body = this.loopBody(node.body, b, c);
350
+ return (`{\nconst ${items} = ${this.seam("iter", right)};\n${b}: for (const ${item} of ${items}) {\n` +
351
+ `${this.fuel()}${bind}${c}: {\n${body}}\n}\n}\n`);
352
+ }
353
+ /**
354
+ * `try`, with JavaScript's own completion semantics (`try { return 1 } finally { return 2 }` is 2)
355
+ * and the catch parameter bound the way the walker binds it.
356
+ *
357
+ * `__ctx.caught` is required at the head of every catch clause and does both halves: it rethrows
358
+ * the six classes no program may catch, and otherwise answers the frozen `{code, kind, message}`
359
+ * record the walker hands a catch. A native binding of the raw host value diverges on the first
360
+ * program that reads `e.code`.
361
+ */
362
+ tryStatement(node) {
363
+ let out = `try ${this.block(node.block)}`;
364
+ const handler = node.handler;
365
+ if (handler !== null && handler !== undefined) {
366
+ const raw = this.temp();
367
+ const caught = this.seam("caught", raw);
368
+ const param = handler.param;
369
+ const bind = param === null || param === undefined ? `${caught};\n` : this.bindPattern(param, caught, "const");
370
+ out += `catch (${raw}) {\n${bind}${this.block(handler.body, true)}}\n`;
371
+ }
372
+ if (node.finalizer !== null && node.finalizer !== undefined)
373
+ out += `finally ${this.block(node.finalizer)}`;
374
+ return out;
375
+ }
376
+ /**
377
+ * `switch`, native.
378
+ *
379
+ * The walker reproduces JavaScript's selection by hand (tests in source order, `default` skipped
380
+ * during matching and entered only when nothing matched, then fall-through from the selected
381
+ * clause) because a walker has to. Emitting a native `switch` is that same meaning, not an
382
+ * approximation of it, and a hand-rolled two-pass form here would be a second implementation of a
383
+ * rule the engine already has.
384
+ */
385
+ switchStatement(node) {
386
+ const b = this.label("b");
387
+ const save = this.breakTo;
388
+ this.breakTo = b;
389
+ // A SWITCH'S CASES SHARE ONE BLOCK SCOPE, and no statement inside it runs before the jump, so a
390
+ // cell declared in a case is created in a block AROUND the switch instead of at its top. That
391
+ // block holds nothing else, so it scopes exactly as the switch's own block does.
392
+ const cells = this.hoistCells((node.cases ?? []).flatMap((c) => c.consequent ?? []));
393
+ const disc = this.expr(node.discriminant);
394
+ let out = `${b}: switch (${disc}) {\n`;
395
+ for (const c of node.cases ?? []) {
396
+ out += c.test === null || c.test === undefined ? "default:\n" : `case ${this.expr(c.test)}:\n`;
397
+ for (const s of c.consequent ?? [])
398
+ out += this.stmt(s);
399
+ }
400
+ this.breakTo = save;
401
+ return cells === "" ? `${out}}\n` : `{\n${cells}${out}}\n}\n`;
402
+ }
403
+ // ---- bindings ---------------------------------------------------------------------------------
404
+ /**
405
+ * Write a cell's field: `set(cell, "v", value)`, with the binding NAME where the write can land
406
+ * in the binding's dead zone.
407
+ *
408
+ * The name is passed on every write to a hoisted (dead-zone) cell and on NO other, which is what
409
+ * the host reads as "refuse if the declaration has not run". The declaration's own initializing
410
+ * write is the one that ends the dead zone, so it never carries it, and passing it there would make
411
+ * a binding refuse its own initialisation.
412
+ */
413
+ cellWrite(b, target, value) {
414
+ const named = b !== undefined && b.deadZone;
415
+ return this.seam("set", `${target}, ${q("v")}, ${value}${named ? `, ${q(b.name)}` : ""}`);
416
+ }
417
+ /**
418
+ * Bind a pattern, declaring (`const`/`let`) or assigning.
419
+ *
420
+ * Native destructuring is NOT used: `const { a } = o` reaches the prototype chain, and the
421
+ * walker's `bindPattern` reads own fields only, through `memberOf`. So every field read goes
422
+ * through `__ctx.get` and every list read through `__ctx.iter`, which is also what keeps L4014 and
423
+ * L4015 where the walker put them.
424
+ */
425
+ bindPattern(pattern, valueCode, mode) {
426
+ switch (pattern.type) {
427
+ case "Identifier": {
428
+ const b = this.binding(pattern);
429
+ const name = pattern.name;
430
+ if (mode === "assign") {
431
+ return b?.cell === true ? `${this.cellWrite(b, name, valueCode)};\n` : `${name} = ${valueCode};\n`;
432
+ }
433
+ // A hoisted cell's record already exists (see hoistCells): the declaration is the write that
434
+ // ends its dead zone. A cell that was NOT hoisted is a loop head's carrier, which builds a
435
+ // fresh record per iteration.
436
+ if (b?.cell === true && this.hoisted.has(b))
437
+ return `${this.seam("set", `${name}, ${q("v")}, ${valueCode}`)};\n`;
438
+ if (b?.cell === true)
439
+ return `const ${name} = ${this.seam("born", `{ v: ${valueCode} }`)};\n`;
440
+ return `${mode === "let" ? "let" : "const"} ${name} = ${valueCode};\n`;
441
+ }
442
+ case "MemberExpression": {
443
+ // Only in `assign` mode: `[o.a, o.b] = pair`.
444
+ const obj = this.temp();
445
+ const key = this.temp();
446
+ return `${obj} = ${this.expr(pattern.object)};\n${key} = ${this.memberKey(pattern)};\n${this.seam("set", `${obj}, ${key}, ${valueCode}`)};\n`;
447
+ }
448
+ case "AssignmentPattern": {
449
+ const t = this.temp();
450
+ return `${t} = ${valueCode};\n` + this.bindPattern(pattern.left, `(${t} === ${VOID} ? ${this.expr(pattern.right)} : ${t})`, mode);
451
+ }
452
+ case "ObjectPattern": {
453
+ const s = this.temp();
454
+ // The walker refuses a null or undefined subject with L4010 before it reads a field, and an
455
+ // EMPTY pattern refuses too, so the guard is not the first field read. `__ctx.get` raises
456
+ // L4010 on exactly these two values; the text differs (a declared divergence, code parity is
457
+ // the contract).
458
+ let out = `${s} = ${valueCode};\nif (${s} === null || ${s} === ${VOID}) ${this.seam("get", `${s}, ${q("")}`)};\n`;
459
+ const taken = [];
460
+ for (const p of pattern.properties ?? []) {
461
+ if (p.type === "RestElement") {
462
+ const rest = this.temp();
463
+ out += `${rest} = { ...${s} };\n`;
464
+ for (const k of taken)
465
+ out += `delete ${rest}[${k}];\n`;
466
+ out += this.bindPattern(p.argument, this.seam("born", rest), mode);
467
+ continue;
468
+ }
469
+ const key = this.propertyKey(p);
470
+ taken.push(key);
471
+ out += this.bindPattern(p.value, this.seam("get", `${s}, ${key}`), mode);
472
+ }
473
+ return out;
474
+ }
475
+ case "ArrayPattern": {
476
+ const s = this.temp();
477
+ let out = `${s} = ${valueCode};\nif (${s} === null || ${s} === ${VOID}) ${this.seam("get", `${s}, ${q("")}`)};\n`;
478
+ const items = this.temp();
479
+ out += `${items} = ${this.seam("iter", s)};\n`;
480
+ const els = pattern.elements ?? [];
481
+ for (let i = 0; i < els.length; i += 1) {
482
+ const el = els[i];
483
+ if (el === null || el === undefined)
484
+ continue;
485
+ if (el.type === "RestElement") {
486
+ out += this.bindPattern(el.argument, this.seam("born", `${items}.slice(${i})`), mode);
487
+ break;
488
+ }
489
+ out += this.bindPattern(el, `${items}[${i}]`, mode);
490
+ }
491
+ return out;
492
+ }
493
+ default:
494
+ throw new Error(`transform: no rule for binding pattern ${pattern.type}`);
495
+ }
496
+ }
497
+ /** A property key in a pattern or an object literal, as a string expression. */
498
+ propertyKey(p) {
499
+ const key = p.key;
500
+ if (p.computed === true)
501
+ return this.expr(key);
502
+ return key.type === "Identifier" ? q(key.name) : q(String(key.value));
503
+ }
504
+ /** A member expression's key: the spelled name, or the computed value the host holds to L4018. */
505
+ memberKey(node) {
506
+ if (node.computed !== true)
507
+ return q(node.property.name);
508
+ return this.expr(node.property);
509
+ }
510
+ // ---- functions --------------------------------------------------------------------------------
511
+ /**
512
+ * A function.
513
+ *
514
+ * Parameters arrive as one rest array and are bound in the body, in order, exactly as the walker's
515
+ * `makeFunction` binds them, which is what makes a default, a pattern and a rest parameter one
516
+ * rule rather than three. Nothing observes a function's arity here: a function has no members.
517
+ */
518
+ fn(node, name) {
519
+ const saveTop = this.tempTop;
520
+ const saveMax = this.tempMax;
521
+ const saveBreak = this.breakTo;
522
+ const saveContinue = this.continueTo;
523
+ this.tempTop = 0;
524
+ this.tempMax = 0;
525
+ this.breakTo = null;
526
+ this.continueTo = null;
527
+ const args = `${this.n.temp}a`;
528
+ let binds = "";
529
+ const params = node.params ?? [];
530
+ for (let i = 0; i < params.length; i += 1) {
531
+ const p = params[i];
532
+ if (p.type === "RestElement") {
533
+ binds += this.bindPattern(p.argument, this.seam("born", `${args}.slice(${i})`), "let");
534
+ break;
535
+ }
536
+ binds += this.bindPattern(p, `${args}[${i}]`, "let");
537
+ }
538
+ const body = node.body;
539
+ const inner = body.type === "BlockStatement" ? this.block(body, true) : `return ${this.expr(body)};\n`;
540
+ const decls = this.tempMax === 0 ? "" : `let ${Array.from({ length: this.tempMax }, (_, i) => `${this.n.temp}${i}`).join(", ")};\n`;
541
+ const head = name === null ? `async (...${args}) => ` : `async function ${name}(...${args}) `;
542
+ const out = `${head}{\n${decls}${this.fuel()}${binds}${inner}}`;
543
+ this.tempTop = saveTop;
544
+ this.tempMax = saveMax;
545
+ this.breakTo = saveBreak;
546
+ this.continueTo = saveContinue;
547
+ return out;
548
+ }
549
+ // ---- expressions ------------------------------------------------------------------------------
550
+ expr(node) {
551
+ return this.scoped(() => this.expression(node));
552
+ }
553
+ expression(node) {
554
+ switch (node.type) {
555
+ case "Literal":
556
+ return this.literal(node);
557
+ case "Identifier":
558
+ return this.readName(node);
559
+ case "TemplateLiteral": {
560
+ const quasis = node.quasis.map((qu) => q((qu.value.cooked)));
561
+ const exprs = node.expressions.map((e) => this.expr(e));
562
+ return this.seam("template", `[${quasis.join(", ")}], [${exprs.join(", ")}]`);
563
+ }
564
+ case "ArrayExpression": {
565
+ const parts = (node.elements ?? []).map((el) => {
566
+ if (el === null || el === undefined)
567
+ throw new Error("transform: an array hole is not a value this language has");
568
+ return el.type === "SpreadElement" ? `...${this.seam("iter", this.expr(el.argument))}` : this.expr(el);
569
+ });
570
+ return this.seam("born", `[${parts.join(", ")}]`);
571
+ }
572
+ case "ObjectExpression": {
573
+ // Every key is emitted COMPUTED. A literal `__proto__:` key sets a prototype in JavaScript
574
+ // and names an own field in this language; the validator refuses the literal spelling
575
+ // (L1028, measured), and a computed key never reaches the prototype at all, so the emitted
576
+ // form cannot express the hazard whatever the validator does later.
577
+ const parts = (node.properties ?? []).map((p) => p.type === "SpreadElement" ? `...${this.expr(p.argument)}` : `[${this.propertyKey(p)}]: ${this.expr(p.value)}`);
578
+ return this.seam("born", `{ ${parts.join(", ")} }`);
579
+ }
580
+ case "MemberExpression":
581
+ case "ChainExpression":
582
+ case "CallExpression":
583
+ return this.chain(node);
584
+ case "UnaryExpression": {
585
+ const op = node.operator;
586
+ const v = this.expr(node.argument);
587
+ if (op === "!")
588
+ return `(!${v})`;
589
+ if (op === "typeof")
590
+ return `(typeof ${v})`;
591
+ const t = this.temp();
592
+ return `((${t} = ${v}), typeof ${t} === "number" ? ${op}${t} : ${this.seam("unary", `${q(op)}, ${t}`)})`;
593
+ }
594
+ case "UpdateExpression":
595
+ return this.update(node);
596
+ case "BinaryExpression": {
597
+ const op = node.operator;
598
+ const l = this.expr(node.left);
599
+ // `===`/`!==` are taken before the coercion refusal in the walker, so they are native here.
600
+ if (op === "===" || op === "!==")
601
+ return `(${l} ${op} ${this.expr(node.right)})`;
602
+ // THE OPERAND TEMPORARIES ARE ALLOCATED BEFORE THE RIGHT SIDE IS EMITTED. A temporary's life
603
+ // is its own expression, so a sibling may reuse the slot, but `a` outlives the right
604
+ // operand's evaluation, and emitting the right side first let it reuse `a`'s slot and
605
+ // overwrite the left value in place. Measured: `(5 + 1) + (3 + 4)` answered 10 for 13.
606
+ const a = this.temp();
607
+ const b = this.temp();
608
+ const r = this.expr(node.right);
609
+ // BOTH operands are assigned before the test. A `&&` between the assignments would
610
+ // short-circuit past the right-hand one, so its effects would vanish and the host leg would
611
+ // refuse a stale value (the engine host caught this in the design sketch).
612
+ const native = `${a} ${op} ${b}`;
613
+ const fast = STRING_SAFE.has(op)
614
+ ? `(typeof ${a} === "number" && typeof ${b} === "number") || (typeof ${a} === "string" && typeof ${b} === "string")`
615
+ : `typeof ${a} === "number" && typeof ${b} === "number"`;
616
+ return `((${a} = ${l}), (${b} = ${r}), (${fast}) ? (${native}) : ${this.seam("binary", `${q(op)}, ${a}, ${b}`)})`;
617
+ }
618
+ case "LogicalExpression":
619
+ return `(${this.expr(node.left)} ${node.operator} ${this.expr(node.right)})`;
620
+ case "ConditionalExpression":
621
+ return `(${this.expr(node.test)} ? ${this.expr(node.consequent)} : ${this.expr(node.alternate)})`;
622
+ case "AssignmentExpression":
623
+ return this.assign(node);
624
+ case "AwaitExpression":
625
+ return `(await ${this.seam("fuel", "")}, await ${this.seam("await", this.expr(node.argument))})`;
626
+ case "ArrowFunctionExpression":
627
+ return this.fn(node, null);
628
+ case "FunctionExpression": {
629
+ // A named function expression sees its own name. Emitting the name on the expression is what
630
+ // binds it, exactly as the walker declares it into the call's environment.
631
+ const id = node.id;
632
+ if (id === null || id === undefined)
633
+ return this.fn(node, null);
634
+ return `(${this.fnNamedExpression(node, id.name)})`;
635
+ }
636
+ default:
637
+ throw new Error(`transform: no rule for expression ${node.type}`);
638
+ }
639
+ }
640
+ fnNamedExpression(node, name) {
641
+ const decl = this.fn(node, name);
642
+ return decl.replace(/^async function /, "async function ");
643
+ }
644
+ literal(node) {
645
+ if (node.bigint !== undefined)
646
+ throw new Error("transform: a bigint literal is not in this language");
647
+ if (node.regex !== undefined)
648
+ throw new Error("transform: a regular expression literal is not in this language");
649
+ const v = node.value;
650
+ if (typeof v === "string")
651
+ return q(v);
652
+ if (v === null)
653
+ return "null";
654
+ return String(node.raw ?? String(v));
655
+ }
656
+ /** `x++`, `--o.count`: JavaScript's meaning, with the read charged through `Number` as the walker charges it. */
657
+ /**
658
+ * `x++`'s operand, coerced through the seam's `update` selector.
659
+ *
660
+ * The walker reads the old value with a bare `Number(...)` and NO refusal: `o.c++` on a record is
661
+ * NaN there, while `-o.c` on the same record is L4018. The transform reproduces that and declines
662
+ * to rebuild it: silent coercion is the class the language exists to refuse, so `update` refuses a
663
+ * non-number operand and the walker's answer is a DECLARED divergence (issue 646) with its own
664
+ * cells, not a fidelity target. A number never reaches the host: the fast path keeps every counter
665
+ * native, which is what makes the numeric corpus identical on both arms.
666
+ */
667
+ toNumber(code) {
668
+ const t = this.temp();
669
+ return `((${t} = ${code}), typeof ${t} === "number" ? ${t} : ${this.seam("unary", `${q("update")}, ${t}`)})`;
670
+ }
671
+ update(node) {
672
+ const delta = node.operator === "++" ? "+ 1" : "- 1";
673
+ const prefix = node.prefix === true;
674
+ const arg = node.argument;
675
+ const old = this.temp();
676
+ const next = this.temp();
677
+ if (arg.type === "Identifier") {
678
+ const read = this.readName(arg);
679
+ const write = (value) => {
680
+ const b = this.binding(arg);
681
+ return b?.cell === true ? this.cellWrite(b, arg.name, value) : `(${arg.name} = ${value})`;
682
+ };
683
+ return `((${old} = ${this.toNumber(read)}), (${next} = ${old} ${delta}), ${write(next)}, ${prefix ? next : old})`;
684
+ }
685
+ const obj = this.temp();
686
+ const key = this.temp();
687
+ return (`((${obj} = ${this.expr(arg.object)}), (${key} = ${this.memberKey(arg)}), ` +
688
+ `(${old} = ${this.toNumber(this.seam("get", `${obj}, ${key}`))}), (${next} = ${old} ${delta}), ` +
689
+ `${this.seam("set", `${obj}, ${key}, ${next}`)}, ${prefix ? next : old})`);
690
+ }
691
+ assign(node) {
692
+ const op = node.operator;
693
+ const left = node.left;
694
+ if (left.type === "ObjectPattern" || left.type === "ArrayPattern") {
695
+ // The value of a destructuring assignment is its right-hand side; as an expression it needs a
696
+ // statement body, so it becomes one. As a STATEMENT it never reaches here.
697
+ const t = this.temp();
698
+ return `(await (async () => {\n${t} = ${this.expr(node.right)};\n${this.bindPattern(left, t, "assign")}return ${t};\n})())`;
699
+ }
700
+ if (left.type === "Identifier") {
701
+ const b = this.binding(left);
702
+ const name = left.name;
703
+ const write = (value) => b?.cell === true ? this.cellWrite(b, name, value) : `(${name} = ${value})`;
704
+ const t = this.temp();
705
+ // The READ is taken only where the operator needs one. Taking it unconditionally emitted a
706
+ // `get` a plain `=` never uses, and charged the site count for it.
707
+ if (op === "=")
708
+ return `((${t} = ${this.expr(node.right)}), ${write(t)}, ${t})`;
709
+ if (op === "&&=" || op === "||=" || op === "??=")
710
+ return this.logicalAssign(op, this.readName(left), write, node.right, t);
711
+ const cur = this.temp();
712
+ return `((${cur} = ${this.readName(left)}), (${t} = ${this.binaryOf(op.slice(0, -1), cur, () => this.expr(node.right))}), ${write(t)}, ${t})`;
713
+ }
714
+ const obj = this.temp();
715
+ const key = this.temp();
716
+ const head = `(${obj} = ${this.expr(left.object)}), (${key} = ${this.memberKey(left)})`;
717
+ const write = (value) => this.seam("set", `${obj}, ${key}, ${value}`);
718
+ const t = this.temp();
719
+ if (op === "=")
720
+ return `(${head}, (${t} = ${this.expr(node.right)}), ${write(t)}, ${t})`;
721
+ const read = () => this.seam("get", `${obj}, ${key}`);
722
+ if (op === "&&=" || op === "||=" || op === "??=")
723
+ return `(${head}, ${this.logicalAssign(op, read(), write, node.right, t)})`;
724
+ const cur = this.temp();
725
+ return `(${head}, (${cur} = ${read()}), (${t} = ${this.binaryOf(op.slice(0, -1), cur, () => this.expr(node.right))}), ${write(t)}, ${t})`;
726
+ }
727
+ /** `&&=`, `||=`, `??=`: the right side is evaluated, and the write happens, only when it proceeds. */
728
+ logicalAssign(op, read, write, right, t) {
729
+ const cur = this.temp();
730
+ const guard = op === "&&=" ? `${cur}` : op === "||=" ? `!${cur}` : `${cur} === null || ${cur} === ${VOID}`;
731
+ return `((${cur} = ${read}), (${guard}) ? ((${t} = ${this.expr(right)}), ${write(t)}, ${t}) : ${cur})`;
732
+ }
733
+ /**
734
+ * A binary operation over an already-emitted left operand and a right one this emits.
735
+ *
736
+ * The right side is a thunk for the reason the `BinaryExpression` case gives: `a` has to be
737
+ * allocated before the right operand is emitted, or the right operand reuses `a`'s slot and
738
+ * overwrites the left value between the assignment and the test.
739
+ */
740
+ binaryOf(op, leftCode, right) {
741
+ if (op === "===" || op === "!==")
742
+ return `(${leftCode} ${op} ${right()})`;
743
+ const a = this.temp();
744
+ const b = this.temp();
745
+ const rightCode = right();
746
+ const fast = STRING_SAFE.has(op)
747
+ ? `(typeof ${a} === "number" && typeof ${b} === "number") || (typeof ${a} === "string" && typeof ${b} === "string")`
748
+ : `typeof ${a} === "number" && typeof ${b} === "number"`;
749
+ return `((${a} = ${leftCode}), (${b} = ${rightCode}), (${fast}) ? (${a} ${op} ${b}) : ${this.seam("binary", `${q(op)}, ${a}, ${b}`)})`;
750
+ }
751
+ // ---- chains, calls ----------------------------------------------------------------------------
752
+ /**
753
+ * A member chain, with `?.` short-circuiting the WHOLE chain and nothing after it evaluated.
754
+ *
755
+ * Each optional link stores its object in a temp inside the guard, so the value is computed once:
756
+ * the guards run in order, and the body reads the last temp rather than re-deriving the links.
757
+ */
758
+ chain(node) {
759
+ const guards = [];
760
+ const body = this.chainLink(node.type === "ChainExpression" ? node.expression : node, guards, undefined);
761
+ return this.short(body, guards);
762
+ }
763
+ /** A body under the guards in force where it is written: nullish at any of them and nothing after runs. */
764
+ short(body, guards) {
765
+ if (guards.length === 0)
766
+ return body;
767
+ return `((${guards.join(") || (")}) ? ${VOID} : ${body})`;
768
+ }
769
+ /**
770
+ * One link of a chain, with everything written AFTER it handed down as {@link Rest}.
771
+ *
772
+ * The chain is emitted from the inside out (the innermost value first, each link wrapping what it
773
+ * produced) but the OPTIONAL CALL needs the opposite direction. `o.m?.()` short-circuits on
774
+ * whether the member is nullish, and the seam resolves a method name and calls it in one step, the
775
+ * one place a method name may be resolved at all, so the host makes that decision and the
776
+ * transform never sees the answer. What the host cannot then tell the transform is WHETHER it
777
+ * short-circuited: measured on the walker, `o.z?.().x` on an absent member is undefined while
778
+ * `o.m?.().x` on a member that RETURNS undefined is L4010, and a guard on the returned value
779
+ * answers undefined for both, so it would drop a refusal in silence. The seam's fifth argument to
780
+ * `call` hands the rest of the chain to the call that made the decision instead.
781
+ *
782
+ * So `rest` travels down to every link, and each one applies it to its own value, except the
783
+ * optional call, which compiles it into a closure the host applies or skips.
784
+ */
785
+ chainLink(node, guards, rest) {
786
+ const done = (code, g) => (rest === undefined ? code : rest(code, g));
787
+ if (node.type === "MemberExpression") {
788
+ return this.chainLink(node.object, guards, (objCode, g) => {
789
+ const obj = node.optional === true ? this.guard(objCode, g) : objCode;
790
+ return done(this.seam("get", `${obj}, ${this.memberKey(node)}`), g);
791
+ });
792
+ }
793
+ if (node.type === "CallExpression") {
794
+ const callee = node.callee;
795
+ // A primitive is dispatched by NAME, never by value: the validator forbids shadowing one, so a
796
+ // call spelled `turn` is always the effect. It is emitted BEFORE the ordinary argument list is
797
+ // built, because a primitive that defers its body hands that argument over differently.
798
+ if (callee.type === "Identifier" && this.binding(callee) === undefined && PRIMITIVES[callee.name] !== undefined) {
799
+ const name = callee.name;
800
+ return done(`(await ${this.seam("effect", `${q(name)}, [${this.effectArgs(name, node)}], ${this.site(name, node)}`)})`, guards);
801
+ }
802
+ const args = this.args(node);
803
+ if (callee.type === "Identifier" && this.binding(callee) === undefined && FREE_VALUES.has(callee.name)) {
804
+ return done(`(await ${this.seam("free", `${q(callee.name)}, [${args}]`)})`, guards);
805
+ }
806
+ if (callee.type === "MemberExpression") {
807
+ return this.chainLink(callee.object, guards, (objCode, g) => {
808
+ const obj = callee.optional === true ? this.guard(objCode, g) : objCode;
809
+ const key = this.memberKey(callee);
810
+ // The one place a method NAME may be resolved: at the call. That is what lets `get` refuse
811
+ // the same name everywhere else (L4020). An ordinary call's chain is written natively,
812
+ // and nothing in it waits on a decision only the host made.
813
+ if (node.optional !== true)
814
+ return done(`(await ${this.seam("call", `${obj}, ${key}, [${args}]`)})`, g);
815
+ // AND THE OPTIONAL FORM HANDS ITS ARGUMENTS OVER UNEVALUATED. The walker checks the member
816
+ // BEFORE it evaluates the argument list, so `o.m?.(await sleep("1s"))` on an absent member
817
+ // journals nothing while the same argument on a present method journals a sleep (the
818
+ // engine host measured both). An emitted array has already run them, and a resume would
819
+ // replay a step the walker's run never recorded, so it is a thunk, and `async` because an
820
+ // argument may await. The ordinary form is unchanged and still hands over a plain array.
821
+ const cont = rest === undefined ? "" : `, ${this.continuation(rest)}`;
822
+ return `(await ${this.seam("call", `${obj}, ${key}, async () => [${args}], true${cont}`)})`;
823
+ });
824
+ }
825
+ return this.chainLink(callee, guards, (fnCode, g) => {
826
+ const fn = node.optional === true ? this.guard(fnCode, g) : fnCode;
827
+ const f = this.temp();
828
+ // L4011 at a non-function callee, behind a `typeof` so a call to a real function never leaves
829
+ // the compartment. `const f = 1; f()` is admitted by the validator (measured).
830
+ return done(`(await ((${f} = ${fn}), typeof ${f} === "function" ? ${f} : ${this.seam("callee", f)})(${args}))`, g);
831
+ });
832
+ }
833
+ return done(this.expr(node), guards);
834
+ }
835
+ /**
836
+ * The rest of a chain, as the closure the seam's fifth argument to `call` takes.
837
+ *
838
+ * Its guards are its OWN: a link after the optional call is guarded only where the call answered,
839
+ * because a short-circuit already skipped everything here. `async` because a continuation may hold
840
+ * another call, and the host awaits what it applies.
841
+ */
842
+ continuation(rest) {
843
+ const value = `${this.n.temp}c${this.conts}`;
844
+ this.conts += 1;
845
+ const guards = [];
846
+ const body = rest(value, guards);
847
+ return `async (${value}) => ${this.short(body, guards)}`;
848
+ }
849
+ guard(code, guards) {
850
+ const t = this.temp();
851
+ guards.push(`(${t} = ${code}) === null || ${t} === ${VOID}`);
852
+ return t;
853
+ }
854
+ /**
855
+ * A primitive's arguments, with the DEFERRED BODY handed over unevaluated.
856
+ *
857
+ * `fanOut` and `conclave` take their body at index 1, and the walker evaluates it INSIDE the
858
+ * scope, after the entry has begun. Measured on the oracle: the same awaited effect journals
859
+ * after the scope entry in the body position and before it in the options bag. A body handed over
860
+ * already evaluated has journalled its effects in the wrong place, and a resume would replay a
861
+ * step the walker's run never recorded; it is the same rule as the optional call's arguments, and
862
+ * the host refuses a body that is not a thunk (L1000).
863
+ *
864
+ * WHICH primitives defer comes from the table (a scope-opener whose options sit at 2) rather
865
+ * than from a list of names spelled here, so a fifth combinator cannot arrive with its body
866
+ * silently eager.
867
+ */
868
+ effectArgs(name, node) {
869
+ const spec = PRIMITIVES[name];
870
+ const defers = spec !== undefined && spec.opensScope && spec.optionsAt === 2;
871
+ return (node.arguments ?? [])
872
+ .map((a, i) => {
873
+ const code = a.type === "SpreadElement" ? `...${this.seam("iter", this.expr(a.argument))}` : this.expr(a);
874
+ return defers && i === 1 ? `async () => (${code})` : code;
875
+ })
876
+ .join(", ");
877
+ }
878
+ args(node) {
879
+ return (node.arguments ?? [])
880
+ .map((a) => (a.type === "SpreadElement" ? `...${this.seam("iter", this.expr(a.argument))}` : this.expr(a)))
881
+ .join(", ");
882
+ }
883
+ /**
884
+ * The static per-call-site payload an effect carries.
885
+ *
886
+ * Only `race` needs one today: its journal entry holds a `branchDigest` over the LOSING arms'
887
+ * source, which the walker computes from the AST at run time. The engine has no AST then, so
888
+ * without this the two journals cannot be byte-identical for any race that settled.
889
+ */
890
+ /**
891
+ * The static payload a call site carries, because the engine has no AST at run time.
892
+ *
893
+ * A settled `race` journals a `branchDigest` over the arms it will never walk into, and that
894
+ * digest is a function of the SOURCE. The walker computes it from the object literal in hand;
895
+ * the engine has to be handed the same material, so the branch bodies travel here with their
896
+ * positions stripped by `interpret.ts`'s OWN function, imported and never copied, because a second
897
+ * implementation of "what the code IS" is a second answer to whether a resumed run diverged.
898
+ *
899
+ * WHAT TRAVELS IS THE STRIPPED BODY, NOT A PER-BRANCH DIGEST. The walker hashes one array of
900
+ * `[loserName, body]` pairs over the loser set, which is only known at run time; per-branch
901
+ * digests would make the host hash a hash and produce a different byte string for a journal entry
902
+ * that has to be identical. Only losers are ever digested, but which branches lose is the run's
903
+ * answer, so every branch travels.
904
+ */
905
+ site(name, node) {
906
+ if (name !== "race")
907
+ return "{}";
908
+ const branches = (node.arguments ?? [])[0];
909
+ if (branches === undefined || branches.type !== "ObjectExpression")
910
+ return "{}";
911
+ const bodies = {};
912
+ for (const p of branches.properties ?? []) {
913
+ const key = p.key;
914
+ const named = key?.name ?? key?.value;
915
+ if (named !== undefined)
916
+ bodies[named] = stripPositions(p.value);
917
+ }
918
+ return `{ branchDigests: ${JSON.stringify(bodies)} }`;
919
+ }
920
+ result() {
921
+ return { sites: Object.fromEntries([...this.sites].sort()), proposed: [...this.proposed].sort() };
922
+ }
923
+ }
924
+ /** Operators whose native meaning is the walker's for two strings as well as two numbers. */
925
+ const STRING_SAFE = new Set(["+", "<", "<=", ">", ">="]);
926
+ export function emit(program) {
927
+ const ids = new Set();
928
+ identifiers(program, ids);
929
+ const names = pickNames(ids);
930
+ const e = new Emitter(analyze(program), names);
931
+ const module = e.module(program);
932
+ return { module, names, ...e.result() };
933
+ }
934
+ //# sourceMappingURL=emit.js.map