jz 0.8.0 → 0.9.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 (73) hide show
  1. package/README.md +56 -9
  2. package/bench/README.md +121 -50
  3. package/bench/bench.svg +27 -27
  4. package/cli.js +3 -1
  5. package/dist/interop.js +1 -1
  6. package/dist/jz.js +7541 -6178
  7. package/index.js +165 -74
  8. package/interop.js +189 -17
  9. package/jzify/arguments.js +32 -1
  10. package/jzify/async.js +409 -0
  11. package/jzify/classes.js +136 -18
  12. package/jzify/generators.js +639 -0
  13. package/jzify/hoist-vars.js +73 -1
  14. package/jzify/index.js +123 -4
  15. package/jzify/names.js +1 -0
  16. package/jzify/transform.js +239 -1
  17. package/layout.js +48 -3
  18. package/module/array.js +318 -43
  19. package/module/atomics.js +144 -0
  20. package/module/collection.js +1429 -155
  21. package/module/core.js +431 -40
  22. package/module/date.js +142 -120
  23. package/module/fs.js +144 -0
  24. package/module/function.js +6 -3
  25. package/module/index.js +4 -1
  26. package/module/json.js +270 -49
  27. package/module/math.js +1032 -145
  28. package/module/number.js +532 -163
  29. package/module/object.js +353 -95
  30. package/module/regex.js +157 -7
  31. package/module/schema.js +100 -2
  32. package/module/string.js +428 -93
  33. package/module/typedarray.js +264 -72
  34. package/module/web.js +36 -0
  35. package/package.json +5 -5
  36. package/src/abi/string.js +82 -11
  37. package/src/ast.js +11 -5
  38. package/src/autoload.js +28 -1
  39. package/src/bridge.js +7 -2
  40. package/src/compile/analyze-scans.js +22 -7
  41. package/src/compile/analyze.js +136 -30
  42. package/src/compile/dyn-closure-tables.js +277 -0
  43. package/src/compile/emit-assign.js +281 -15
  44. package/src/compile/emit.js +1055 -70
  45. package/src/compile/index.js +277 -29
  46. package/src/compile/infer.js +42 -4
  47. package/src/compile/inplace-store.js +329 -0
  48. package/src/compile/loop-recurrence.js +167 -0
  49. package/src/compile/narrow.js +555 -18
  50. package/src/compile/peel-stencil.js +5 -0
  51. package/src/compile/plan/index.js +45 -3
  52. package/src/compile/plan/inline.js +8 -1
  53. package/src/compile/plan/literals.js +39 -0
  54. package/src/compile/plan/scope.js +430 -23
  55. package/src/compile/program-facts.js +732 -38
  56. package/src/ctx.js +64 -9
  57. package/src/helper-counters.js +7 -1
  58. package/src/ir.js +113 -5
  59. package/src/kind-traits.js +66 -3
  60. package/src/kind.js +84 -12
  61. package/src/op-policy.js +7 -4
  62. package/src/optimize/index.js +1179 -704
  63. package/src/optimize/recurse.js +2 -2
  64. package/src/optimize/vectorize.js +982 -67
  65. package/src/prepare/index.js +809 -67
  66. package/src/prepare/math-kernel.js +331 -0
  67. package/src/prepare/pre-eval.js +714 -0
  68. package/src/snapshot.js +194 -0
  69. package/src/type.js +1170 -56
  70. package/src/wat/assemble.js +403 -65
  71. package/src/wat/codegen.js +74 -14
  72. package/transform.js +113 -4
  73. package/wasi.js +3 -0
@@ -0,0 +1,639 @@
1
+ /**
2
+ * Generators — regenerator-style state machines, no stack suspension.
3
+ *
4
+ * `function* g(a) { … yield E … }` lowers to a factory arrow returning
5
+ * `{ next, return }` closures over shared mutable state: the body becomes a
6
+ * dispatch loop over a state local, each yield splits a state boundary, and
7
+ * `next(v)` is an ordinary closure call through the uniform ABI (mutable
8
+ * captures already ship). Sync only — no event loop, no microtasks.
9
+ *
10
+ * v1 surface (everything else rejects with a precise message):
11
+ * - yield as a statement, or as the RHS of `let x = yield E` / `x = yield E`
12
+ * - yield inside if/else, while, do-while and C-style for (any nesting)
13
+ * - plain `return E` anywhere; unlabeled break/continue of yield-bearing loops
14
+ * - compound statements WITHOUT yield stay atomic (later passes handle them)
15
+ * - yield* E — delegates to ANY iterator-protocol value (sent values thread,
16
+ * the completion value lands in `x = yield* E`)
17
+ * Out (v1): yield inside arbitrary expressions, try across yield,
18
+ * for-of/for-in bodies containing yield (except known-generator for-of, which
19
+ * desugars), labeled break/continue across states.
20
+ *
21
+ * @module jzify/generators
22
+ */
23
+
24
+ const isYield = (n) => Array.isArray(n) && (n[0] === 'yield' || n[0] === 'yield*')
25
+ const hasYield = (n) => Array.isArray(n) && (isYield(n) || n.some(hasYield))
26
+
27
+ // A break/continue that would bind OUTSIDE this statement (its target loop was
28
+ // decomposed into states, so the raw op would bind my dispatch while(1) instead).
29
+ // Inner loops re-bind their own jumps; arrows are their own function.
30
+ const hasFreeJump = (n, depth = 0) => {
31
+ if (!Array.isArray(n)) return false
32
+ const op = n[0]
33
+ if ((op === 'break' || op === 'continue') && n[1] == null) return depth === 0
34
+ if (op === '=>') return false
35
+ const inner = op === 'while' || op === 'do' || op === 'for' || op === 'for-in' ||
36
+ op === 'for-of' || op === 'switch' ? depth + 1 : depth
37
+ return n.some((c, i) => i > 0 && hasFreeJump(c, inner))
38
+ }
39
+
40
+ const S = { NEXT: '__s', SENT: '__sent' }
41
+
42
+ // ES2025 iterator helpers on iterator VALUES — injected (pay-per-use) when a
43
+ // program that mints iterators also uses helper methods in non-fusable
44
+ // positions (chain stored as a value, helper on an unknown receiver) or tests
45
+ // `instanceof Iterator`. Generator objects then mint through __it_mk, whose
46
+ // helpers each wrap the source in a fresh decorated iterator — lazy,
47
+ // spec-shaped (value+counter callbacks, early return() on short-circuit).
48
+ // Fusable chains still fuse (zero-cost path unchanged); this is the fallback
49
+ // that makes helper results first-class values.
50
+ export const ITER_HELPERS_RUNTIME = `
51
+ let __it_fn = (f, name) => { if (f == null || typeof f !== 'function') throw 'TypeError: ' + name + ' callback must be callable' }
52
+ let __it_cl = (it) => { if (typeof it.return === 'function') it.return(undefined) }
53
+ let __it_lim = (n, name) => {
54
+ let lim = +n
55
+ if (lim !== lim) throw 'RangeError: ' + name + ' limit must not be NaN'
56
+ lim = Math.trunc(lim)
57
+ if (lim < 0) throw 'RangeError: ' + name + ' limit must be non-negative'
58
+ return lim
59
+ }
60
+ let __it_mk = (nx, rt, th) => {
61
+ let it = { next: nx, return: rt, throw: th, '@@iterator': undefined,
62
+ map: undefined, filter: undefined, take: undefined, drop: undefined, flatMap: undefined,
63
+ toArray: undefined, reduce: undefined, forEach: undefined, some: undefined, every: undefined, find: undefined }
64
+ it[Symbol.iterator] = () => it
65
+ it.map = (f) => { __it_fn(f, 'map'); let c = 0; return __it_mk((v) => {
66
+ let r = it.next(v)
67
+ if (r.done) return r
68
+ let m = f(r.value, c)
69
+ c++
70
+ return { value: m, done: false }
71
+ }, it.return, it.throw) }
72
+ it.filter = (f) => { __it_fn(f, 'filter'); let c = 0; return __it_mk((v) => {
73
+ let r = it.next(v)
74
+ while (!r.done) { let hit = f(r.value, c); c++; if (hit) return { value: r.value, done: false }; r = it.next() }
75
+ return r
76
+ }, it.return, it.throw) }
77
+ it.take = (n) => {
78
+ let lim = __it_lim(n, 'take')
79
+ let c = 0
80
+ return __it_mk(() => {
81
+ if (c >= lim) { __it_cl(it); return { value: undefined, done: true } }
82
+ c++
83
+ return it.next()
84
+ }, it.return, it.throw)
85
+ }
86
+ it.drop = (n) => {
87
+ let lim = __it_lim(n, 'drop')
88
+ let c = 0
89
+ return __it_mk(() => {
90
+ while (c < lim) { c++; let r0 = it.next(); if (r0.done) return r0 }
91
+ return it.next()
92
+ }, it.return, it.throw)
93
+ }
94
+ it.flatMap = (f) => {
95
+ __it_fn(f, 'flatMap')
96
+ let inner = null, c = 0
97
+ return __it_mk(() => {
98
+ while (true) {
99
+ if (inner != null) {
100
+ let ri = inner.next()
101
+ if (!ri.done) return ri
102
+ inner = null
103
+ }
104
+ let r = it.next()
105
+ if (r.done) return r
106
+ let m = f(r.value, c)
107
+ c++
108
+ inner = __it_from(m)
109
+ }
110
+ }, (rv) => {
111
+ // closing the helper closes the ACTIVE inner iterator first (spec:
112
+ // IteratorClose forwards through the flattening), then the source.
113
+ if (inner != null) { let i2 = inner; inner = null; __it_cl(i2) }
114
+ if (typeof it.return === 'function') return it.return(rv)
115
+ return { value: rv, done: true }
116
+ }, it.throw)
117
+ }
118
+ it.toArray = () => { let a = [], r = it.next(); while (!r.done) { a.push(r.value); r = it.next() } return a }
119
+ it.reduce = (f, init) => {
120
+ __it_fn(f, 'reduce')
121
+ let acc = init, c = 0
122
+ if (init === undefined) {
123
+ let r0 = it.next()
124
+ if (r0.done) throw 'TypeError: Reduce of empty iterator with no initial value'
125
+ acc = r0.value
126
+ c = 1
127
+ }
128
+ let r = it.next()
129
+ while (!r.done) { acc = f(acc, r.value, c); c++; r = it.next() }
130
+ return acc
131
+ }
132
+ it.forEach = (f) => { __it_fn(f, 'forEach'); let c = 0, r = it.next(); while (!r.done) { f(r.value, c); c++; r = it.next() } }
133
+ it.some = (f) => { __it_fn(f, 'some'); let c = 0, r = it.next(); while (!r.done) { if (f(r.value, c)) { __it_cl(it); return true } c++; r = it.next() } return false }
134
+ it.every = (f) => { __it_fn(f, 'every'); let c = 0, r = it.next(); while (!r.done) { if (!f(r.value, c)) { __it_cl(it); return false } c++; r = it.next() } return true }
135
+ it.find = (f) => { __it_fn(f, 'find'); let c = 0, r = it.next(); while (!r.done) { if (f(r.value, c)) { __it_cl(it); return r.value } c++; r = it.next() } return undefined }
136
+ return it
137
+ }
138
+ let __it_from = (v) => {
139
+ if (v == null) throw 'TypeError: value is not iterable'
140
+ let w = v
141
+ if (typeof w === 'object' && w[Symbol.iterator] != null) {
142
+ if (typeof w[Symbol.iterator] !== 'function') throw 'TypeError: [Symbol.iterator] is not callable'
143
+ w = w[Symbol.iterator]()
144
+ }
145
+ if (typeof w === 'object' && w.next != null) return w
146
+ let ix = 0
147
+ return __it_mk(() => {
148
+ if (ix >= v.length) return { value: undefined, done: true }
149
+ let e = v[ix]
150
+ ix++
151
+ return { value: e, done: false }
152
+ }, undefined, undefined)
153
+ }
154
+ `
155
+
156
+ // Array.from over iterator values — rewires \`Array.from(x)\` in iterator-
157
+ // minting programs: protocol values materialize, arrays COPY (from() always
158
+ // returns a fresh array), array-likes build by length. Injected on use only.
159
+ export const ITER_ARR_RUNTIME = `
160
+ let __it_arr = (v) => {
161
+ if (v == null) throw 'TypeError: value is not iterable'
162
+ let w = v
163
+ if (typeof w === 'object' && w[Symbol.iterator] != null) w = w[Symbol.iterator]()
164
+ if (typeof w === 'object' && w.next != null) {
165
+ let a = [], r = w.next()
166
+ while (!r.done) { a.push(r.value); r = w.next() }
167
+ return a
168
+ }
169
+ let a = [], n = v.length
170
+ for (let i = 0; i < n; i++) a.push(v[i])
171
+ return a
172
+ }
173
+ `
174
+
175
+ export function createGeneratorLowering({ transform, err, generatorNames, genTemp, iterProto }) {
176
+ // Collect every let/const binding name in the body — generator locals live in
177
+ // the factory scope so they survive across next() resumes. Shadowing across
178
+ // sibling blocks would collide after hoisting — reject (rename support later).
179
+ const collectLocals = (node, out, path) => {
180
+ if (!Array.isArray(node)) return
181
+ if ((node[0] === 'let' || node[0] === 'const')) {
182
+ for (let i = 1; i < node.length; i++) {
183
+ const d = node[i]
184
+ const name = Array.isArray(d) && d[0] === '=' ? d[1] : d
185
+ if (typeof name !== 'string')
186
+ err('generators v1: destructuring declarations inside a generator body are not supported yet — bind names first')
187
+ if (out.has(name)) err(`generators v1: '${name}' is declared twice in the generator body — hoisted locals must be unique`)
188
+ out.add(name)
189
+ }
190
+ }
191
+ // arrows create their own scope — their decls don't hoist
192
+ if (node[0] === '=>') return
193
+ for (let i = 1; i < node.length; i++) collectLocals(node[i], out, path)
194
+ }
195
+
196
+ function lowerGenerator(params, rawBody) {
197
+ const body = Array.isArray(rawBody) && rawBody[0] === ';' ? rawBody.slice(1) : [rawBody]
198
+
199
+ const locals = new Set()
200
+ for (const st of body) collectLocals(st, locals)
201
+
202
+ // ---- state machine ----
203
+ // states[i] = list of statements; terminators are written explicitly as
204
+ // `__s = k` + return/continue shapes. State 0 is the entry; -1 is done.
205
+ const states = []
206
+ const newState = () => (states.push([]), states.length - 1)
207
+ const stmtsOf = (id) => states[id]
208
+ const setState = (id) => [';;set', id] // internal marker, resolved below
209
+ const gotoIR = (id) => [[';;set', id], [';;continue']]
210
+
211
+ // `yield E` at a resume boundary: park the resume id, emit the {value,done:false}
212
+ // return. The resume state optionally starts by binding `target = __sent`.
213
+ const emitYield = (cur, yexpr, target) => {
214
+ const resume = newState()
215
+ const value = yexpr[1] === undefined ? [null, undefined] : transform(yexpr[1])
216
+ stmtsOf(cur).push(
217
+ [';;set', resume],
218
+ ['return', ['{}', [',', [':', 'value', value], [':', 'done', [null, false]]]]])
219
+ if (target) stmtsOf(resume).push(['=', target, S.SENT])
220
+ return resume
221
+ }
222
+
223
+ // yield* E — the delegate loop is nothing but already-supported constructs:
224
+ // sent values thread through (`sent = yield r.value; r = it.next(sent)`),
225
+ // and the delegate's COMPLETION value (final r.value) lands in `target`.
226
+ // E may be any iterable: an ['@@iterator']() provider unwraps first; a
227
+ // plain indexed iterable (array/string) yields element-wise (fork mirrors
228
+ // desugarForOfProtocol; both arms decompose like any generator-body loop).
229
+ const desugarYieldStar = (expr, target) => {
230
+ // the source copies before the @@iterator() unwrap — single-assignment
231
+ // locals keep the probe reads on the precise kind.
232
+ const src = genTemp('yv'), it = genTemp('yi'), r = genTemp('yr'), sent = genTemp('ys'), ix = genTemp('yx')
233
+ locals.add(src); locals.add(it); locals.add(r); locals.add(sent); locals.add(ix)
234
+ const NULL = [null, null]
235
+ return ['{}', [';',
236
+ ['=', src, expr],
237
+ ['=', it, src],
238
+ ['if', ['&&', ['!=', src, NULL], ['!=', ['.', src, '@@iterator'], NULL]],
239
+ ['=', it, ['()', ['.', src, '@@iterator'], null]]],
240
+ ['if', ['&&', ['!=', it, NULL], ['!=', ['.', it, 'next'], NULL]],
241
+ ['{}', [';',
242
+ ['=', r, ['()', ['.', it, 'next'], null]],
243
+ ['while', ['!', ['.', r, 'done']], ['{}', [';',
244
+ ['=', sent, ['yield', ['.', r, 'value']]],
245
+ ['=', r, ['()', ['.', it, 'next'], sent]],
246
+ ]]],
247
+ ...(target ? [['=', target, ['.', r, 'value']]] : []),
248
+ ]],
249
+ ['{}', [';',
250
+ ['=', ix, [null, 0]],
251
+ ['while', ['<', ix, ['.', it, 'length']], ['{}', [';',
252
+ ['yield', ['[]', it, ix]],
253
+ ['=', ix, ['+', ix, [null, 1]]],
254
+ ]]],
255
+ ...(target ? [['=', target, [null, undefined]]] : []),
256
+ ]]],
257
+ ]]
258
+ }
259
+
260
+ // Flatten a statement list into states. Returns the state id control falls
261
+ // into after the list (or null if control never falls through).
262
+ // loopCtx = { cont, brk } target state ids for the innermost decomposed loop.
263
+ const flattenList = (stmts, cur, loopCtx) => {
264
+ for (const st of stmts) {
265
+ if (cur == null) return null // unreachable code after a terminator — drop
266
+ cur = flattenStmt(st, cur, loopCtx)
267
+ }
268
+ return cur
269
+ }
270
+
271
+ const flattenStmt = (st, cur, loopCtx) => {
272
+ if (!Array.isArray(st)) { if (st != null) stmtsOf(cur).push(transform(st)); return cur }
273
+ const op = st[0]
274
+
275
+ // --- yield forms ---
276
+ if (op === 'yield*') return flattenStmt(desugarYieldStar(st[1], null), cur, loopCtx)
277
+ if (op === 'yield') return emitYield(cur, st, null)
278
+ if ((op === 'let' || op === 'const') && st.length === 2 && Array.isArray(st[1]) &&
279
+ st[1][0] === '=' && isYield(st[1][2])) {
280
+ if (st[1][2][0] === 'yield*') return flattenStmt(desugarYieldStar(st[1][2][1], st[1][1]), cur, loopCtx)
281
+ return emitYield(cur, st[1][2], st[1][1])
282
+ }
283
+ if (op === '=' && typeof st[1] === 'string' && isYield(st[2])) {
284
+ if (st[2][0] === 'yield*') return flattenStmt(desugarYieldStar(st[2][1], st[1]), cur, loopCtx)
285
+ return emitYield(cur, st[2], st[1])
286
+ }
287
+
288
+ // --- return ---
289
+ if (op === 'return') {
290
+ const v = st[1] === undefined ? [null, undefined] : transform(st[1])
291
+ stmtsOf(cur).push(
292
+ [';;set', -1],
293
+ ['return', ['{}', [',', [':', 'value', v], [':', 'done', [null, true]]]]])
294
+ return null
295
+ }
296
+
297
+ // --- break/continue of a DECOMPOSED loop ---
298
+ if (op === 'break' && st[1] == null && loopCtx) { stmtsOf(cur).push(...gotoIR(loopCtx.brk)); return null }
299
+ if (op === 'continue' && st[1] == null && loopCtx) { stmtsOf(cur).push(...gotoIR(loopCtx.cont)); return null }
300
+
301
+ // --- compound statements stay atomic only when they carry no yield AND no
302
+ // break/continue that binds a DECOMPOSED loop (the raw op would bind the
303
+ // dispatch while(1) instead — an infinite next()) ---
304
+ if (!hasYield(st) && !(loopCtx && hasFreeJump(st))) {
305
+ // let/const initializers become assignments (names are hoisted)
306
+ if (op === 'let' || op === 'const') {
307
+ for (let i = 1; i < st.length; i++) {
308
+ const d = st[i]
309
+ if (Array.isArray(d) && d[0] === '=') stmtsOf(cur).push(['=', d[1], transform(d[2])])
310
+ }
311
+ return cur
312
+ }
313
+ stmtsOf(cur).push(transform(st))
314
+ return cur
315
+ }
316
+
317
+ // --- yield-bearing control ---
318
+ if (op === 'if') {
319
+ const [, cond, thenB, elseB] = st
320
+ const join = newState()
321
+ const thenS = newState()
322
+ const elseS = elseB != null ? newState() : join
323
+ stmtsOf(cur).push(['if', transform(cond), [';', ...gotoIR(thenS)], [';', ...gotoIR(elseS)]], [';;continue'])
324
+ const tEnd = flattenList(blockStmts(thenB), thenS, loopCtx)
325
+ if (tEnd != null) stmtsOf(tEnd).push(...gotoIR(join))
326
+ if (elseB != null) {
327
+ const eEnd = flattenList(blockStmts(elseB), elseS, loopCtx)
328
+ if (eEnd != null) stmtsOf(eEnd).push(...gotoIR(join))
329
+ }
330
+ return join
331
+ }
332
+ if (op === 'while') {
333
+ const [, cond, bodyB] = st
334
+ const test = newState(), bodyS = newState(), exit = newState()
335
+ stmtsOf(cur).push(...gotoIR(test))
336
+ stmtsOf(test).push(['if', transform(cond), [';', ...gotoIR(bodyS)], [';', ...gotoIR(exit)]], [';;continue'])
337
+ const bEnd = flattenList(blockStmts(bodyB), bodyS, { cont: test, brk: exit })
338
+ if (bEnd != null) stmtsOf(bEnd).push(...gotoIR(test))
339
+ return exit
340
+ }
341
+ if (op === 'do') {
342
+ const [, bodyB, cond] = st
343
+ const bodyS = newState(), test = newState(), exit = newState()
344
+ stmtsOf(cur).push(...gotoIR(bodyS))
345
+ const bEnd = flattenList(blockStmts(bodyB), bodyS, { cont: test, brk: exit })
346
+ if (bEnd != null) stmtsOf(bEnd).push(...gotoIR(test))
347
+ stmtsOf(test).push(['if', transform(cond), [';', ...gotoIR(bodyS)], [';', ...gotoIR(exit)]], [';;continue'])
348
+ return exit
349
+ }
350
+ if (op === 'for') {
351
+ const [, head, bodyB] = st
352
+ // for-of over a KNOWN generator call inside a generator body: desugar to
353
+ // the while-next form first — the result is yield-decomposable.
354
+ if (Array.isArray(head) && head[0] === 'of' && Array.isArray(head[2]) &&
355
+ head[2][0] === '()' && typeof head[2][1] === 'string' && generatorNames?.has(head[2][1])) {
356
+ const localTemp = (t) => { const n = genTemp(t); locals.add(n); return n }
357
+ return flattenStmt(desugarForOfGenerator(head[1], head[2], bodyB, localTemp), cur, loopCtx)
358
+ }
359
+ if (Array.isArray(head) && (head[0] === 'of' || head[0] === 'in'))
360
+ err('generators v1: yield inside for-of/for-in is not supported yet — use an indexed for')
361
+ // C-style [';', init, cond, step] (subscript head shape)
362
+ const [, init, cond, step] = Array.isArray(head) && head[0] === ';' ? head : [';', head, undefined, undefined]
363
+ if (init != null) flattenStmt(init, cur, null) === cur || err('generators v1: yield in a for-init is not supported')
364
+ const test = newState(), bodyS = newState(), stepS = newState(), exit = newState()
365
+ stmtsOf(cur).push(...gotoIR(test))
366
+ stmtsOf(test).push(['if', cond == null ? [null, true] : transform(cond), [';', ...gotoIR(bodyS)], [';', ...gotoIR(exit)]], [';;continue'])
367
+ const bEnd = flattenList(blockStmts(bodyB), bodyS, { cont: stepS, brk: exit })
368
+ if (bEnd != null) stmtsOf(bEnd).push(...gotoIR(stepS))
369
+ if (step != null) stmtsOf(stepS).push(transform(step))
370
+ stmtsOf(stepS).push(...gotoIR(test))
371
+ return exit
372
+ }
373
+ if (op === '{}' || op === ';') return flattenList(blockStmts(st), cur, loopCtx)
374
+ if (op === 'try' || op === 'catch' || op === 'finally')
375
+ err('generators v1: try/catch across a yield is not supported yet')
376
+ err(`generators v1: yield inside \`${op}\` is not supported yet — hoist the yield to statement position`)
377
+ }
378
+
379
+ const blockStmts = (b) =>
380
+ b == null ? []
381
+ : Array.isArray(b) && b[0] === '{}' ? blockStmts(b[1])
382
+ : Array.isArray(b) && b[0] === ';' ? b.slice(1)
383
+ : [b]
384
+
385
+ const entry = newState()
386
+ const end = flattenList(body, entry, null)
387
+ if (end != null) stmtsOf(end).push(
388
+ [';;set', -1],
389
+ ['return', ['{}', [',', [':', 'value', [null, undefined]], [':', 'done', [null, true]]]]])
390
+
391
+ // ---- assemble the dispatch loop ----
392
+ // Internal markers resolve here: [';;set', k] → __s = k; [';;continue'] → continue.
393
+ const resolve = (n) => {
394
+ if (!Array.isArray(n)) return n
395
+ if (n[0] === ';;set') return ['=', S.NEXT, [null, n[1]]]
396
+ if (n[0] === ';;continue') return ['continue']
397
+ return n.map(resolve)
398
+ }
399
+ // if-chain over states (highest → the shape jz compiles tightly)
400
+ let dispatch = ['return', ['{}', [',', [':', 'value', [null, undefined]], [':', 'done', [null, true]]]]]
401
+ for (let i = states.length - 1; i >= 0; i--)
402
+ dispatch = ['if', ['===', S.NEXT, [null, i]], ['{}', [';', ...states[i].map(resolve), ['continue']]], ['{}', [';', dispatch]]]
403
+
404
+ const nextBody = ['{}', [';',
405
+ ['=', S.SENT, '__in'],
406
+ ['while', [null, true], ['{}', [';', dispatch]]],
407
+ ]]
408
+
409
+ const decls = [
410
+ ['let', ['=', S.NEXT, [null, 0]], ['=', S.SENT, [null, undefined]],
411
+ ...[...locals].map(n => ['=', n, [null, undefined]])],
412
+ ['const', ['=', '__next', ['=>', '__in', nextBody]]],
413
+ ]
414
+
415
+ const nextFn = ['=>', '__v', ['()', '__next', '__v']]
416
+ const returnFn = ['=>', '__v', ['{}', [';',
417
+ ['=', S.NEXT, [null, -1]],
418
+ ['return', ['{}', [',', [':', 'value', '__v'], [':', 'done', [null, true]]]]]]]]
419
+ // throw(v): no try may span a yield (v1 rejects it), so every injected
420
+ // exception is unhandled by spec — close the machine, rethrow to the
421
+ // caller of throw() (catchable jz throw).
422
+ const throwFn = ['=>', '__v', ['{}', [';',
423
+ ['=', S.NEXT, [null, -1]],
424
+ ['throw', '__v']]]]
425
+
426
+ // Helper-bearing programs mint through __it_mk (decorated iterator —
427
+ // map/filter/… as value-position methods); others keep the bare record.
428
+ if (iterProto?.helpers) {
429
+ iterProto.helpersUsed = true
430
+ return ['=>', params, ['{}', [';', ...decls,
431
+ ['return', ['()', '__it_mk', [',', nextFn, returnFn, throwFn]]]]]]
432
+ }
433
+ const genObj = ['{}', [',',
434
+ [':', 'next', nextFn],
435
+ [':', 'return', returnFn],
436
+ [':', 'throw', throwFn],
437
+ ]]
438
+
439
+ return ['=>', params, ['{}', [';', ...decls, ['return', genObj]]]]
440
+ }
441
+
442
+ // ---- ES2025 iterator-helper chain fusion ----
443
+ // `g(args).map(f).filter(p).take(n)` rooted at a KNOWN generator call fuses
444
+ // into ONE while-next loop — no intermediate iterator objects. Consuming
445
+ // positions: a for-of head, or a terminal helper (toArray/reduce/forEach/
446
+ // some/every/find) in expression position. A chain stored as a VALUE is out
447
+ // of the v1 model (the object has no helper methods — the known-receiver
448
+ // fail-fast reports it precisely).
449
+ const STAGE_HELPERS = new Set(['map', 'filter', 'take', 'drop'])
450
+ const TERMINAL_HELPERS = new Set(['toArray', 'reduce', 'forEach', 'some', 'every', 'find'])
451
+
452
+ // Unwind `root.h1(a).h2(b)…` → { root: ['()', gen, args], stages: [{h, args}] }
453
+ // when the root is a known generator call; null otherwise.
454
+ function unwindChain(node) {
455
+ const stages = []
456
+ let cur = node
457
+ while (Array.isArray(cur) && cur[0] === '()' && Array.isArray(cur[1]) && cur[1][0] === '.') {
458
+ const helper = cur[1][2]
459
+ if (!STAGE_HELPERS.has(helper) && !TERMINAL_HELPERS.has(helper)) return null
460
+ stages.unshift({ h: helper, args: cur[2] == null ? [] : (Array.isArray(cur[2]) && cur[2][0] === ',' ? cur[2].slice(1) : [cur[2]]) })
461
+ cur = cur[1][1]
462
+ }
463
+ if (!(Array.isArray(cur) && cur[0] === '()' && typeof cur[1] === 'string' && generatorNames?.has(cur[1]))) return null
464
+ return { root: cur, stages }
465
+ }
466
+
467
+ // Compose the per-item body: stage transforms wrap `emit(x)` — the innermost
468
+ // callback receives the final item statements.
469
+ // Returns statements for the while body given (xName, emitStmts).
470
+ function stageBody(stages, x, temp, emitStmts) {
471
+ // build from the last stage outward
472
+ let build = emitStmts
473
+ for (let i = stages.length - 1; i >= 0; i--) {
474
+ const { h, args } = stages[i]
475
+ const inner = build
476
+ if (h === 'map') {
477
+ // spec: fn(value, counter)
478
+ const fn = args[0], c = temp('mc')
479
+ build = () => [['=', x, ['()', fn, [',', x, c]]], ['=', c, ['+', c, [null, 1]]], ...inner()]
480
+ build.decls = [[c, [null, 0]]].concat(inner.decls || [])
481
+ } else if (h === 'filter') {
482
+ const fn = args[0], c = temp('fc'), hv = temp('fh')
483
+ build = () => [
484
+ ['=', hv, ['()', fn, [',', x, c]]],
485
+ ['=', c, ['+', c, [null, 1]]],
486
+ ['if', hv, ['{}', [';', ...inner()]]]]
487
+ build.decls = [[c, [null, 0]], [hv, [null, undefined]]].concat(inner.decls || [])
488
+ } else if (h === 'take') {
489
+ const n = args[0], c = temp('tk')
490
+ build = () => [
491
+ ['if', ['>=', c, n], ['{}', [';', ['break']]]],
492
+ ['=', c, ['+', c, [null, 1]]],
493
+ ...inner()]
494
+ build.decls = [[c, [null, 0]]].concat(inner.decls || [])
495
+ } else if (h === 'drop') {
496
+ const n = args[0], c = temp('dp')
497
+ build = () => [
498
+ ['if', ['<', c, n], ['{}', [';', ['=', c, ['+', c, [null, 1]]], ['continue']]]],
499
+ ...inner()]
500
+ build.decls = [[c, [null, 0]]].concat(inner.decls || [])
501
+ }
502
+ if (!build.decls && inner.decls) build.decls = inner.decls
503
+ }
504
+ return build
505
+ }
506
+
507
+ // Fused loop skeleton: declares it/r (+stage counters), loops next().
508
+ function fusedLoop(root, stages, temp, x, emitStmts, prologue = [], epilogue = []) {
509
+ const it = temp('gi'), r = temp('gr')
510
+ const build = stageBody(stages, x, temp, emitStmts)
511
+ // Pull at the TOP of the body: stage/user `continue` must advance to the
512
+ // NEXT item — a tail-position pull would be skipped and re-process the
513
+ // same value forever (the drop()-stage / user-continue hazard).
514
+ return ['{}', [';',
515
+ ['const', ['=', it, root]],
516
+ ['let', ['=', x, [null, undefined]], ['=', r, [null, undefined]],
517
+ ...(build.decls || []).map(([n, init]) => ['=', n, init])],
518
+ ...prologue,
519
+ ['while', [null, true], ['{}', [';',
520
+ ['=', r, ['()', ['.', it, 'next'], null]],
521
+ ['if', ['.', r, 'done'], ['{}', [';', ['break']]]],
522
+ ['=', x, ['.', r, 'value']],
523
+ ...build(),
524
+ ]]],
525
+ ...epilogue,
526
+ ]]
527
+ }
528
+
529
+ // Terminal helper in EXPRESSION position → IIFE returning the reduction.
530
+ function fuseTerminal(chain, temp) {
531
+ const { root, stages } = chain
532
+ const last = stages[stages.length - 1]
533
+ if (!TERMINAL_HELPERS.has(last.h)) return null
534
+ const mid = stages.slice(0, -1)
535
+ const x = temp('gx'), acc = temp('ga'), cn = temp('gc')
536
+ const T = last.h, A = last.args
537
+ const ret = (v) => ['return', v]
538
+ const bump = ['=', cn, ['+', cn, [null, 1]]]
539
+ // spec: every terminal callback receives (…, value, counter)
540
+ let prologue = [], emit, epilogue
541
+ if (T === 'toArray') {
542
+ prologue = [['let', ['=', acc, ['[]', null]]]]
543
+ emit = () => [['()', ['.', acc, 'push'], x]]
544
+ epilogue = [ret(acc)]
545
+ } else if (T === 'reduce') {
546
+ // no initial value → the first element seeds the accumulator (counter
547
+ // starts at 1 for the first reducer call); empty + no init throws.
548
+ const first = temp('gf')
549
+ const noInit = A[1] === undefined
550
+ prologue = [['let', ['=', cn, [null, 0]], ['=', first, [null, noInit]],
551
+ ['=', acc, noInit ? [null, undefined] : A[1]]]]
552
+ emit = () => [
553
+ ['if', first,
554
+ ['{}', [';', ['=', acc, x], ['=', first, [null, false]]]],
555
+ ['{}', [';', ['=', acc, ['()', A[0], [',', acc, x, cn]]]]]],
556
+ bump]
557
+ epilogue = [
558
+ ...(noInit ? [['if', first, ['throw', [null, 'Reduce of empty iterator with no initial value']]]] : []),
559
+ ret(acc)]
560
+ } else if (T === 'forEach') {
561
+ prologue = [['let', ['=', cn, [null, 0]]]]
562
+ emit = () => [['()', A[0], [',', x, cn]], bump]
563
+ epilogue = [ret([null, undefined])]
564
+ } else if (T === 'some') {
565
+ prologue = [['let', ['=', cn, [null, 0]]]]
566
+ emit = () => [['if', ['()', A[0], [',', x, cn]], ['{}', [';', ret([null, true])]]], bump]
567
+ epilogue = [ret([null, false])]
568
+ } else if (T === 'every') {
569
+ prologue = [['let', ['=', cn, [null, 0]]]]
570
+ emit = () => [['if', ['!', ['()', A[0], [',', x, cn]]], ['{}', [';', ret([null, false])]]], bump]
571
+ epilogue = [ret([null, true])]
572
+ } else if (T === 'find') {
573
+ prologue = [['let', ['=', cn, [null, 0]]]]
574
+ emit = () => [['if', ['()', A[0], [',', x, cn]], ['{}', [';', ret(x)]]], bump]
575
+ epilogue = [ret([null, undefined])]
576
+ } else return null
577
+ const body = fusedLoop(root, mid, temp, x, emit, prologue, epilogue)
578
+ const iife = ['()', ['=>', ['()', null], body], null]
579
+ // a loop-bearing arrow with multiple boolean returns loses bool kind
580
+ // (stringifies as '1') — !! restores it for the predicate terminals
581
+ return T === 'some' || T === 'every' ? ['!', ['!', iife]] : iife
582
+ }
583
+
584
+ // for-of over a KNOWN generator call → while-next desugar (fusion-friendly:
585
+ // the optimizer sees plain closure calls + a fixed-shape result object).
586
+ function desugarForOfGenerator(decl, iterExpr, body, temp) {
587
+ const it = temp('gi'), r = temp('gr')
588
+ const name = Array.isArray(decl) ? decl[1] : decl
589
+ // Pull at the TOP: a user `continue` in the body must advance the iterator
590
+ // (a tail pull would be skipped — infinite loop on the same item).
591
+ return ['{}', [';',
592
+ ['const', ['=', it, iterExpr]],
593
+ ['let', ['=', r, [null, undefined]]],
594
+ ['while', [null, true], ['{}', [';',
595
+ ['=', r, ['()', ['.', it, 'next'], null]],
596
+ ['if', ['.', r, 'done'], ['{}', [';', ['break']]]],
597
+ ['let', ['=', name, ['.', r, 'value']]],
598
+ ...(Array.isArray(body) && body[0] === ';' ? body.slice(1) : [body]),
599
+ ]]],
600
+ ]]
601
+ }
602
+
603
+ // for-of over an UNKNOWN source in a program that mints iterators: unwrap a
604
+ // ['@@iterator']() provider, then a callable `next` drives the machine
605
+ // LAZILY (pull-at-top — a `break` stops pulling, spec-faithful); anything
606
+ // else falls to the indexed array path. 'of-idx' marks that arm so the fork
607
+ // doesn't re-enter. The probes run once per loop, not per iteration, and
608
+ // programs without iterator producers never take this shape (iterProto
609
+ // gate) — they compile byte-identically.
610
+ function desugarForOfProtocol(decl, iterExpr, body, temp) {
611
+ // `w` starts as a COPY of the source and takes the @@iterator() result —
612
+ // the source local stays single-assignment so its precise kind keeps
613
+ // serving the probe reads above the fork.
614
+ const v = temp('gv'), w = temp('gw'), r = temp('gr')
615
+ const NULL = [null, null]
616
+ const isDecl = Array.isArray(decl) && (decl[0] === 'let' || decl[0] === 'const')
617
+ const bind = isDecl ? [decl[0], ['=', decl[1], ['.', r, 'value']]] : ['=', decl, ['.', r, 'value']]
618
+ const bodyStmts = Array.isArray(body) && body[0] === ';' ? body.slice(1) : [body]
619
+ return ['{}', [';',
620
+ ['let', ['=', v, iterExpr]],
621
+ ['let', ['=', w, v]],
622
+ ['if', ['&&', ['!=', v, NULL], ['!=', ['.', v, '@@iterator'], NULL]],
623
+ ['=', w, ['()', ['.', v, '@@iterator'], null]]],
624
+ ['if', ['&&', ['!=', w, NULL], ['!=', ['.', w, 'next'], NULL]],
625
+ ['{}', [';',
626
+ ['let', ['=', r, [null, undefined]]],
627
+ ['while', [null, true], ['{}', [';',
628
+ ['=', r, ['()', ['.', w, 'next'], null]],
629
+ ['if', ['.', r, 'done'], ['{}', [';', ['break']]]],
630
+ bind,
631
+ ...bodyStmts,
632
+ ]]],
633
+ ]],
634
+ ['for', ['of-idx', decl, w], body]],
635
+ ]]
636
+ }
637
+
638
+ return { lowerGenerator, desugarForOfGenerator, desugarForOfProtocol, unwindChain, fuseTerminal, fusedLoop, isTerminal: (h) => TERMINAL_HELPERS.has(h) }
639
+ }