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
package/jzify/async.js ADDED
@@ -0,0 +1,409 @@
1
+ /**
2
+ * async/await lowering — the generator machinery driving a plain-jz promise
3
+ * runtime. No engine event loop, no stdlib/WAT additions: an async function
4
+ * body lowers to the SAME state machine as function* (await ≡ yield), and a
5
+ * driver (__async_run) steps it, parking on awaited promises. Promises are
6
+ * fixed-shape objects; `then` callbacks queue as closures in a module-level
7
+ * microtask array drained at host boundaries (export return, timer tick) —
8
+ * the host wrapper (interop.js) calls the exported __mt_drain and settles
9
+ * host Promises for async exports.
10
+ *
11
+ * v1 surface (precise rejects elsewhere): no try/catch across an await (the
12
+ * machine constraint — an awaited rejection rejects the async function);
13
+ * `new Promise(executor)`, `Promise.resolve/reject/all/race/allSettled/any/
14
+ * try/withResolvers`, `.then/.catch/.finally` chains all work (canonicalized
15
+ * to the injected helpers). AggregateError surfaces as a fixed-shape
16
+ * `{ name, message, errors }` value (errors are untagged in jz).
17
+ * Divergences (documented): unhandled rejections don't report; job ordering
18
+ * is per-drain-cycle (boundary/timer granularity), not per-continuation.
19
+ *
20
+ * Pay-per-use: nothing below is injected unless the program contains async
21
+ * source; sync programs compile byte-identically.
22
+ *
23
+ * @module jzify/async
24
+ */
25
+
26
+ // The runtime, as readable jz source — parsed + spliced ahead of user code
27
+ // when async is present. Exported entries are the host-boundary contract.
28
+ export const ASYNC_RUNTIME = `
29
+ let __mt = []
30
+ let __sq = []
31
+ let __drain = () => {
32
+ while (__mt.length > 0 || __sq.length > 0) {
33
+ while (__mt.length > 0) { let cb = __mt.shift(); cb() }
34
+ if (__sq.length > 0) {
35
+ let p = __sq.shift()
36
+ let cbs = p.cbs
37
+ p.cbs = []
38
+ let st = p.st
39
+ let v = p.val
40
+ for (let i = 0; i < cbs.length; i++) { let cb = cbs[i]; cb(st, v) }
41
+ }
42
+ }
43
+ }
44
+ let __state = (p) => p != null && typeof p === 'object' && p.__p === 1 ? p.st : -1
45
+ let __value = (p) => p.val
46
+ let __p_new = () => {
47
+ let p = { __p: 1, st: 0, rs: 0, val: undefined, cbs: [], then: undefined, catch: undefined, finally: undefined }
48
+ p.then = (ok, err) => {
49
+ let q = __p_new()
50
+ __p_sub(p, (st, v) => {
51
+ // non-callable handlers are ignored (spec: fulfillment/rejection pass through)
52
+ if (st === 1) { if (ok == null || typeof ok !== 'function') { __p_settle(q, 1, v) } else { try { __p_settle(q, 1, ok(v)) } catch (e) { __p_settle(q, 2, e) } } }
53
+ else { if (err == null || typeof err !== 'function') { __p_settle(q, 2, v) } else { try { __p_settle(q, 1, err(v)) } catch (e2) { __p_settle(q, 2, e2) } } }
54
+ })
55
+ return q
56
+ }
57
+ p.catch = (err) => p.then(undefined, err)
58
+ p.finally = (fn) => p.then((v) => { fn(); return v }, (e) => { fn(); throw e })
59
+ return p
60
+ }
61
+ let __p_sub = (p, h) => {
62
+ if (p.st > 0) { let st = p.st, v = p.val; __mt.push(() => h(st, v)) }
63
+ else p.cbs.push(h)
64
+ }
65
+ let __p_fin = (p, st, v) => {
66
+ if (p.st > 0) return
67
+ p.st = st
68
+ p.val = v
69
+ if (p.cbs.length > 0) __sq.push(p)
70
+ }
71
+ let __p_settle = (p, st, v) => {
72
+ if (p.st > 0 || p.rs === 1) return
73
+ if (st === 1 && v != null && typeof v === 'object') {
74
+ if (v.__p === 1) { p.rs = 1; __p_sub(v, (st2, v2) => __p_fin(p, st2, v2)); return }
75
+ // plain-object thenable (spec: any object with callable then) — adopt in a
76
+ // job; the resolve/reject pair shares one already-called latch, and a
77
+ // then() throw after that latch is ignored (25.4.1.3.2).
78
+ if (typeof v.then === 'function') {
79
+ p.rs = 1
80
+ __mt.push(() => {
81
+ let done = 0
82
+ try {
83
+ v.then(
84
+ (x) => { if (done) return; done = 1; p.rs = 0; __p_settle(p, 1, x) },
85
+ (e) => { if (done) return; done = 1; __p_fin(p, 2, e) })
86
+ } catch (e2) { if (done === 0) __p_fin(p, 2, e2) }
87
+ })
88
+ return
89
+ }
90
+ }
91
+ __p_fin(p, st, v)
92
+ }
93
+ let __await = (v, ok, err) => {
94
+ if (v != null && typeof v === 'object' && (v.__p === 1 || typeof v.then === 'function'))
95
+ __p_sub(__p_resolve(v), (st, x) => { if (st === 1) ok(x); else err(x) })
96
+ else { __mt.push(() => ok(v)) }
97
+ }
98
+ let __async_run = (it) => {
99
+ let p = __p_new()
100
+ let onstep = (r) => {
101
+ if (r.done) __p_settle(p, 1, r.value)
102
+ else __await(r.value,
103
+ (v) => { let r2; try { r2 = it.next(v) } catch (e) { __p_settle(p, 2, e); return } onstep(r2) },
104
+ (e) => { it.return(undefined); __p_settle(p, 2, e) })
105
+ }
106
+ let r0
107
+ try { r0 = it.next() } catch (e) { __p_settle(p, 2, e); return p }
108
+ onstep(r0)
109
+ return p
110
+ }
111
+ let __p_exec = (fn) => {
112
+ if (fn == null || typeof fn !== 'function') throw 'Promise executor is not callable'
113
+ let p = __p_new()
114
+ let done = 0
115
+ try {
116
+ fn((v) => { if (done) return; done = 1; __p_settle(p, 1, v) },
117
+ (e) => { if (done) return; done = 1; __p_settle(p, 2, e) })
118
+ } catch (e2) { if (done === 0) __p_settle(p, 2, e2) }
119
+ return p
120
+ }
121
+ let __p_resolve = (v) => {
122
+ if (v != null && typeof v === 'object' && v.__p === 1) return v
123
+ let p = __p_new(); __p_settle(p, 1, v); return p
124
+ }
125
+ let __p_reject = (e) => { let p = __p_new(); __p_settle(p, 2, e); return p }
126
+ // GetIterator for the combinators: arrays pass through, iterator-protocol
127
+ // values drain, anything else (non-iterable input, or an @@iterator that is
128
+ // non-callable / returns a non-object) yields null → the combinator REJECTS
129
+ // with a TypeError value instead of resolving garbage.
130
+ let __p_list = (v) => {
131
+ if (v == null) return null
132
+ if (typeof v === 'string') return v.split('')
133
+ if (typeof v !== 'object') return null
134
+ let w = v
135
+ if (w['@@iterator'] != null) {
136
+ if (typeof w['@@iterator'] !== 'function') return null
137
+ w = w['@@iterator']()
138
+ }
139
+ if (w != null && typeof w === 'object' && typeof w.next === 'function') {
140
+ let a = [], r = w.next()
141
+ while (!r.done) { a.push(r.value); r = w.next() }
142
+ return a
143
+ }
144
+ if (w != null && typeof w === 'object' && w.length != null) return w
145
+ return null
146
+ }
147
+ let __p_all = (arr) => {
148
+ let p = __p_new()
149
+ let a = __p_list(arr)
150
+ if (a == null) { __p_settle(p, 2, 'TypeError: Promise.all argument is not iterable'); return p }
151
+ let n = a.length, out = [], left = n
152
+ if (n === 0) { __p_settle(p, 1, out); return p }
153
+ for (let i = 0; i < n; i++) {
154
+ let k = i
155
+ __await(a[k], (v) => { out[k] = v; left--; if (left === 0) __p_settle(p, 1, out) }, (e) => __p_settle(p, 2, e))
156
+ }
157
+ return p
158
+ }
159
+ let __p_race = (arr) => {
160
+ let p = __p_new()
161
+ let a = __p_list(arr)
162
+ if (a == null) { __p_settle(p, 2, 'TypeError: Promise.race argument is not iterable'); return p }
163
+ for (let i = 0; i < a.length; i++) __await(a[i], (v) => __p_settle(p, 1, v), (e) => __p_settle(p, 2, e))
164
+ return p
165
+ }
166
+ let __p_allSettled = (arr) => {
167
+ let p = __p_new()
168
+ let a = __p_list(arr)
169
+ if (a == null) { __p_settle(p, 2, 'TypeError: Promise.allSettled argument is not iterable'); return p }
170
+ let n = a.length, out = [], left = n
171
+ if (n === 0) { __p_settle(p, 1, out); return p }
172
+ for (let i = 0; i < n; i++) {
173
+ let k = i
174
+ __await(a[k],
175
+ (v) => { out[k] = { status: 'fulfilled', value: v, reason: undefined }; left--; if (left === 0) __p_settle(p, 1, out) },
176
+ (e) => { out[k] = { status: 'rejected', value: undefined, reason: e }; left--; if (left === 0) __p_settle(p, 1, out) })
177
+ }
178
+ return p
179
+ }
180
+ let __p_any = (arr) => {
181
+ let p = __p_new()
182
+ let a = __p_list(arr)
183
+ if (a == null) { __p_settle(p, 2, 'TypeError: Promise.any argument is not iterable'); return p }
184
+ let n = a.length, errs = [], left = n
185
+ if (n === 0) { __p_settle(p, 2, { name: 'AggregateError', message: 'All promises were rejected', errors: errs }); return p }
186
+ for (let i = 0; i < n; i++) {
187
+ let k = i
188
+ __await(a[k], (v) => __p_settle(p, 1, v),
189
+ (e) => { errs[k] = e; left--; if (left === 0) __p_settle(p, 2, { name: 'AggregateError', message: 'All promises were rejected', errors: errs }) })
190
+ }
191
+ return p
192
+ }
193
+ let __p_try = (fn, ...aa) => {
194
+ let p = __p_new()
195
+ try { __p_settle(p, 1, fn(...aa)) } catch (e) { __p_settle(p, 2, e) }
196
+ return p
197
+ }
198
+ let __p_withResolvers = () => {
199
+ let p = __p_new()
200
+ return { promise: p, resolve: (v) => __p_settle(p, 1, v), reject: (e) => __p_settle(p, 2, e) }
201
+ }
202
+ export let __mt_drain = () => __drain()
203
+ export let __p_state = (p) => __state(p)
204
+ export let __p_value = (p) => __value(p)
205
+ export let __p_make = () => __p_new()
206
+ export let __p_finish = (p, st, v) => __p_settle(p, st, v)
207
+ `
208
+
209
+ // Async generators — the SAME sync machine, with TAGGED yields: the lowered
210
+ // body yields { a: 1, v } where the source awaited and { a: 0, v } where it
211
+ // yielded, and __ag_run drives the machine, parking on awaited promises and
212
+ // resolving each next() with a { value, done } record. next() calls serialize
213
+ // through a per-instance queue (spec: requests queue while a step is inflight).
214
+ // Injected only when a program contains async generators / for-await.
215
+ export const ASYNC_GEN_RUNTIME = `
216
+ let __ag_fin = (st, p, ok, v) => { __p_settle(p, ok, v); st.b = 0; __ag_kick(st) }
217
+ let __ag_step = (st, g, p, r) => {
218
+ if (r.done) { __ag_fin(st, p, 1, { value: r.value, done: true }); return }
219
+ let t = r.value
220
+ // AsyncGeneratorYield AWAITS the yielded value first — yielding a rejected
221
+ // promise rejects the pending next() and closes the machine.
222
+ if (t.a === 0) {
223
+ __await(t.v,
224
+ (v) => __ag_fin(st, p, 1, { value: v, done: false }),
225
+ (e) => { g.return(undefined); __ag_fin(st, p, 2, e) })
226
+ return
227
+ }
228
+ __await(t.v,
229
+ (v) => {
230
+ let r2
231
+ try { r2 = g.next(v) } catch (e) { __ag_fin(st, p, 2, e); return }
232
+ __ag_step(st, g, p, r2)
233
+ },
234
+ (e) => { g.return(undefined); __ag_fin(st, p, 2, e) })
235
+ }
236
+ let __ag_kick = (st) => {
237
+ if (st.b === 1) return
238
+ if (st.q.length === 0) return
239
+ st.b = 1
240
+ let job = st.q.shift()
241
+ let r
242
+ try { r = job.g.next(job.v) } catch (e) { st.b = 0; __p_settle(job.p, 2, e); __ag_kick(st); return }
243
+ __ag_step(st, job.g, job.p, r)
244
+ }
245
+ let __ag_run = (g) => {
246
+ let st = { b: 0, q: [] }
247
+ let ag = { next: undefined, return: undefined, throw: undefined, '@@asyncIterator': undefined }
248
+ ag.next = (v) => { let p = __p_new(); st.q.push({ g: g, p: p, v: v }); __ag_kick(st); return p }
249
+ ag.return = (v) => { let p = __p_new(); let r = g.return(v); __p_settle(p, 1, { value: r.value, done: true }); return p }
250
+ ag.throw = (e) => { let p = __p_new(); try { g.throw(e) } catch (x) { __p_settle(p, 2, x) } return p }
251
+ ag[Symbol.asyncIterator] = () => ag
252
+ return ag
253
+ }
254
+ `
255
+
256
+ export function createAsyncLowering({ genTemp, err }) {
257
+ let used = false
258
+ let agUsed = false
259
+
260
+ // await → yield inside THIS function body only (nested function forms keep
261
+ // their own await/this rules; a stray await inside a nested sync fn falls
262
+ // through to prepare's clean reject).
263
+ const FN_OPS = new Set(['=>', 'function', 'function*', 'class', 'async'])
264
+
265
+ // `for await (decl of src)` → protocol loop in terms of PLAIN await: unwrap
266
+ // @@asyncIterator (else @@iterator), drive next() through await, and await
267
+ // each element (sync-source values may be promises; awaiting an async
268
+ // iterator's already-resolved value is a harmless pass-through). Non-protocol
269
+ // sources fall to an indexed loop with per-element await. The result is
270
+ // machine-lowerable by the same v1 yield surface as any while loop.
271
+ const NULL = [null, null]
272
+ function desugarForAwait(head, body) {
273
+ const [, decl, srcExpr] = head
274
+ const src = genTemp('fa'), it = genTemp('fi'), r = genTemp('fr'), ix = genTemp('fx')
275
+ const isDecl = Array.isArray(decl) && (decl[0] === 'let' || decl[0] === 'const')
276
+ const name = isDecl ? decl[1] : decl
277
+ // the loop binding declares ONCE before the protocol fork (both arms would
278
+ // otherwise re-declare it — the machine hoists locals and rejects dupes)
279
+ const bind = (valExpr) => ['=', name, ['await', valExpr]]
280
+ const bodyStmts = Array.isArray(body) && body[0] === ';' ? body.slice(1) : [body]
281
+ return ['{}', [';',
282
+ ...(isDecl ? [['let', ['=', name, [null, undefined]]]] : []),
283
+ ['let', ['=', src, srcExpr]],
284
+ ['let', ['=', it, src]],
285
+ ['if', ['&&', ['!=', src, NULL], ['!=', ['.', src, '@@asyncIterator'], NULL]],
286
+ ['=', it, ['()', ['.', src, '@@asyncIterator'], null]],
287
+ ['if', ['&&', ['!=', src, NULL], ['!=', ['.', src, '@@iterator'], NULL]],
288
+ ['=', it, ['()', ['.', src, '@@iterator'], null]]]],
289
+ ['if', ['&&', ['!=', it, NULL], ['!=', ['.', it, 'next'], NULL]],
290
+ ['{}', [';',
291
+ ['let', ['=', r, ['await', ['()', ['.', it, 'next'], null]]]],
292
+ ['while', ['!', ['.', r, 'done']], ['{}', [';',
293
+ bind(['.', r, 'value']),
294
+ ...bodyStmts,
295
+ ['=', r, ['await', ['()', ['.', it, 'next'], null]]],
296
+ ]]],
297
+ ]],
298
+ ['{}', [';',
299
+ ['let', ['=', ix, [null, 0]]],
300
+ ['while', ['<', ix, ['.', it, 'length']], ['{}', [';',
301
+ bind(['[]', it, ix]),
302
+ ['=', ix, ['+', ix, [null, 1]]],
303
+ ...bodyStmts,
304
+ ]]],
305
+ ]]],
306
+ ]]
307
+ }
308
+
309
+ function mapAwait(node) {
310
+ if (!Array.isArray(node)) return node
311
+ if (FN_OPS.has(node[0])) return node
312
+ if (node[0] === 'for await' && Array.isArray(node[1]) && node[1][0] === 'of')
313
+ return mapAwait(desugarForAwait(node[1], node[2]))
314
+ if (node[0] === 'await') return ['yield', mapAwait(node[1])]
315
+ if (node[0] === 'try' && refsAwait(node))
316
+ err('try/catch across `await` is outside the v1 async surface — let the rejection reject the async function, or move the try into a sync helper')
317
+ return node.map((n, i) => i === 0 ? n : mapAwait(n))
318
+ }
319
+ function refsAwait(node) {
320
+ if (!Array.isArray(node)) return false
321
+ if (FN_OPS.has(node[0])) return false
322
+ if (node[0] === 'await' || node[0] === 'for await') return true
323
+ return node.some(refsAwait)
324
+ }
325
+ function refsSuspend(node) {
326
+ if (!Array.isArray(node)) return false
327
+ if (FN_OPS.has(node[0])) return false
328
+ if (node[0] === 'await' || node[0] === 'for await' || node[0] === 'yield' || node[0] === 'yield*') return true
329
+ return node.some(refsSuspend)
330
+ }
331
+
332
+ // async generator body → tagged-yield machine body: `await E` suspends as
333
+ // { a: 1, v: E } (driver resumes with the resolved value), `yield E` as
334
+ // { a: 0, v: E } (driver resolves next() and resumes with the sent value).
335
+ // `yield*`/for-await desugar into plain await/yield loops first and recurse.
336
+ const tag = (a, v) => ['yield', ['{}', [',', [':', 'a', [null, a]], [':', 'v', v]]]]
337
+ function mapAgen(node) {
338
+ if (!Array.isArray(node)) return node
339
+ if (FN_OPS.has(node[0])) return node
340
+ if (node[0] === 'for await' && Array.isArray(node[1]) && node[1][0] === 'of')
341
+ return mapAgen(desugarForAwait(node[1], node[2]))
342
+ if (node[0] === 'yield*') return mapAgen(desugarYieldStarAsync(node[1], null))
343
+ if (node[0] === 'await') return tag(1, mapAgen(node[1]))
344
+ if (node[0] === 'yield') return node[1] === undefined ? tag(0, [null, undefined]) : tag(0, mapAgen(node[1]))
345
+ if (node[0] === 'try' && refsSuspend(node))
346
+ err('try/catch across `await`/`yield` is outside the v1 async-generator surface — let the rejection reject, or move the try into a sync helper')
347
+ return node.map((n, i) => i === 0 ? n : mapAgen(n))
348
+ }
349
+
350
+ // `yield* E` inside an async generator: delegate through await'd next()
351
+ // (async or sync sources both work — __await passes plain values through).
352
+ function desugarYieldStarAsync(expr) {
353
+ const src = genTemp('ya'), it = genTemp('yb'), r = genTemp('yc'), sent = genTemp('yd'), ix = genTemp('ye')
354
+ return ['{}', [';',
355
+ ['let', ['=', src, expr]],
356
+ ['let', ['=', it, src]],
357
+ ['if', ['&&', ['!=', src, NULL], ['!=', ['.', src, '@@asyncIterator'], NULL]],
358
+ ['=', it, ['()', ['.', src, '@@asyncIterator'], null]],
359
+ ['if', ['&&', ['!=', src, NULL], ['!=', ['.', src, '@@iterator'], NULL]],
360
+ ['=', it, ['()', ['.', src, '@@iterator'], null]]]],
361
+ ['if', ['&&', ['!=', it, NULL], ['!=', ['.', it, 'next'], NULL]],
362
+ ['{}', [';',
363
+ ['let', ['=', r, ['await', ['()', ['.', it, 'next'], null]]]],
364
+ ['while', ['!', ['.', r, 'done']], ['{}', [';',
365
+ ['let', ['=', sent, ['yield', ['.', r, 'value']]]],
366
+ ['=', r, ['await', ['()', ['.', it, 'next'], sent]]],
367
+ ]]],
368
+ ]],
369
+ // indexed fallback (arrays/strings): each element rides the same
370
+ // tagged yield, so a rejected element rejects next() and closes.
371
+ ['{}', [';',
372
+ ['let', ['=', ix, [null, 0]]],
373
+ ['while', ['<', ix, ['.', it, 'length']], ['{}', [';',
374
+ ['yield', ['[]', it, ix]],
375
+ ['=', ix, ['+', ix, [null, 1]]],
376
+ ]]],
377
+ ]]],
378
+ ]]
379
+ }
380
+
381
+ // async (params) => body / async function (params) { body } →
382
+ // (...aa) => __async_run(MACHINE_FACTORY(...aa))
383
+ // The factory is the standard generator lowering of the await-mapped body.
384
+ function lowerAsync(params, body) {
385
+ used = true
386
+ // Source-level desugar: (...aa) => __async_run((function* (params) { mappedBody })(...aa))
387
+ // The function* expression rides the standard generator lowering; the body
388
+ // runs synchronously to the first await (spec), then parks on the promise.
389
+ const aa = genTemp('aa')
390
+ return ['=>', ['()', ['...', aa]],
391
+ ['()', '__async_run', ['()', ['function*', null, params, mapAwait(body)], ['...', aa]]]]
392
+ }
393
+
394
+ // async function* (params) { body } → (...aa) => __ag_run(TAGGED_MACHINE(...aa))
395
+ function lowerAsyncGen(params, body) {
396
+ used = true
397
+ agUsed = true
398
+ const aa = genTemp('ag')
399
+ return ['=>', ['()', ['...', aa]],
400
+ ['()', '__ag_run', ['()', ['function*', null, params, mapAgen(body)], ['...', aa]]]]
401
+ }
402
+
403
+ return {
404
+ lowerAsync, lowerAsyncGen,
405
+ noteAsync: () => { used = true },
406
+ asyncUsed: () => used, agenUsed: () => agUsed,
407
+ resetAsync: () => { used = false; agUsed = false },
408
+ }
409
+ }
package/jzify/classes.js CHANGED
@@ -6,7 +6,7 @@
6
6
  import { extractParams as paramList, objectLiteralEntries } from '../src/ast.js'
7
7
  import { err } from '../src/ctx.js'
8
8
 
9
- export function createClassLowering({ transform, names, JC }) {
9
+ export function createClassLowering({ transform, names, JC, constStrings }) {
10
10
  // === class lowering ===
11
11
  //
12
12
  // A class is lowered to a factory arrow. Instance state is a plain object;
@@ -78,11 +78,17 @@ function literalStringKey(node) {
78
78
  return Array.isArray(node) && node[0] == null && typeof node[1] === 'string' ? node[1] : null
79
79
  }
80
80
 
81
- function constStringKey(node) {
81
+ function constStringKey(node, constStrings) {
82
82
  if (typeof node === 'string') return node
83
83
  const lit = literalStringKey(node)
84
84
  if (lit != null) return lit
85
- if (Array.isArray(node) && node[0] === '[]') return literalStringKey(node[1])
85
+ if (Array.isArray(node) && node[0] === '[]') {
86
+ const inner = literalStringKey(node[1])
87
+ if (inner != null) return inner
88
+ // [K] where K is a module-scope `const K = 'name'` — fold the binding
89
+ // (collected by jzify's entry prepass; const guarantees no reassignment).
90
+ if (typeof node[1] === 'string') return constStrings?.get(node[1]) ?? null
91
+ }
86
92
  return null
87
93
  }
88
94
 
@@ -186,30 +192,40 @@ function lowerClass(name, heritage, body) {
186
192
  for (const it of classBodyItems(body)) {
187
193
  if (typeof it === 'string') { fields.push([it, null]); continue } // bare `x;`
188
194
  if (!Array.isArray(it)) continue
189
- const bareFieldName = constStringKey(it)
195
+ const bareFieldName = constStringKey(it, constStrings)
190
196
  if (bareFieldName != null) { fields.push([bareFieldName, null]); continue }
191
197
  if (it[0] === ':' && Array.isArray(it[2]) && it[2][0] === '=>') {
192
- const key = constStringKey(it[1])
198
+ const key = constStringKey(it[1], constStrings)
193
199
  if (key == null) jzifyError(JC.computedMember)
194
- if (key === 'constructor') { ctorParams = it[2][1]; ctorBody = it[2][2] }
200
+ if (key === 'constructor' && typeof it[1] === 'string') { ctorParams = it[2][1]; ctorBody = it[2][2] }
195
201
  else methods.push([key, it[2][1], it[2][2]])
196
202
  continue
197
203
  }
204
+ // Generator method `*g() {}` — value is a function* expression (parser emits
205
+ // [':', key, ['function*', null, rawParams, body]]); lowers to the factory
206
+ // arrow via the standard generator lowering, `this` renamed like any method.
207
+ if (it[0] === ':' && Array.isArray(it[2]) && it[2][0] === 'function*') {
208
+ const key = constStringKey(it[1], constStrings)
209
+ if (key == null) jzifyError(JC.computedMember)
210
+ if (key === 'constructor' && typeof it[1] === 'string') jzifyError('`constructor` cannot be a generator')
211
+ methods.push([key, it[2][2], it[2][3], 'gen'])
212
+ continue
213
+ }
198
214
  if (it[0] === '=') {
199
215
  const lhs = it[1]
200
216
  if (Array.isArray(lhs) && lhs[0] === 'static') {
201
- const key = constStringKey(lhs[1])
217
+ const key = constStringKey(lhs[1], constStrings)
202
218
  if (key == null) jzifyError(JC.computedStaticField)
203
219
  statics.push([key, it[2]])
204
220
  continue
205
221
  }
206
- const key = constStringKey(lhs)
222
+ const key = constStringKey(lhs, constStrings)
207
223
  if (key == null) jzifyError(JC.computedField)
208
224
  fields.push([key, it[2]])
209
225
  continue
210
226
  }
211
227
  if (it[0] === 'static') {
212
- const key = constStringKey(it[1])
228
+ const key = constStringKey(it[1], constStrings)
213
229
  if (key != null) {
214
230
  statics.push([key, null])
215
231
  continue
@@ -220,11 +236,22 @@ function lowerClass(name, heritage, body) {
220
236
  continue
221
237
  }
222
238
  if (it[0] === 'static' && Array.isArray(it[1]) && it[1][0] === ':' && Array.isArray(it[1][2]) && it[1][2][0] === '=>') {
223
- const key = constStringKey(it[1][1])
239
+ const key = constStringKey(it[1][1], constStrings)
224
240
  if (key == null) jzifyError(JC.computedStaticMember)
225
241
  statics.push([key, it[1][2], true])
226
242
  continue
227
243
  }
244
+ if (it[0] === 'static' && Array.isArray(it[1]) && it[1][0] === ':' && Array.isArray(it[1][2]) && it[1][2][0] === 'function*') {
245
+ const key = constStringKey(it[1][1], constStrings)
246
+ if (key == null) jzifyError(JC.computedStaticMember)
247
+ statics.push([key, it[1][2], 'gen'])
248
+ continue
249
+ }
250
+ if (it[0] === 'static' && Array.isArray(it[1]) && it[1][0] === '{}') {
251
+ // static initialization block — runs in class-init order, `this` = class
252
+ statics.push([null, it[1], 'block'])
253
+ continue
254
+ }
228
255
  if (it[0] === 'get' || it[0] === 'set') jzifyError(JC.accessor)
229
256
  if (it[0] === 'static') jzifyError(JC.staticMember)
230
257
  jzifyError(`unsupported class member ${JSON.stringify(it).slice(0, 60)}`)
@@ -254,8 +281,10 @@ function lowerClass(name, heritage, body) {
254
281
  if (init != null && !usesThis(init)) litProps.push([':', fname, transform(init)])
255
282
  else { litProps.push([':', fname, UNDEF]); if (init != null) deferred.push([fname, init]) }
256
283
  }
257
- for (const [mname, mparams, mbody] of methods)
258
- litProps.push([':', mname, transform(['=>', mparams ?? ['()', null], block(renameThis(mbody, self))])])
284
+ for (const [mname, mparams, mbody, gen] of methods)
285
+ litProps.push([':', mname, gen
286
+ ? transform(['function*', null, mparams, renameThis(mbody, self)])
287
+ : transform(['=>', mparams ?? ['()', null], block(renameThis(mbody, self))])])
259
288
  const lit = ['{}', litProps.length === 0 ? null : litProps.length === 1 ? litProps[0] : [',', ...litProps]]
260
289
  let params = ctorParams ?? ['()', null]
261
290
  const dynamicBase = heritage != null && typeof heritage !== 'string'
@@ -278,8 +307,10 @@ function lowerClass(name, heritage, body) {
278
307
  }
279
308
  for (const [fname, init] of fields)
280
309
  stmts.push(['=', ['.', self, fname], init != null ? transform(renameThis(rewriteSuperMethodCalls(init, superMethodVars), self)) : UNDEF])
281
- for (const [mname, mparams, mbody] of methods)
282
- stmts.push(['=', ['.', self, mname], transform(['=>', mparams ?? ['()', null], block(renameThis(rewriteSuperMethodCalls(mbody, superMethodVars), self))])])
310
+ for (const [mname, mparams, mbody, gen] of methods)
311
+ stmts.push(['=', ['.', self, mname], gen
312
+ ? transform(['function*', null, mparams, renameThis(rewriteSuperMethodCalls(mbody, superMethodVars), self)])
313
+ : transform(['=>', mparams ?? ['()', null], block(renameThis(rewriteSuperMethodCalls(mbody, superMethodVars), self))])])
283
314
  ctorBody = rewriteSuperMethodCalls(ctorBody, superMethodVars)
284
315
  if (defaultArgs) params = ['()', defaultArgs.length === 1 ? defaultArgs[0] : [',', ...defaultArgs]]
285
316
  } else {
@@ -304,10 +335,19 @@ function lowerClass(name, heritage, body) {
304
335
  const staticStmts = []
305
336
  if (dynamicBase) staticStmts.push(['let', ['=', baseRef, transform(heritage)]])
306
337
  staticStmts.push(['let', ['=', cls, factory]])
307
- for (const [sname, value, isMethod] of statics) {
308
- const rhs = isMethod
309
- ? transform(['=>', value[1], block(renameThis(value[2], cls))])
310
- : value == null ? UNDEF : transform(renameThis(value, cls))
338
+ for (const [sname, value, kind] of statics) {
339
+ if (kind === 'block') {
340
+ let b = transform(renameThis(value, cls))
341
+ if (Array.isArray(b) && b[0] === '{}') b = b[1]
342
+ if (Array.isArray(b) && b[0] === ';') staticStmts.push(...b.slice(1).filter(x => x != null))
343
+ else if (b != null) staticStmts.push(b)
344
+ continue
345
+ }
346
+ const rhs = kind === 'gen'
347
+ ? transform(['function*', null, value[2], renameThis(value[3], cls)])
348
+ : kind
349
+ ? transform(['=>', value[1], block(renameThis(value[2], cls))])
350
+ : value == null ? UNDEF : transform(renameThis(value, cls))
311
351
  staticStmts.push(['=', ['.', cls, sname], rhs])
312
352
  }
313
353
  staticStmts.push(['return', cls])
@@ -326,3 +366,81 @@ function lowerArrayConstructor(arg) {
326
366
 
327
367
  return { lowerClass, lowerObjectLiteralThis, lowerArrayConstructor }
328
368
  }
369
+
370
+ // ── Pseudo-classical fold ────────────────────────────────────────────────────
371
+ // `function P(x) { this.x = x }` + `P.prototype.m = function (…) {…}` siblings
372
+ // fold into the class lowering (`class P { constructor(x){…} m(…){…} }`) — the
373
+ // single biggest `this`-blocker in pre-class npm code. Method-by-method
374
+ // prototype assignments with FUNCTION values only: an arrow RHS keeps lexical
375
+ // `this` (folding it would rebind), and a whole-`prototype = {…}` replacement
376
+ // is a different idiom — both stay untouched (and reject on `this` as before).
377
+ // The ctor must not be reassigned at top level (conservative fail-closed).
378
+ export function foldPseudoClassical(stmts) {
379
+ const protoAssign = (st) => {
380
+ // ['=', ['.', ['.', NAME, 'prototype'], m], ['function', '', params, body]]
381
+ if (!Array.isArray(st) || st[0] !== '=' || !Array.isArray(st[1])) return null
382
+ const lhs = st[1]
383
+ if (lhs[0] !== '.' || typeof lhs[2] !== 'string') return null
384
+ const base = lhs[1]
385
+ if (!Array.isArray(base) || base[0] !== '.' || base[2] !== 'prototype' || typeof base[1] !== 'string') return null
386
+ const rhs = st[2]
387
+ if (!Array.isArray(rhs) || rhs[0] !== 'function') return null
388
+ return { ctor: base[1], methods: [{ method: lhs[2], params: rhs[2], body: rhs[3] }] }
389
+ }
390
+ // Object.assign(NAME.prototype, { m: function (…) {…}, … }) — the batch
391
+ // idiom. Whole-or-nothing: any non-function prop value (arrow = lexical
392
+ // this, data prop = prototype state) skips the fold for this statement.
393
+ const protoAssignBatch = (st) => {
394
+ if (!Array.isArray(st) || st[0] !== '()' || !Array.isArray(st[1])) return null
395
+ if (st[1][0] !== '.' || st[1][1] !== 'Object' || st[1][2] !== 'assign') return null
396
+ const args = Array.isArray(st[2]) && st[2][0] === ',' ? st[2].slice(1) : [st[2]]
397
+ if (args.length !== 2) return null
398
+ const [proto, lit] = args
399
+ if (!Array.isArray(proto) || proto[0] !== '.' || proto[2] !== 'prototype' || typeof proto[1] !== 'string') return null
400
+ if (!Array.isArray(lit) || lit[0] !== '{}') return null
401
+ const props = Array.isArray(lit[1]) && lit[1][0] === ',' ? lit[1].slice(1) : lit[1] === undefined ? [] : [lit[1]]
402
+ const methods = []
403
+ for (const pr of props) {
404
+ if (!Array.isArray(pr) || pr[0] !== ':' || typeof pr[1] !== 'string') return null
405
+ if (!Array.isArray(pr[2]) || pr[2][0] !== 'function') return null
406
+ methods.push({ method: pr[1], params: pr[2][2], body: pr[2][3] })
407
+ }
408
+ return methods.length ? { ctor: proto[1], methods } : null
409
+ }
410
+ const matchProto = (st) => protoAssign(st) ?? protoAssignBatch(st)
411
+ const methods = new Map() // ctorName → [{method, params, body}]
412
+ for (const st of stmts) {
413
+ const pa = matchProto(st)
414
+ if (pa) { const l = methods.get(pa.ctor); l ? l.push(...pa.methods) : methods.set(pa.ctor, [...pa.methods]) }
415
+ }
416
+ if (!methods.size) return stmts
417
+
418
+ const reassigned = (name) => stmts.some(st =>
419
+ Array.isArray(st) && st[0] === '=' && st[1] === name)
420
+ const wholeProtoReplaced = (name) => stmts.some(st =>
421
+ Array.isArray(st) && st[0] === '=' && Array.isArray(st[1]) &&
422
+ st[1][0] === '.' && st[1][1] === name && st[1][2] === 'prototype')
423
+ // Decide up front — prototype assignments may appear BEFORE the constructor
424
+ // declaration (function decls hoist in JS), so consumption can't depend on
425
+ // visit order.
426
+ const foldable = new Set([...methods.keys()].filter(name =>
427
+ stmts.some(st => Array.isArray(st) && st[0] === 'function' && st[1] === name) &&
428
+ !reassigned(name) && !wholeProtoReplaced(name)))
429
+ if (!foldable.size) return stmts
430
+
431
+ const out = []
432
+ for (const st of stmts) {
433
+ if (Array.isArray(st) && st[0] === 'function' && typeof st[1] === 'string' && foldable.has(st[1])) {
434
+ const ms = methods.get(st[1])
435
+ out.push(['class', st[1], null, [';',
436
+ [':', 'constructor', ['=>', ['()', st[2] ?? null], st[3]]],
437
+ ...ms.map(m => [':', m.method, ['=>', ['()', m.params ?? null], m.body]]),
438
+ ]])
439
+ continue
440
+ }
441
+ const pa = matchProto(st)
442
+ if (pa && foldable.has(pa.ctor)) continue // consumed by the fold
443
+ out.push(st)
444
+ }
445
+ return out
446
+ }