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
|
@@ -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
|
+
}
|