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.
- package/README.md +56 -9
- package/bench/README.md +121 -50
- package/bench/bench.svg +27 -27
- package/cli.js +3 -1
- package/dist/interop.js +1 -1
- package/dist/jz.js +7541 -6178
- package/index.js +165 -74
- package/interop.js +189 -17
- package/jzify/arguments.js +32 -1
- package/jzify/async.js +409 -0
- package/jzify/classes.js +136 -18
- package/jzify/generators.js +639 -0
- package/jzify/hoist-vars.js +73 -1
- package/jzify/index.js +123 -4
- package/jzify/names.js +1 -0
- package/jzify/transform.js +239 -1
- package/layout.js +48 -3
- package/module/array.js +318 -43
- package/module/atomics.js +144 -0
- package/module/collection.js +1429 -155
- package/module/core.js +431 -40
- package/module/date.js +142 -120
- package/module/fs.js +144 -0
- package/module/function.js +6 -3
- package/module/index.js +4 -1
- package/module/json.js +270 -49
- package/module/math.js +1032 -145
- package/module/number.js +532 -163
- package/module/object.js +353 -95
- package/module/regex.js +157 -7
- package/module/schema.js +100 -2
- package/module/string.js +428 -93
- package/module/typedarray.js +264 -72
- package/module/web.js +36 -0
- package/package.json +5 -5
- package/src/abi/string.js +82 -11
- package/src/ast.js +11 -5
- package/src/autoload.js +28 -1
- package/src/bridge.js +7 -2
- package/src/compile/analyze-scans.js +22 -7
- package/src/compile/analyze.js +136 -30
- package/src/compile/dyn-closure-tables.js +277 -0
- package/src/compile/emit-assign.js +281 -15
- package/src/compile/emit.js +1055 -70
- package/src/compile/index.js +277 -29
- package/src/compile/infer.js +42 -4
- package/src/compile/inplace-store.js +329 -0
- package/src/compile/loop-recurrence.js +167 -0
- package/src/compile/narrow.js +555 -18
- package/src/compile/peel-stencil.js +5 -0
- package/src/compile/plan/index.js +45 -3
- package/src/compile/plan/inline.js +8 -1
- package/src/compile/plan/literals.js +39 -0
- package/src/compile/plan/scope.js +430 -23
- package/src/compile/program-facts.js +732 -38
- package/src/ctx.js +64 -9
- package/src/helper-counters.js +7 -1
- package/src/ir.js +113 -5
- package/src/kind-traits.js +66 -3
- package/src/kind.js +84 -12
- package/src/op-policy.js +7 -4
- package/src/optimize/index.js +1179 -704
- package/src/optimize/recurse.js +2 -2
- package/src/optimize/vectorize.js +982 -67
- package/src/prepare/index.js +809 -67
- package/src/prepare/math-kernel.js +331 -0
- package/src/prepare/pre-eval.js +714 -0
- package/src/snapshot.js +194 -0
- package/src/type.js +1170 -56
- package/src/wat/assemble.js +403 -65
- package/src/wat/codegen.js +74 -14
- package/transform.js +113 -4
- 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] === '[]')
|
|
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,
|
|
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],
|
|
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,
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
+
}
|