jz 0.4.0 → 0.5.1
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 +275 -255
- package/cli.js +8 -51
- package/index.js +166 -31
- package/{src/host.js → interop.js} +291 -88
- package/module/array.js +181 -71
- package/module/collection.js +222 -8
- package/module/console.js +1 -1
- package/module/core.js +277 -118
- package/module/date.js +9 -5
- package/module/function.js +11 -1
- package/module/json.js +361 -55
- package/module/math.js +601 -130
- package/module/number.js +390 -227
- package/module/object.js +252 -34
- package/module/regex.js +123 -16
- package/module/schema.js +15 -1
- package/module/string.js +545 -96
- package/module/timer.js +1 -1
- package/module/typedarray.js +674 -57
- package/package.json +10 -7
- package/src/abi/array.js +89 -0
- package/src/abi/index.js +35 -0
- package/src/abi/number.js +137 -0
- package/src/abi/object.js +57 -0
- package/src/abi/string.js +529 -0
- package/src/analyze.js +1870 -485
- package/src/assemble.js +27 -12
- package/src/autoload.js +13 -1
- package/src/compile.js +446 -179
- package/src/ctx.js +85 -18
- package/src/emit.js +705 -196
- package/src/infer.js +548 -0
- package/src/ir.js +291 -23
- package/src/jzify.js +851 -132
- package/src/narrow.js +433 -251
- package/src/optimize.js +126 -122
- package/src/plan.js +703 -34
- package/src/prepare.js +833 -153
- package/src/resolve.js +85 -0
- package/src/vectorize.js +99 -18
- package/src/ast.js +0 -160
- package/src/auto-config.js +0 -120
- package/src/fuse.js +0 -159
package/src/jzify.js
CHANGED
|
@@ -27,11 +27,14 @@ export default function jzify(ast) {
|
|
|
27
27
|
argsIdx = 0
|
|
28
28
|
doIdx = 0
|
|
29
29
|
classIdx = 0
|
|
30
|
+
objThisIdx = 0
|
|
31
|
+
staticClassIdx = 0
|
|
32
|
+
classBaseIdx = 0
|
|
30
33
|
// Hoist module-level vars: any `var x` inside nested blocks bubbles up.
|
|
31
34
|
const names = new Set()
|
|
32
35
|
ast = hoistVars(ast, names)
|
|
33
36
|
if (names.size) ast = prependDecls(ast, names)
|
|
34
|
-
return foldStaticExportHelpers(canonicalizeObjectIdioms(transformScope(ast)))
|
|
37
|
+
return foldStaticBundlerHelpers(foldStaticExportHelpers(canonicalizeObjectIdioms(transformScope(ast))))
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
/**
|
|
@@ -71,16 +74,24 @@ function hoistVars(node, names) {
|
|
|
71
74
|
}
|
|
72
75
|
return [op, lhs, hoistVars(node[2], names)]
|
|
73
76
|
}
|
|
77
|
+
// Labeled statement: recurse into the body so its `var`s hoist to the
|
|
78
|
+
// enclosing function, not stop at the label.
|
|
79
|
+
if (op === ':' && typeof node[1] === 'string') {
|
|
80
|
+
return [':', node[1], hoistVars(node[2], names)]
|
|
81
|
+
}
|
|
74
82
|
if (op === '=' && Array.isArray(node[1]) && node[1][0] === 'var' && typeof node[1][1] === 'string' && node[1].length === 2) {
|
|
75
83
|
names.add(node[1][1])
|
|
76
84
|
return ['=', node[1][1], hoistVars(node[2], names)]
|
|
77
85
|
}
|
|
86
|
+
if (op === '=' && isDestructurePat(node[1])) {
|
|
87
|
+
return ['=', hoistPattern(node[1], names), hoistVars(node[2], names)]
|
|
88
|
+
}
|
|
78
89
|
// For-head `;` is positional (init; cond; update), not a statement sequence.
|
|
79
90
|
// Recurse into each slot but never filter nulls — empty slots are valid.
|
|
80
91
|
if (op === 'for') {
|
|
81
92
|
const head = node[1]
|
|
82
93
|
let h2
|
|
83
|
-
const normalizedHead = normalizeForDeclHead(head, names)
|
|
94
|
+
const normalizedHead = normalizeForDeclHead(head, names) || normalizeForCommaHead(head, names)
|
|
84
95
|
if (normalizedHead) {
|
|
85
96
|
h2 = normalizedHead
|
|
86
97
|
} else if (Array.isArray(head) && head[0] === 'var' && Array.isArray(head[1]) &&
|
|
@@ -109,25 +120,84 @@ function hoistVars(node, names) {
|
|
|
109
120
|
if (decls.length === 1) return decls[0]
|
|
110
121
|
return [',', ...decls]
|
|
111
122
|
}
|
|
123
|
+
if (op === 'let' || op === 'const') {
|
|
124
|
+
const decls = [op]
|
|
125
|
+
for (let i = 1; i < node.length; i++) {
|
|
126
|
+
const d = node[i]
|
|
127
|
+
if (Array.isArray(d) && d[0] === '=' && isDestructurePat(d[1])) {
|
|
128
|
+
decls.push(['=', hoistPattern(d[1], names), hoistVars(d[2], names)])
|
|
129
|
+
} else {
|
|
130
|
+
decls.push(hoistVars(d, names))
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return decls
|
|
134
|
+
}
|
|
112
135
|
// Filter null returns from `;` sequences (bare-var no-ops). `{}` is left
|
|
113
136
|
// to recurse normally — it may be either a block or an object literal,
|
|
114
137
|
// and we don't want to clobber `['{}', null]` (empty object literal).
|
|
115
138
|
if (op === ';') {
|
|
116
139
|
const out = [op]
|
|
117
140
|
for (let i = 1; i < node.length; i++) {
|
|
118
|
-
const
|
|
141
|
+
const child = node[i]
|
|
142
|
+
// A direct scope-child `var f = <arrow>` keeps its decl shape (→ a single
|
|
143
|
+
// `let f = arrow`) instead of splitting into a hoisted `let f` + later
|
|
144
|
+
// assignment. `var` and `let` are equivalent at a direct statement
|
|
145
|
+
// position, and the single-decl form is what top-level-function
|
|
146
|
+
// recognition keys on — bundlers (esbuild &c.) emit `var` for every
|
|
147
|
+
// module binding, so without this every bundled function would degrade
|
|
148
|
+
// to a closure value (and hit MAX_CLOSURE_ARITY). Redeclarations are
|
|
149
|
+
// collapsed downstream by the binding-dedup pass.
|
|
150
|
+
if (Array.isArray(child) && child[0] === 'var' && child.length === 2 &&
|
|
151
|
+
Array.isArray(child[1]) && child[1][0] === '=' && typeof child[1][1] === 'string' &&
|
|
152
|
+
Array.isArray(child[1][2]) && child[1][2][0] === '=>') {
|
|
153
|
+
out.push(['let', ['=', child[1][1], hoistVars(child[1][2], names)]])
|
|
154
|
+
continue
|
|
155
|
+
}
|
|
156
|
+
const c = hoistVars(child, names)
|
|
119
157
|
if (c != null) out.push(c)
|
|
120
158
|
}
|
|
121
159
|
if (out.length === 1) return null
|
|
122
160
|
if (out.length === 2) return out[1]
|
|
123
161
|
return out
|
|
124
162
|
}
|
|
163
|
+
// Block body: recursing the inner `;` may collapse it to a bare expression
|
|
164
|
+
// (e.g. `{ v + 1; }` → `['{}', ['+','v',1]]`), which prepare.js would then
|
|
165
|
+
// mistake for an object literal. Re-wrap as a 1-stmt `;` if the inner was
|
|
166
|
+
// block-shaped going in but collapsed to a non-statement-op expression.
|
|
167
|
+
if (op === '{}' && node.length === 2) {
|
|
168
|
+
const inner = node[1]
|
|
169
|
+
const wasBlock = inner != null && Array.isArray(inner) && JZ_BLOCK_OPS.has(inner[0])
|
|
170
|
+
const t = hoistVars(inner, names)
|
|
171
|
+
if (!wasBlock || t == null) return ['{}', t]
|
|
172
|
+
const stayed = Array.isArray(t) && JZ_BLOCK_OPS.has(t[0])
|
|
173
|
+
return ['{}', stayed ? t : [';', t]]
|
|
174
|
+
}
|
|
125
175
|
const out = new Array(node.length)
|
|
126
176
|
out[0] = op
|
|
127
177
|
for (let i = 1; i < node.length; i++) out[i] = hoistVars(node[i], names)
|
|
128
178
|
return out
|
|
129
179
|
}
|
|
130
180
|
|
|
181
|
+
function hoistPattern(node, names) {
|
|
182
|
+
if (node == null || !Array.isArray(node)) return node
|
|
183
|
+
const op = node[0]
|
|
184
|
+
if (op === '=') return ['=', hoistPattern(node[1], names), hoistVars(node[2], names)]
|
|
185
|
+
if (op === ':') return [':', hoistVars(node[1], names), hoistPattern(node[2], names)]
|
|
186
|
+
if (op === '...') return ['...', hoistPattern(node[1], names)]
|
|
187
|
+
if (op === '[]' || op === '{}' || op === ',') return [op, ...node.slice(1).map(n => hoistPattern(n, names))]
|
|
188
|
+
return hoistVars(node, names)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function transformPattern(node) {
|
|
192
|
+
if (node == null || !Array.isArray(node)) return node
|
|
193
|
+
const op = node[0]
|
|
194
|
+
if (op === '=') return ['=', transformPattern(node[1]), transform(node[2])]
|
|
195
|
+
if (op === ':') return [':', transform(node[1]), transformPattern(node[2])]
|
|
196
|
+
if (op === '...') return ['...', transformPattern(node[1])]
|
|
197
|
+
if (op === '[]' || op === '{}' || op === ',') return [op, ...node.slice(1).map(transformPattern)]
|
|
198
|
+
return transform(node)
|
|
199
|
+
}
|
|
200
|
+
|
|
131
201
|
function prependDecls(body, names) {
|
|
132
202
|
const decl = ['let', ...names]
|
|
133
203
|
if (Array.isArray(body) && body[0] === ';') return [';', decl, ...body.slice(1)]
|
|
@@ -141,18 +211,39 @@ function prependDecls(body, names) {
|
|
|
141
211
|
}
|
|
142
212
|
|
|
143
213
|
function normalizeForDeclHead(head, names) {
|
|
144
|
-
if (!Array.isArray(head) || (head[0] !== 'var' && head[0] !== 'let' && head[0] !== 'const')
|
|
214
|
+
if (!Array.isArray(head) || (head[0] !== 'var' && head[0] !== 'let' && head[0] !== 'const')) return null
|
|
145
215
|
const kind = head[0]
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
216
|
+
if (head.length === 2) {
|
|
217
|
+
const expr = head[1]
|
|
218
|
+
if (!Array.isArray(expr)) return null
|
|
219
|
+
if (expr.length >= 3 && Array.isArray(expr[1]) &&
|
|
220
|
+
(expr[1][0] === 'in' || expr[1][0] === 'of') && typeof expr[1][1] === 'string') {
|
|
221
|
+
const iter = expr[1]
|
|
222
|
+
return [iter[0], normalizeForDecl(kind, iter[1], names), hoistVars([expr[0], iter[2], ...expr.slice(2)], names)]
|
|
223
|
+
}
|
|
224
|
+
return null
|
|
225
|
+
}
|
|
226
|
+
// Comma Expression in a for-in/of head: subscript parses with no for-head
|
|
227
|
+
// context, so `for (let x in A, B)` becomes a multi-declarator `let` whose
|
|
228
|
+
// tail declarators are really the rest of a comma Expression. Fold them back
|
|
229
|
+
// into the iterated source so the comma operator evaluates them in order.
|
|
230
|
+
if (head.length > 2 && Array.isArray(head[1]) &&
|
|
231
|
+
(head[1][0] === 'in' || head[1][0] === 'of') && typeof head[1][1] === 'string') {
|
|
232
|
+
const iter = head[1]
|
|
233
|
+
return [iter[0], normalizeForDecl(kind, iter[1], names), hoistVars([',', iter[2], ...head.slice(2)], names)]
|
|
152
234
|
}
|
|
153
235
|
return null
|
|
154
236
|
}
|
|
155
237
|
|
|
238
|
+
// Bare-LHS counterpart of the above: `for (x in A, B)` (no declaration) parses
|
|
239
|
+
// as a comma expression whose first operand is the for-in/of head.
|
|
240
|
+
function normalizeForCommaHead(head, names) {
|
|
241
|
+
if (!Array.isArray(head) || head[0] !== ',' || head.length < 3) return null
|
|
242
|
+
const iter = head[1]
|
|
243
|
+
if (!Array.isArray(iter) || (iter[0] !== 'in' && iter[0] !== 'of') || typeof iter[1] !== 'string') return null
|
|
244
|
+
return [iter[0], iter[1], hoistVars([',', iter[2], ...head.slice(2)], names)]
|
|
245
|
+
}
|
|
246
|
+
|
|
156
247
|
function normalizeForDecl(kind, name, names) {
|
|
157
248
|
if (kind === 'var') {
|
|
158
249
|
names.add(name)
|
|
@@ -163,7 +254,7 @@ function normalizeForDecl(kind, name, names) {
|
|
|
163
254
|
|
|
164
255
|
/** Convert a named function declaration to a hoisted const arrow */
|
|
165
256
|
function hoistFnDecl(name, params, body) {
|
|
166
|
-
const [p2, b2] = lowerArguments(params, body)
|
|
257
|
+
const [p2, b2] = lowerArguments(params, functionBodyBlock(body))
|
|
167
258
|
const decl = ['const', ['=', name, ['=>', p2, wrapArrowBody(b2)]]]
|
|
168
259
|
decl._hoisted = true
|
|
169
260
|
return decl
|
|
@@ -185,25 +276,6 @@ function transformScope(node) {
|
|
|
185
276
|
const hoisted = [], rest = []
|
|
186
277
|
for (let i = 0; i < args.length; i++) {
|
|
187
278
|
const stmt = args[i]
|
|
188
|
-
// Workaround for subscript parser ASI bug: multiline named IIFE
|
|
189
|
-
// `(function name(){...})();` is parsed as two statements when there are
|
|
190
|
-
// newlines inside the function body. Reconstruct the single-statement IIFE
|
|
191
|
-
// so the () handler can desugar it correctly.
|
|
192
|
-
if (Array.isArray(stmt) && stmt[0] === '()' &&
|
|
193
|
-
Array.isArray(stmt[1]) && stmt[1][0] === 'function' && stmt[1][1] &&
|
|
194
|
-
i + 1 < args.length && Array.isArray(args[i + 1]) && args[i + 1][0] === '()') {
|
|
195
|
-
const merged = ['()', ['()', stmt[1]], args[i + 1][1] ?? null]
|
|
196
|
-
const t = transform(merged)
|
|
197
|
-
if (t != null) {
|
|
198
|
-
if (Array.isArray(t) && t[0] === ';') {
|
|
199
|
-
for (const s of t.slice(1)) { if (s != null) rest.push(s) }
|
|
200
|
-
} else {
|
|
201
|
-
rest.push(t)
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
i++
|
|
205
|
-
continue
|
|
206
|
-
}
|
|
207
279
|
// Statement-form named function declaration: hoist directly (skip expression handler)
|
|
208
280
|
if (Array.isArray(stmt) && stmt[0] === 'function' && stmt[1]) {
|
|
209
281
|
hoisted.push(hoistFnDecl(stmt[1], stmt[2], stmt[3]))
|
|
@@ -249,33 +321,109 @@ function transformScope(node) {
|
|
|
249
321
|
* wins; a later redeclaration keeps only its initializer, as a plain assignment.
|
|
250
322
|
*/
|
|
251
323
|
function dedupeRedecls(stmts) {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
: null
|
|
324
|
+
// Per-declarator name — bare `x` or `['=', x, init]`; null for patterns.
|
|
325
|
+
const declName = d => typeof d === 'string' ? d
|
|
326
|
+
: Array.isArray(d) && d[0] === '=' && typeof d[1] === 'string' ? d[1] : null
|
|
256
327
|
const seen = new Set(), out = []
|
|
257
328
|
for (const s of stmts) {
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
329
|
+
if (!Array.isArray(s) || (s[0] !== 'let' && s[0] !== 'const' && s[0] !== 'var')) { out.push(s); continue }
|
|
330
|
+
// Walk every declarator: a multi-name bare `let` (the var-hoist
|
|
331
|
+
// `prependDecls` output) can carry a name already bound by a hoisted
|
|
332
|
+
// function `const`. Keep only fresh declarators; a redeclaration with an
|
|
333
|
+
// initializer survives as a plain assignment.
|
|
334
|
+
const keep = [s[0]], reassign = []
|
|
335
|
+
for (let i = 1; i < s.length; i++) {
|
|
336
|
+
const d = s[i], n = declName(d)
|
|
337
|
+
if (n == null) { keep.push(d); continue }
|
|
338
|
+
if (seen.has(n)) { if (Array.isArray(d) && d[0] === '=') reassign.push(['=', d[1], d[2]]) }
|
|
339
|
+
else { seen.add(n); keep.push(d) }
|
|
340
|
+
}
|
|
341
|
+
if (keep.length > 1) out.push(keep)
|
|
342
|
+
for (const r of reassign) out.push(r)
|
|
262
343
|
}
|
|
263
344
|
return out
|
|
264
345
|
}
|
|
265
346
|
|
|
266
|
-
/** Wrap function body for arrow conversion
|
|
347
|
+
/** Wrap function body for arrow conversion.
|
|
348
|
+
* Produces the canonical block form `['{}', [';', ...stmts]]`: a `{}` whose
|
|
349
|
+
* sole child is a `;`-list. A bare single statement (`['{}', stmt]`, from the
|
|
350
|
+
* parser eliding the `;` wrapper) would otherwise be mistaken for an object
|
|
351
|
+
* literal, so it is `;`-wrapped here too — `function`→arrow conversions bypass
|
|
352
|
+
* the `=>` transform handler and must normalize their own bodies. */
|
|
267
353
|
function wrapArrowBody(body) {
|
|
268
354
|
const t = transformScope(body)
|
|
269
|
-
|
|
355
|
+
if (!Array.isArray(t)) return ['{}', [';', t]]
|
|
356
|
+
if (t[0] === ';') return ['{}', t]
|
|
357
|
+
if (t[0] !== '{}') return ['{}', [';', t]]
|
|
358
|
+
if (t.length === 2 && !(Array.isArray(t[1]) && t[1][0] === ';')) return ['{}', [';', t[1]]]
|
|
359
|
+
return t
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function functionBodyBlock(body) {
|
|
363
|
+
if (Array.isArray(body) && body[0] === '{}') return body
|
|
364
|
+
if (Array.isArray(body) && body[0] === ';') return ['{}', body]
|
|
365
|
+
return ['{}', [';', body]]
|
|
270
366
|
}
|
|
271
367
|
|
|
272
368
|
/** Prototype identity check: X.prototype.Y */
|
|
273
369
|
const isProto = n => Array.isArray(n) && n[0] === '.' && Array.isArray(n[1]) && n[1][0] === '.' && n[1][2] === 'prototype'
|
|
274
370
|
|
|
371
|
+
/** `obj.M <eq> Ctor.prototype.M` is not a real reference comparison — jz has no
|
|
372
|
+
* prototype objects — it asks whether `obj` overrides `M`, which is exactly
|
|
373
|
+
* `obj.hasOwnProperty('M')`. Returns that call node when the shape matches
|
|
374
|
+
* (a member access `obj.M` against a same-named prototype method), else null. */
|
|
375
|
+
const methodOverrideHasOwn = (a, b) => {
|
|
376
|
+
const proto = isProto(a) ? a : isProto(b) ? b : null
|
|
377
|
+
if (!proto) return null
|
|
378
|
+
const other = proto === a ? b : a
|
|
379
|
+
if (!Array.isArray(other) || other[0] !== '.' || other[2] !== proto[2]) return null
|
|
380
|
+
return ['()', ['.', transform(other[1]), 'hasOwnProperty'], [null, proto[2]]]
|
|
381
|
+
}
|
|
382
|
+
|
|
275
383
|
const TYPED_ARRAYS = new Set(['Float64Array','Float32Array','Int32Array','Uint32Array',
|
|
276
384
|
'Int16Array','Uint16Array','Int8Array','Uint8Array',
|
|
277
385
|
'ArrayBuffer','BigInt64Array','BigUint64Array','DataView'])
|
|
278
386
|
|
|
387
|
+
// Block-shape ops used to detect "this `{}` is a block body, not an object literal".
|
|
388
|
+
// Mirrors analyze.STMT_OPS — kept inline so jzify stays self-contained.
|
|
389
|
+
const JZ_BLOCK_OPS = new Set([';', 'let', 'const', 'var', 'return', 'if', 'for', 'for-in', 'for-of',
|
|
390
|
+
'while', 'do', 'break', 'continue', 'switch', 'throw', 'try', 'catch', 'finally',
|
|
391
|
+
'=', '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '>>=', '<<=', '>>>=', '||=', '&&=', '??=',
|
|
392
|
+
'++', '--', '()', 'function', 'class', 'import', 'export', 'label'])
|
|
393
|
+
const LABEL_BODY_OPS = new Set([';', 'if', 'for', 'for-in', 'for-of', 'while', 'do', 'switch', 'try', 'throw'])
|
|
394
|
+
|
|
395
|
+
/** Statically discriminate `x instanceof Ctor` when the LHS's syntactic shape
|
|
396
|
+
* already pins down its runtime type. Returns true/false or null (unknown).
|
|
397
|
+
* Matches the lhs forms a user might plausibly write: literal arrays, object
|
|
398
|
+
* literals, string/number literals, and `new C(...)` whose ctor name is known.
|
|
399
|
+
* Stays conservative — anything else returns null and falls back to the runtime
|
|
400
|
+
* predicate so behavior is never silently changed. */
|
|
401
|
+
function staticInstanceofFold(val, ctor) {
|
|
402
|
+
if (typeof ctor !== 'string' || !Array.isArray(val)) return null
|
|
403
|
+
// Unwrap grouping parens: `(expr)` parses as `['()', expr]`
|
|
404
|
+
if (val[0] === '()' && val.length === 2) return staticInstanceofFold(val[1], ctor)
|
|
405
|
+
// Array literal: `[1,2,3]` → `['[]', [',', …]]` (or `['[]']` for empty array)
|
|
406
|
+
if (val[0] === '[]' && val.length <= 2) return ctor === 'Array' || ctor === 'Object'
|
|
407
|
+
// Object literal: `{a:1}` → `['{}', [':', …], …]`
|
|
408
|
+
if (val[0] === '{}') return ctor === 'Object'
|
|
409
|
+
// Regex literal: `/x/` → `['//', …]`
|
|
410
|
+
if (val[0] === '//') return ctor === 'RegExp' || ctor === 'Object'
|
|
411
|
+
// `new C(...)` — `['new', ['()', 'C', args]]` or `['new', 'C']`
|
|
412
|
+
if (val[0] === 'new') {
|
|
413
|
+
const inner = val[1]
|
|
414
|
+
const cname = typeof inner === 'string' ? inner
|
|
415
|
+
: (Array.isArray(inner) && inner[0] === '()' && typeof inner[1] === 'string') ? inner[1]
|
|
416
|
+
: null
|
|
417
|
+
if (cname) return cname === ctor || (cname !== 'Object' && ctor === 'Object')
|
|
418
|
+
}
|
|
419
|
+
// Bare primitive: `[null, v]` where v is a primitive — never an instance per JS spec.
|
|
420
|
+
if (val[0] == null && val.length === 2) {
|
|
421
|
+
const v = val[1]
|
|
422
|
+
if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean' || v == null) return false
|
|
423
|
+
}
|
|
424
|
+
return null
|
|
425
|
+
}
|
|
426
|
+
|
|
279
427
|
// `arguments` lowering: regular `function` has implicit `arguments`; arrow doesn't.
|
|
280
428
|
// jzify converts function → arrow, so any `arguments` use must be rewritten to a rest param.
|
|
281
429
|
// Arrow functions inherit `arguments` from enclosing function — don't stop at '=>'.
|
|
@@ -373,6 +521,10 @@ function prependParamDecls(decl, body) {
|
|
|
373
521
|
}
|
|
374
522
|
|
|
375
523
|
const arrowParams = params => Array.isArray(params) && params[0] === '()' ? params : ['()', params]
|
|
524
|
+
// A method body from jessie is a bare statement / `;`-sequence — wrap it in a
|
|
525
|
+
// `{}` block so the `=>` handler treats it as a function body, not an expression
|
|
526
|
+
// (an unwrapped `;`-seq arrow body produces malformed IR).
|
|
527
|
+
const block = b => Array.isArray(b) && b[0] === '{}' ? b : ['{}', b]
|
|
376
528
|
|
|
377
529
|
// === class lowering ===
|
|
378
530
|
//
|
|
@@ -393,10 +545,19 @@ const arrowParams = params => Array.isArray(params) && params[0] === '()' ? para
|
|
|
393
545
|
// return selfN
|
|
394
546
|
// }
|
|
395
547
|
//
|
|
396
|
-
//
|
|
397
|
-
// `
|
|
398
|
-
//
|
|
548
|
+
// Simple inheritance is lowered too: `class D extends B` builds the instance
|
|
549
|
+
// from `B`'s factory — forwarding `super(...)` args, or the derived ctor params
|
|
550
|
+
// when the derived constructor is implicit — then applies D's own fields and
|
|
551
|
+
// methods over it.
|
|
552
|
+
//
|
|
553
|
+
// Out of scope (rejected with a clear message): full `super.foo` property
|
|
554
|
+
// semantics, getters/setters, non-constant computed member names. Private
|
|
555
|
+
// `#name` members are kept as the literal key string `#name` (jz allows it).
|
|
399
556
|
let classIdx = 0
|
|
557
|
+
let objThisIdx = 0
|
|
558
|
+
let staticClassIdx = 0
|
|
559
|
+
let classBaseIdx = 0
|
|
560
|
+
const DEFAULT_DERIVED_CTOR_ARITY = 8
|
|
400
561
|
|
|
401
562
|
const classBodyItems = (body) =>
|
|
402
563
|
body == null ? [] : Array.isArray(body) && body[0] === ';' ? body.slice(1) : [body]
|
|
@@ -413,39 +574,200 @@ function renameThis(node, to) {
|
|
|
413
574
|
return node.map(n => renameThis(n, to))
|
|
414
575
|
}
|
|
415
576
|
|
|
577
|
+
function usesThis(node) {
|
|
578
|
+
if (node === 'this') return true
|
|
579
|
+
if (!Array.isArray(node)) return false
|
|
580
|
+
if (node[0] === 'function' || node[0] === 'class') return false
|
|
581
|
+
if (node[0] === '.' || node[0] === '?.') return usesThis(node[1])
|
|
582
|
+
if (node[0] === ':') return usesThis(node[2])
|
|
583
|
+
return node.some(usesThis)
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function hasSuperProp(node) {
|
|
587
|
+
if (!Array.isArray(node)) return false
|
|
588
|
+
if ((node[0] === '.' || node[0] === '?.') && node[1] === 'super') return true
|
|
589
|
+
if (node[0] === '[]' && node[1] === 'super') return true
|
|
590
|
+
return node.some(hasSuperProp)
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function isSuperCall(node) {
|
|
594
|
+
return Array.isArray(node) && node[0] === '()' && node[1] === 'super'
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function literalStringKey(node) {
|
|
598
|
+
return Array.isArray(node) && node[0] == null && typeof node[1] === 'string' ? node[1] : null
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function constStringKey(node) {
|
|
602
|
+
if (typeof node === 'string') return node
|
|
603
|
+
const lit = literalStringKey(node)
|
|
604
|
+
if (lit != null) return lit
|
|
605
|
+
if (Array.isArray(node) && node[0] === '[]') return literalStringKey(node[1])
|
|
606
|
+
return null
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function superMethodName(callee) {
|
|
610
|
+
if (!Array.isArray(callee)) return null
|
|
611
|
+
if ((callee[0] === '.' || callee[0] === '?.') && callee[1] === 'super') return callee[2]
|
|
612
|
+
if (callee[0] === '[]' && callee[1] === 'super') return literalStringKey(callee[2])
|
|
613
|
+
return null
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function collectSuperMethodCalls(node, out = new Set()) {
|
|
617
|
+
if (!Array.isArray(node)) return out
|
|
618
|
+
if (node[0] === 'function' || node[0] === 'class') return out
|
|
619
|
+
if (node[0] === '()') {
|
|
620
|
+
const name = superMethodName(node[1])
|
|
621
|
+
if (name) out.add(name)
|
|
622
|
+
}
|
|
623
|
+
for (const n of node) collectSuperMethodCalls(n, out)
|
|
624
|
+
return out
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function rewriteSuperMethodCalls(node, baseMethodVars) {
|
|
628
|
+
if (!Array.isArray(node)) return node
|
|
629
|
+
if (node[0] === 'function' || node[0] === 'class') return node
|
|
630
|
+
if (node[0] === '()') {
|
|
631
|
+
const name = superMethodName(node[1])
|
|
632
|
+
if (name) {
|
|
633
|
+
const fn = baseMethodVars.get(name)
|
|
634
|
+
if (!fn) jzifyError(`super.${name} is not available on the base class`)
|
|
635
|
+
return ['()', fn, ...node.slice(2).map(n => rewriteSuperMethodCalls(n, baseMethodVars))]
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
return node.map(n => rewriteSuperMethodCalls(n, baseMethodVars))
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function splitCtorSuper(body) {
|
|
642
|
+
if (body == null) return { args: null, body }
|
|
643
|
+
if (isSuperCall(body)) return { args: body.slice(2), body: null }
|
|
644
|
+
if (Array.isArray(body) && body[0] === '{}') {
|
|
645
|
+
const inner = splitCtorSuper(body[1])
|
|
646
|
+
return { args: inner.args, body: ['{}', inner.body] }
|
|
647
|
+
}
|
|
648
|
+
if (Array.isArray(body) && body[0] === ';') {
|
|
649
|
+
const out = [';']
|
|
650
|
+
let args = null
|
|
651
|
+
for (const stmt of body.slice(1)) {
|
|
652
|
+
if (args == null && isSuperCall(stmt)) { args = stmt.slice(2); continue }
|
|
653
|
+
out.push(stmt)
|
|
654
|
+
}
|
|
655
|
+
return { args, body: out.length === 1 ? null : out.length === 2 ? out[1] : out }
|
|
656
|
+
}
|
|
657
|
+
return { args: null, body }
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// Object shorthand methods and arrow-valued properties both parse as `=>`.
|
|
661
|
+
// Stay conservative: only statement-shaped bodies are receiver methods here;
|
|
662
|
+
// expression-bodied arrows keep their lexical `this` and remain unsupported.
|
|
663
|
+
const OBJ_METHOD_BODY_OPS = new Set([';', 'return', 'if', 'for', 'for-in', 'for-of',
|
|
664
|
+
'while', 'do', 'switch', 'throw', 'try', 'break', 'continue'])
|
|
665
|
+
|
|
666
|
+
function objectLiteralProps(args) {
|
|
667
|
+
const raw = args.length === 1 && Array.isArray(args[0]) && args[0][0] === ',' ? args[0].slice(1) : args
|
|
668
|
+
return raw.filter(p => p != null)
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function isStatementBody(body) {
|
|
672
|
+
return Array.isArray(body) && OBJ_METHOD_BODY_OPS.has(body[0])
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function objectMethodUsesThis(prop) {
|
|
676
|
+
if (!Array.isArray(prop) || prop[0] !== ':' || typeof prop[1] !== 'string') return false
|
|
677
|
+
const value = prop[2]
|
|
678
|
+
if (!Array.isArray(value)) return false
|
|
679
|
+
if (value[0] === '=>' && isStatementBody(value[2])) return usesThis(value[2])
|
|
680
|
+
return false
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function lowerObjectLiteralThis(args) {
|
|
684
|
+
const props = objectLiteralProps(args)
|
|
685
|
+
if (props.length === 0 || !props.some(objectMethodUsesThis)) return null
|
|
686
|
+
if (!props.every(p => Array.isArray(p) && p[0] === ':' && typeof p[1] === 'string')) return null
|
|
687
|
+
|
|
688
|
+
const self = `obj${objThisIdx++}`
|
|
689
|
+
const litProps = props.map(p => {
|
|
690
|
+
const value = p[2]
|
|
691
|
+
if (objectMethodUsesThis(p)) {
|
|
692
|
+
return [':', p[1], transform(['=>', value[1], block(renameThis(value[2], self))])]
|
|
693
|
+
}
|
|
694
|
+
return [':', p[1], transform(value)]
|
|
695
|
+
})
|
|
696
|
+
const lit = ['{}', litProps.length === 1 ? litProps[0] : [',', ...litProps]]
|
|
697
|
+
return ['()', ['()', ['=>', null, ['{}', [';',
|
|
698
|
+
['let', ['=', self, lit]],
|
|
699
|
+
['return', self]
|
|
700
|
+
]]]], null]
|
|
701
|
+
}
|
|
702
|
+
|
|
416
703
|
function jzifyError(msg) { throw new Error(`jzify: ${msg}`) }
|
|
417
704
|
|
|
418
705
|
function lowerClass(name, heritage, body) {
|
|
419
|
-
if (heritage != null) jzifyError('`class … extends …` is not supported yet — flatten the hierarchy or compose explicitly')
|
|
420
706
|
let ctorParams = null, ctorBody = null
|
|
421
|
-
const methods = [], fields = []
|
|
707
|
+
const methods = [], fields = [], statics = []
|
|
422
708
|
for (const it of classBodyItems(body)) {
|
|
423
709
|
if (typeof it === 'string') { fields.push([it, null]); continue } // bare `x;`
|
|
424
710
|
if (!Array.isArray(it)) continue
|
|
711
|
+
const bareFieldName = constStringKey(it)
|
|
712
|
+
if (bareFieldName != null) { fields.push([bareFieldName, null]); continue }
|
|
425
713
|
if (it[0] === ':' && Array.isArray(it[2]) && it[2][0] === '=>') {
|
|
426
|
-
|
|
427
|
-
if (
|
|
428
|
-
|
|
714
|
+
const key = constStringKey(it[1])
|
|
715
|
+
if (key == null) jzifyError('non-constant computed class member names are not supported')
|
|
716
|
+
if (key === 'constructor') { ctorParams = it[2][1]; ctorBody = it[2][2] }
|
|
717
|
+
else methods.push([key, it[2][1], it[2][2]])
|
|
429
718
|
continue
|
|
430
719
|
}
|
|
431
720
|
if (it[0] === '=') {
|
|
432
721
|
const lhs = it[1]
|
|
433
|
-
if (Array.isArray(lhs) && lhs[0] === 'static')
|
|
434
|
-
|
|
435
|
-
|
|
722
|
+
if (Array.isArray(lhs) && lhs[0] === 'static') {
|
|
723
|
+
const key = constStringKey(lhs[1])
|
|
724
|
+
if (key == null) jzifyError('non-constant computed static class fields are not supported')
|
|
725
|
+
statics.push([key, it[2]])
|
|
726
|
+
continue
|
|
727
|
+
}
|
|
728
|
+
const key = constStringKey(lhs)
|
|
729
|
+
if (key == null) jzifyError('non-constant computed/destructured class fields are not supported')
|
|
730
|
+
fields.push([key, it[2]])
|
|
731
|
+
continue
|
|
732
|
+
}
|
|
733
|
+
if (it[0] === 'static') {
|
|
734
|
+
const key = constStringKey(it[1])
|
|
735
|
+
if (key != null) {
|
|
736
|
+
statics.push([key, null])
|
|
737
|
+
continue
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
if (it[0] === 'static' && typeof it[1] === 'string') {
|
|
741
|
+
statics.push([it[1], null])
|
|
742
|
+
continue
|
|
743
|
+
}
|
|
744
|
+
if (it[0] === 'static' && Array.isArray(it[1]) && it[1][0] === ':' && Array.isArray(it[1][2]) && it[1][2][0] === '=>') {
|
|
745
|
+
const key = constStringKey(it[1][1])
|
|
746
|
+
if (key == null) jzifyError('non-constant computed static class member names are not supported')
|
|
747
|
+
statics.push([key, it[1][2], true])
|
|
436
748
|
continue
|
|
437
749
|
}
|
|
438
750
|
if (it[0] === 'get' || it[0] === 'set') jzifyError('class getters/setters are not supported — jz objects have no accessors')
|
|
439
751
|
if (it[0] === 'static') jzifyError('`static` class members are not supported yet')
|
|
440
752
|
jzifyError(`unsupported class member ${JSON.stringify(it).slice(0, 60)}`)
|
|
441
753
|
}
|
|
754
|
+
const superMethods = heritage == null ? new Set() : new Set([
|
|
755
|
+
...collectSuperMethodCalls(ctorBody),
|
|
756
|
+
...fields.flatMap(([, init]) => init == null ? [] : [...collectSuperMethodCalls(init)]),
|
|
757
|
+
...methods.flatMap(([, , mbody]) => [...collectSuperMethodCalls(mbody)])
|
|
758
|
+
])
|
|
759
|
+
if (heritage != null) {
|
|
760
|
+
const dummySuperVars = new Map([...superMethods].map((k, i) => [k, `super_${i}`]))
|
|
761
|
+
const unsupportedSuperProp = node => node != null && hasSuperProp(rewriteSuperMethodCalls(node, dummySuperVars))
|
|
762
|
+
if (
|
|
763
|
+
unsupportedSuperProp(ctorBody) ||
|
|
764
|
+
fields.some(([, init]) => unsupportedSuperProp(init)) ||
|
|
765
|
+
methods.some(([, , mbody]) => unsupportedSuperProp(mbody))
|
|
766
|
+
)
|
|
767
|
+
jzifyError('`super` property access is not supported yet')
|
|
768
|
+
}
|
|
442
769
|
const self = `self${classIdx++}`
|
|
443
770
|
const UNDEF = [] // jessie's node for `undefined`
|
|
444
|
-
// A class member body from jessie is a bare statement / `;`-sequence — wrap it
|
|
445
|
-
// in a `{}` block so the `=>` handler treats it as a function body, not an
|
|
446
|
-
// expression (an unwrapped `;`-seq arrow body produces malformed IR).
|
|
447
|
-
const block = b => Array.isArray(b) && b[0] === '{}' ? b : ['{}', b]
|
|
448
|
-
const usesThis = n => n === 'this' || (Array.isArray(n) && n[0] !== 'function' && n[0] !== 'class' && n.some(usesThis))
|
|
449
771
|
// Object literal: every declared field (its initializer inline when it doesn't
|
|
450
772
|
// touch `this`, else `undefined` and assigned below), every method as its
|
|
451
773
|
// self-capturing arrow. Declaring all fields up front fixes the object shape.
|
|
@@ -457,10 +779,39 @@ function lowerClass(name, heritage, body) {
|
|
|
457
779
|
for (const [mname, mparams, mbody] of methods)
|
|
458
780
|
litProps.push([':', mname, transform(['=>', mparams ?? ['()', null], block(renameThis(mbody, self))])])
|
|
459
781
|
const lit = ['{}', litProps.length === 0 ? null : litProps.length === 1 ? litProps[0] : [',', ...litProps]]
|
|
460
|
-
|
|
782
|
+
let params = ctorParams ?? ['()', null]
|
|
783
|
+
const dynamicBase = heritage != null && typeof heritage !== 'string'
|
|
784
|
+
const baseRef = heritage == null ? null : dynamicBase ? `base${classBaseIdx++}` : heritage
|
|
785
|
+
const stmts = []
|
|
786
|
+
if (heritage != null) {
|
|
787
|
+
const split = splitCtorSuper(ctorBody)
|
|
788
|
+
ctorBody = split.body
|
|
789
|
+
const defaultArgs = ctorParams == null
|
|
790
|
+
? Array.from({ length: DEFAULT_DERIVED_CTOR_ARITY }, (_, i) => `superArg${classIdx}_${i}`)
|
|
791
|
+
: null
|
|
792
|
+
const baseArgs = split.args ?? (defaultArgs ? [defaultArgs.length === 1 ? defaultArgs[0] : [',', ...defaultArgs]] : paramList(ctorParams))
|
|
793
|
+
stmts.push(['let', ['=', self, ['()', baseRef, ...baseArgs.map(transform)]]])
|
|
794
|
+
const superMethodVars = new Map()
|
|
795
|
+
let superIdx = 0
|
|
796
|
+
for (const mname of superMethods) {
|
|
797
|
+
const v = `super${classIdx}_${superIdx++}`
|
|
798
|
+
superMethodVars.set(mname, v)
|
|
799
|
+
stmts.push(['let', ['=', v, ['.', self, mname]]])
|
|
800
|
+
}
|
|
801
|
+
for (const [fname, init] of fields)
|
|
802
|
+
stmts.push(['=', ['.', self, fname], init != null ? transform(renameThis(rewriteSuperMethodCalls(init, superMethodVars), self)) : UNDEF])
|
|
803
|
+
for (const [mname, mparams, mbody] of methods)
|
|
804
|
+
stmts.push(['=', ['.', self, mname], transform(['=>', mparams ?? ['()', null], block(renameThis(rewriteSuperMethodCalls(mbody, superMethodVars), self))])])
|
|
805
|
+
ctorBody = rewriteSuperMethodCalls(ctorBody, superMethodVars)
|
|
806
|
+
if (defaultArgs) params = ['()', defaultArgs.length === 1 ? defaultArgs[0] : [',', ...defaultArgs]]
|
|
807
|
+
} else {
|
|
808
|
+
stmts.push(['let', ['=', self, lit]])
|
|
809
|
+
}
|
|
461
810
|
// `this`-dependent field initializers run, in declaration order, before the ctor.
|
|
462
|
-
|
|
463
|
-
|
|
811
|
+
if (heritage == null) {
|
|
812
|
+
for (const [fname, init] of deferred)
|
|
813
|
+
stmts.push(['=', ['.', self, fname], transform(renameThis(init, self))])
|
|
814
|
+
}
|
|
464
815
|
if (ctorBody != null) {
|
|
465
816
|
let cb = transform(renameThis(ctorBody, self))
|
|
466
817
|
if (Array.isArray(cb) && cb[0] === '{}') cb = cb[1]
|
|
@@ -468,15 +819,43 @@ function lowerClass(name, heritage, body) {
|
|
|
468
819
|
else if (cb != null) stmts.push(cb)
|
|
469
820
|
}
|
|
470
821
|
stmts.push(['return', self])
|
|
471
|
-
|
|
822
|
+
const factory = ['=>', arrowParams(params), ['{}', [';', ...stmts]]]
|
|
823
|
+
if (!dynamicBase && statics.length === 0) return factory
|
|
824
|
+
|
|
825
|
+
const cls = name || `class${staticClassIdx++}`
|
|
826
|
+
const staticStmts = []
|
|
827
|
+
if (dynamicBase) staticStmts.push(['let', ['=', baseRef, transform(heritage)]])
|
|
828
|
+
staticStmts.push(['let', ['=', cls, factory]])
|
|
829
|
+
for (const [sname, value, isMethod] of statics) {
|
|
830
|
+
const rhs = isMethod
|
|
831
|
+
? transform(['=>', value[1], block(renameThis(value[2], cls))])
|
|
832
|
+
: value == null ? UNDEF : transform(renameThis(value, cls))
|
|
833
|
+
staticStmts.push(['=', ['.', cls, sname], rhs])
|
|
834
|
+
}
|
|
835
|
+
staticStmts.push(['return', cls])
|
|
836
|
+
return ['()', ['()', ['=>', null, ['{}', [';', ...staticStmts]]]], null]
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// Array(a, b, …) / new Array(a, b, …) → array literal [a, b, …]; Array() → [].
|
|
840
|
+
// The single-argument Array(n) is a length constructor (n holes), not a
|
|
841
|
+
// literal — return null there so the caller keeps it as a constructor call.
|
|
842
|
+
function lowerArrayConstructor(arg) {
|
|
843
|
+
if (arg == null) return ['[]', null]
|
|
844
|
+
if (Array.isArray(arg) && arg[0] === ',' && arg.length > 2)
|
|
845
|
+
return ['[]', [',', ...arg.slice(1).map(transform)]]
|
|
846
|
+
return null
|
|
472
847
|
}
|
|
473
848
|
|
|
474
849
|
const handlers = {
|
|
475
850
|
// Named IIFE: (function name(p){b})(a) → let name = arrow; name(a)
|
|
476
851
|
'()'(callee, ...rest) {
|
|
852
|
+
if (callee === 'Array') {
|
|
853
|
+
const lit = lowerArrayConstructor(rest[0])
|
|
854
|
+
if (lit) return lit
|
|
855
|
+
}
|
|
477
856
|
if (Array.isArray(callee) && callee[0] === '()' && Array.isArray(callee[1]) && callee[1][0] === 'function' && callee[1][1]) {
|
|
478
857
|
const [, name, params, body] = callee[1]
|
|
479
|
-
const [p2, b2] = lowerArguments(params, body)
|
|
858
|
+
const [p2, b2] = lowerArguments(params, functionBodyBlock(body))
|
|
480
859
|
return [';', ['let', ['=', name, ['=>', arrowParams(p2), wrapArrowBody(b2)]]], ['()', name, ...rest.map(transform)]]
|
|
481
860
|
}
|
|
482
861
|
},
|
|
@@ -485,7 +864,7 @@ const handlers = {
|
|
|
485
864
|
// bound inside body per ES spec: `function f(){...f...}` → `(()=>{let f;f=arrow;return f})()`.
|
|
486
865
|
// Statement-form named functions are hoisted by transformScope before reaching here.
|
|
487
866
|
'function'(name, params, body) {
|
|
488
|
-
const [p2, b2] = lowerArguments(params, body)
|
|
867
|
+
const [p2, b2] = lowerArguments(params, functionBodyBlock(body))
|
|
489
868
|
const arrow = ['=>', p2, wrapArrowBody(b2)]
|
|
490
869
|
if (name) {
|
|
491
870
|
return ['()', ['()', ['=>', null, ['{}', [';',
|
|
@@ -498,7 +877,21 @@ const handlers = {
|
|
|
498
877
|
},
|
|
499
878
|
|
|
500
879
|
'=>'(params, body) {
|
|
501
|
-
|
|
880
|
+
// The subscript parser elides the `[';', stmt, null]` wrapper inside an
|
|
881
|
+
// arrow's `{` block when the block holds a single statement — the result
|
|
882
|
+
// `['{}', stmt]` is syntactically identical to an object-literal shape, and
|
|
883
|
+
// downstream `{}` handlers can no longer tell them apart. JS grammar makes
|
|
884
|
+
// it unambiguous: `=>` followed by `{` is always a block (use `=> ({...})`
|
|
885
|
+
// for an object return), so coerce every single-statement body to the
|
|
886
|
+
// canonical `['{}', [';', stmt]]` block form — `;`-wrapped, never bare.
|
|
887
|
+
let b = body
|
|
888
|
+
if (Array.isArray(b) && b[0] === '{}' && b.length === 2) {
|
|
889
|
+
const inner = b[1]
|
|
890
|
+
if (inner != null && !(Array.isArray(inner) && inner[0] === ';')) {
|
|
891
|
+
b = ['{}', [';', inner]]
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
const [p2, b2] = lowerArguments(params, b)
|
|
502
895
|
return ['=>', p2, transform(b2)]
|
|
503
896
|
},
|
|
504
897
|
|
|
@@ -514,64 +907,69 @@ const handlers = {
|
|
|
514
907
|
return ['let', ...args.map(transform)]
|
|
515
908
|
},
|
|
516
909
|
|
|
910
|
+
':'(label, body) {
|
|
911
|
+
if (typeof label === 'string' && Array.isArray(body) && LABEL_BODY_OPS.has(body[0]))
|
|
912
|
+
return ['label', label, transform(body)]
|
|
913
|
+
},
|
|
914
|
+
|
|
517
915
|
'='(lhs, rhs) {
|
|
518
|
-
|
|
519
|
-
if (Array.isArray(lhs) && lhs[0] === '.' && Array.isArray(rhs) && rhs[0] === '=') {
|
|
520
|
-
const targets = []
|
|
521
|
-
let cur = ['=', lhs, rhs]
|
|
522
|
-
while (Array.isArray(cur) && cur[0] === '=') { targets.push(cur[1]); cur = cur[2] }
|
|
523
|
-
const val = transform(cur)
|
|
524
|
-
const stmts = []
|
|
525
|
-
for (let i = targets.length - 1; i >= 0; i--) stmts.push(['=', transform(targets[i]), val])
|
|
526
|
-
return stmts.length === 1 ? stmts[0] : [';', ...stmts]
|
|
527
|
-
}
|
|
916
|
+
if (isDestructurePat(lhs)) return ['=', transformPattern(lhs), transform(rhs)]
|
|
528
917
|
},
|
|
529
918
|
|
|
530
919
|
'switch'(disc, ...cases) {
|
|
531
920
|
const clean = cases.map(c => {
|
|
532
|
-
if (c[0] === 'case'
|
|
533
|
-
|
|
534
|
-
const stripped = stripTerminalSwitchBreak(body.length === 1 ? body[0] : [';', ...body])
|
|
535
|
-
return ['case', c[1], stripped]
|
|
536
|
-
}
|
|
537
|
-
if (c[0] === 'default' && Array.isArray(c[1]) && c[1][0] === ';') {
|
|
538
|
-
const body = c[1].slice(1).filter(s => s != null && typeof s !== 'number')
|
|
539
|
-
const stripped = stripTerminalSwitchBreak(body.length === 1 ? body[0] : [';', ...body])
|
|
540
|
-
return ['default', stripped]
|
|
541
|
-
}
|
|
542
|
-
if (c[0] === 'case') return ['case', c[1], stripTerminalSwitchBreak(c[2])]
|
|
543
|
-
if (c[0] === 'default') return ['default', stripTerminalSwitchBreak(c[1])]
|
|
921
|
+
if (c[0] === 'case') return ['case', c[1], normalizeCaseBody(c[2])]
|
|
922
|
+
if (c[0] === 'default') return ['default', normalizeCaseBody(c[1])]
|
|
544
923
|
return c
|
|
545
924
|
})
|
|
546
925
|
return transformSwitch(disc, clean)
|
|
547
926
|
},
|
|
548
927
|
|
|
549
|
-
//
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
928
|
+
// Equality keeps the JS loose/strict distinction (jz core now lowers both):
|
|
929
|
+
// `==`/`!=` stay loose, `===`/`!==` stay strict. A comparison against a prototype
|
|
930
|
+
// object folds to a boolean — jz has no prototype objects, so identity against one
|
|
931
|
+
// is decided statically. The one exception is `obj.M <eq> Ctor.prototype.M`: that
|
|
932
|
+
// probes whether `obj` overrides the builtin `M`, so it lowers to a runtime
|
|
933
|
+
// `obj.hasOwnProperty('M')` (equal ⇒ not overridden, unequal ⇒ overridden).
|
|
934
|
+
'=='(a, b) { const own = methodOverrideHasOwn(a, b); if (own) return ['!', own]; return isProto(a) || isProto(b) ? 1 : ['==', transform(a), transform(b)] },
|
|
935
|
+
'!='(a, b) { const own = methodOverrideHasOwn(a, b); if (own) return own; return isProto(a) || isProto(b) ? 0 : ['!=', transform(a), transform(b)] },
|
|
936
|
+
'==='(a, b) { const own = methodOverrideHasOwn(a, b); if (own) return ['!', own]; if (isProto(a) || isProto(b)) return 1 },
|
|
937
|
+
'!=='(a, b) { const own = methodOverrideHasOwn(a, b); if (own) return own; if (isProto(a) || isProto(b)) return 0 },
|
|
554
938
|
|
|
555
939
|
// new → call (keep TypedArrays)
|
|
556
940
|
'new'(ctor, ...cargs) {
|
|
557
|
-
if (Array.isArray(ctor) && ctor[0] === '()' &&
|
|
558
|
-
|
|
941
|
+
if (Array.isArray(ctor) && ctor[0] === '()' && ctor[1] === 'Array') {
|
|
942
|
+
const lit = lowerArrayConstructor(ctor[2])
|
|
943
|
+
if (lit) return lit
|
|
559
944
|
}
|
|
560
945
|
const name = typeof ctor === 'string' ? ctor : (Array.isArray(ctor) && ctor[0] === '()' ? ctor[1] : null)
|
|
561
|
-
if (typeof name === 'string' && (TYPED_ARRAYS.has(name) || name === 'Array')) return ['new', transform(ctor), ...cargs.map(transform)]
|
|
946
|
+
if (typeof name === 'string' && (TYPED_ARRAYS.has(name) || name === 'Array' || name === 'RegExp')) return ['new', transform(ctor), ...cargs.map(transform)]
|
|
562
947
|
if (Array.isArray(ctor) && ctor[0] === '()') return transform(ctor)
|
|
563
948
|
// `new C(a)` → `C(a)`; `new C` (no parens) → `C()` — a 2-element `['()', X]`
|
|
564
949
|
// is grouping parens, so a no-arg call needs the explicit `null` arg slot.
|
|
565
950
|
return ['()', transform(ctor), ...(cargs.length ? cargs.map(transform) : [null])]
|
|
566
951
|
},
|
|
567
952
|
|
|
568
|
-
// instanceof → typeof / Array.isArray
|
|
953
|
+
// instanceof → typeof / Array.isArray / __is_* helpers. jzify lets us preserve
|
|
954
|
+
// constructor identity that strict-mode `instanceof` discards — Map, Set, and
|
|
955
|
+
// TypedArrays get dedicated typed predicates (__is_map / __is_set / __is_typed)
|
|
956
|
+
// that both compile to a runtime __ptr_type check and feed extractRefinements
|
|
957
|
+
// for downstream method-dispatch elision (e.g. `if (x instanceof Map) x.has(k)`
|
|
958
|
+
// resolves to __map_has, not the default __set_has fallback). Date / RegExp /
|
|
959
|
+
// Object stay on the weak typeof-object lowering — they share PTR.OBJECT and
|
|
960
|
+
// the JS runtime offers no cheaper discrimination.
|
|
569
961
|
'instanceof'(val, ctor) {
|
|
570
962
|
const t = transform(val)
|
|
571
963
|
const name = typeof ctor === 'string' ? ctor : (Array.isArray(ctor) && ctor[0] === '()' ? ctor[1] : null)
|
|
964
|
+
// Static fold: literal shape of LHS already discriminates against the constructor.
|
|
965
|
+
const fold = staticInstanceofFold(val, name)
|
|
966
|
+
if (fold != null) return [null, fold]
|
|
572
967
|
if (name === 'Array') return ['()', ['.', 'Array', 'isArray'], t]
|
|
573
|
-
if (name === '
|
|
574
|
-
if (
|
|
968
|
+
if (name === 'Map') return ['()', '__is_map', t]
|
|
969
|
+
if (name === 'Set') return ['()', '__is_set', t]
|
|
970
|
+
if (typeof name === 'string' && TYPED_ARRAYS.has(name) && name !== 'ArrayBuffer' && name !== 'DataView')
|
|
971
|
+
return ['()', '__is_typed', t]
|
|
972
|
+
// Object / ArrayBuffer / DataView / RegExp / Date / unknown: weak typeof-object check.
|
|
575
973
|
return ['===', ['typeof', t], [null, 'object']]
|
|
576
974
|
},
|
|
577
975
|
|
|
@@ -585,8 +983,27 @@ const handlers = {
|
|
|
585
983
|
['while', ['||', flag, transform(cond)], ['{}', [';', ['=', flag, [null, false]], transform(body)]]]]
|
|
586
984
|
},
|
|
587
985
|
|
|
588
|
-
// Block body: recurse as scope for hoisting
|
|
589
|
-
|
|
986
|
+
// Block body: recurse as scope for hoisting. transformScope reduces a single-statement
|
|
987
|
+
// sequence to its bare element; if the input WAS block-shaped (`;` list or a single
|
|
988
|
+
// statement op), we re-wrap the collapsed result in `[';', ...]` so prepare.js keeps
|
|
989
|
+
// routing the `{}` through the block branch instead of mistaking it for `{ expr }`.
|
|
990
|
+
'{}'(...args) {
|
|
991
|
+
const loweredObject = lowerObjectLiteralThis(args)
|
|
992
|
+
if (loweredObject) return loweredObject
|
|
993
|
+
|
|
994
|
+
return ['{}', ...args.map((a, i) => {
|
|
995
|
+
const t = transformScope(a) ?? a
|
|
996
|
+
if (i !== 0 || a == null) return t
|
|
997
|
+
const blockIn = Array.isArray(a) && JZ_BLOCK_OPS.has(a[0])
|
|
998
|
+
if (!blockIn || t == null) return t
|
|
999
|
+
// transformScope collapses a single-statement `;`-list to its bare element;
|
|
1000
|
+
// re-wrap so the block always stays `['{}', [';', ...]]`. The `;` is the
|
|
1001
|
+
// only reliable block marker — a bare statement op can desugar to an
|
|
1002
|
+
// expression op downstream (postfix `c++` → `['-', ...]`) and lose its
|
|
1003
|
+
// block identity, leaving `['{}', expr]` mistakable for an object literal.
|
|
1004
|
+
return Array.isArray(t) && t[0] === ';' ? t : [';', t]
|
|
1005
|
+
})]
|
|
1006
|
+
},
|
|
590
1007
|
|
|
591
1008
|
// Export: recurse into exported declaration. Statement-form `export function name`
|
|
592
1009
|
// and `export default function name` must be hoisted as const-arrows — otherwise
|
|
@@ -642,16 +1059,16 @@ function foldStaticExportHelpers(ast) {
|
|
|
642
1059
|
|
|
643
1060
|
const defPropAliases = new Set()
|
|
644
1061
|
for (const stmt of body) {
|
|
645
|
-
|
|
646
|
-
|
|
1062
|
+
const b = bindingOf(stmt)
|
|
1063
|
+
if (b && isObjectDefineProperty(b[1])) defPropAliases.add(b[0])
|
|
647
1064
|
}
|
|
648
1065
|
if (!defPropAliases.size) return ast
|
|
649
1066
|
|
|
650
1067
|
const helperNames = new Set()
|
|
651
1068
|
for (const stmt of body) {
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
helperNames.add(
|
|
1069
|
+
const b = bindingOf(stmt)
|
|
1070
|
+
if (b && Array.isArray(b[1]) && b[1][0] === '=>' && containsDefinePropertyCall(b[1], defPropAliases))
|
|
1071
|
+
helperNames.add(b[0])
|
|
655
1072
|
}
|
|
656
1073
|
if (!helperNames.size) return ast
|
|
657
1074
|
|
|
@@ -671,6 +1088,210 @@ function foldStaticExportHelpers(ast) {
|
|
|
671
1088
|
return rewritten.length === 0 ? null : rewritten.length === 1 ? rewritten[0] : [';', ...rewritten]
|
|
672
1089
|
}
|
|
673
1090
|
|
|
1091
|
+
// Esbuild's CommonJS/ESM interop helpers alias Object reflection built-ins into
|
|
1092
|
+
// locals (`var __create = Object.create`, `var __getOwnPropNames =
|
|
1093
|
+
// Object.getOwnPropertyNames`, ...). jz deliberately does not expose those
|
|
1094
|
+
// built-ins as first-class function values, but the helpers are static enough to
|
|
1095
|
+
// lower back to the supported direct calls and module reads.
|
|
1096
|
+
function foldStaticBundlerHelpers(ast) {
|
|
1097
|
+
const body = astSeq(ast)
|
|
1098
|
+
if (!body) return ast
|
|
1099
|
+
const binds = body.map(bindingOf) // [name, init] | null, index-aligned with body
|
|
1100
|
+
|
|
1101
|
+
// Local aliases of Object reflection built-ins: name -> canonical built-in.
|
|
1102
|
+
// esbuild's interop preamble always emits these (`var __defProp =
|
|
1103
|
+
// Object.defineProperty`, ...); their absence proves the input is not a
|
|
1104
|
+
// bundle, so the fold stays a strict no-op rather than guessing.
|
|
1105
|
+
const aliases = new Map()
|
|
1106
|
+
for (const b of binds) {
|
|
1107
|
+
const key = b && objectBuiltinKey(b[1])
|
|
1108
|
+
if (key) aliases.set(b[0], key)
|
|
1109
|
+
}
|
|
1110
|
+
if (!aliases.size) return ast
|
|
1111
|
+
|
|
1112
|
+
// __copyProps: an arrow driving both aliased getOwnPropertyNames + defineProperty.
|
|
1113
|
+
const copyHelpers = new Set()
|
|
1114
|
+
for (const b of binds)
|
|
1115
|
+
if (b && isArrow(b[1]) &&
|
|
1116
|
+
containsCall(b[1], c => aliases.get(c) === 'Object.getOwnPropertyNames') &&
|
|
1117
|
+
containsCall(b[1], c => aliases.get(c) === 'Object.defineProperty'))
|
|
1118
|
+
copyHelpers.add(b[0])
|
|
1119
|
+
|
|
1120
|
+
// __toESM: an arrow cloning a module behind a prototype, tagging default/__esModule.
|
|
1121
|
+
const interopHelpers = new Set()
|
|
1122
|
+
for (const b of binds)
|
|
1123
|
+
if (b && isArrow(b[1]) &&
|
|
1124
|
+
containsCall(b[1], c => aliases.get(c) === 'Object.create') &&
|
|
1125
|
+
containsCall(b[1], c => aliases.get(c) === 'Object.getPrototypeOf') &&
|
|
1126
|
+
containsCall(b[1], c => copyHelpers.has(c)) &&
|
|
1127
|
+
astSome(b[1], n => n === 'default') && astSome(b[1], n => n === '__esModule'))
|
|
1128
|
+
interopHelpers.add(b[0])
|
|
1129
|
+
|
|
1130
|
+
// Bindings produced by an interop-helper call: name -> wrapped module expression.
|
|
1131
|
+
const interopBindings = new Map()
|
|
1132
|
+
for (const b of binds)
|
|
1133
|
+
if (b && Array.isArray(b[1]) && b[1][0] === '()' && interopHelpers.has(b[1][1])) {
|
|
1134
|
+
const args = callArgs(b[1].slice(2))
|
|
1135
|
+
if (args.length) interopBindings.set(b[0], args[0])
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
let out = body.map(stmt => rewriteBundlerAliases(stmt, aliases, interopBindings))
|
|
1139
|
+
if (interopBindings.size) out = out.map(stmt => replaceInteropReads(stmt, interopBindings))
|
|
1140
|
+
out = out.map(stmt => rewriteBundlerAliases(stmt, aliases, interopBindings)).filter(s => s != null)
|
|
1141
|
+
|
|
1142
|
+
// Drop synthetic alias/helper bindings nothing references after rewriting.
|
|
1143
|
+
const synthetic = n => aliases.has(n) || copyHelpers.has(n) || interopHelpers.has(n) || interopBindings.has(n)
|
|
1144
|
+
const live = new Set()
|
|
1145
|
+
for (const stmt of out) {
|
|
1146
|
+
const b = bindingOf(stmt)
|
|
1147
|
+
if (!(b && synthetic(b[0]))) collectRefs(stmt, live)
|
|
1148
|
+
}
|
|
1149
|
+
out = out.filter(stmt => {
|
|
1150
|
+
const b = bindingOf(stmt)
|
|
1151
|
+
return !(b && synthetic(b[0]) && !live.has(b[0]))
|
|
1152
|
+
})
|
|
1153
|
+
|
|
1154
|
+
return out.length === 0 ? null : out.length === 1 ? out[0] : [';', ...out]
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
const isArrow = node => Array.isArray(node) && node[0] === '=>'
|
|
1158
|
+
|
|
1159
|
+
const OBJECT_BUILTINS = new Set(['create', 'getPrototypeOf', 'getOwnPropertyNames', 'getOwnPropertyDescriptor', 'defineProperty'])
|
|
1160
|
+
|
|
1161
|
+
// Canonical name of the Object reflection built-in `node` references, or null.
|
|
1162
|
+
function objectBuiltinKey(node) {
|
|
1163
|
+
if (!Array.isArray(node) || node[0] !== '.') return null
|
|
1164
|
+
if (node[1] === 'Object' && OBJECT_BUILTINS.has(node[2])) return 'Object.' + node[2]
|
|
1165
|
+
return isObjectHasOwnPropertyRef(node) ? 'Object.prototype.hasOwnProperty' : null
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// Deep `some` over AST children (skips the op slot, so op names never match).
|
|
1169
|
+
function astSome(node, pred) {
|
|
1170
|
+
if (pred(node)) return true
|
|
1171
|
+
if (!Array.isArray(node)) return false
|
|
1172
|
+
for (let i = 1; i < node.length; i++) if (astSome(node[i], pred)) return true
|
|
1173
|
+
return false
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// Does `node` contain a `()` call whose string callee satisfies `ok`?
|
|
1177
|
+
const containsCall = (node, ok) =>
|
|
1178
|
+
astSome(node, n => Array.isArray(n) && n[0] === '()' && typeof n[1] === 'string' && ok(n[1]))
|
|
1179
|
+
|
|
1180
|
+
function rewriteBundlerAliases(node, aliases, interopBindings) {
|
|
1181
|
+
if (!Array.isArray(node)) return node
|
|
1182
|
+
const rec = n => rewriteBundlerAliases(n, aliases, interopBindings)
|
|
1183
|
+
|
|
1184
|
+
if (node[0] === ';') {
|
|
1185
|
+
const out = [';']
|
|
1186
|
+
for (let i = 1; i < node.length; i++) {
|
|
1187
|
+
const child = rec(node[i])
|
|
1188
|
+
if (child != null) out.push(child)
|
|
1189
|
+
}
|
|
1190
|
+
return out.length === 1 ? null : out.length === 2 ? out[1] : out
|
|
1191
|
+
}
|
|
1192
|
+
if (node[0] === '{}' && node.length === 2) {
|
|
1193
|
+
const wasBlock = Array.isArray(node[1]) && JZ_BLOCK_OPS.has(node[1][0])
|
|
1194
|
+
const inner = rec(node[1])
|
|
1195
|
+
if (!wasBlock || inner == null) return ['{}', inner]
|
|
1196
|
+
const stayed = Array.isArray(inner) && JZ_BLOCK_OPS.has(inner[0])
|
|
1197
|
+
return ['{}', stayed ? inner : [';', inner]]
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
if (node[0] === '()') {
|
|
1201
|
+
const callee = node[1]
|
|
1202
|
+
const args = callArgs(node.slice(2))
|
|
1203
|
+
|
|
1204
|
+
if (typeof callee === 'string') {
|
|
1205
|
+
const key = aliases.get(callee)
|
|
1206
|
+
if (key === 'Object.defineProperty') {
|
|
1207
|
+
const define = staticDefineProperty(args)
|
|
1208
|
+
if (define !== undefined) return define
|
|
1209
|
+
}
|
|
1210
|
+
if (key === 'Object.getOwnPropertyNames' || key === 'Object.create') {
|
|
1211
|
+
if (key === 'Object.create' && isGetPrototypeOfCall(args[0], aliases)) return ['{}', null]
|
|
1212
|
+
return ['()', key, ...args.map(rec)]
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
// `__hasOwnProp.call(o, k)` -> `o.hasOwnProperty(k)`.
|
|
1216
|
+
if (Array.isArray(callee) && callee[0] === '.' && callee[2] === 'call' && args.length >= 2 &&
|
|
1217
|
+
typeof callee[1] === 'string' && aliases.get(callee[1]) === 'Object.prototype.hasOwnProperty')
|
|
1218
|
+
return ['()', ['.', rec(args[0]), 'hasOwnProperty'], rec(args[1])]
|
|
1219
|
+
// `(0, fn)(...)` comma-call resolving to an interop module read.
|
|
1220
|
+
const seqCall = commaZeroCall(callee, interopBindings)
|
|
1221
|
+
if (seqCall) return ['()', seqCall, ...args.map(rec)]
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
if (node[0] === '.' || node[0] === '?.') return [node[0], rec(node[1]), node[2]]
|
|
1225
|
+
if (node[0] === ':') return [node[0], node[1], rec(node[2])]
|
|
1226
|
+
return node.map((part, i) => i === 0 ? part : rec(part))
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
function replaceInteropReads(node, bindings) {
|
|
1230
|
+
if (typeof node === 'string' && bindings.has(node)) return cloneAst(bindings.get(node))
|
|
1231
|
+
if (!Array.isArray(node)) return node
|
|
1232
|
+
if (node[0] === '=' && typeof node[1] === 'string') return ['=', node[1], replaceInteropReads(node[2], bindings)]
|
|
1233
|
+
if (node[0] === 'let' || node[0] === 'const' || node[0] === 'var')
|
|
1234
|
+
return [node[0], ...node.slice(1).map(decl =>
|
|
1235
|
+
Array.isArray(decl) && decl[0] === '=' ? ['=', decl[1], replaceInteropReads(decl[2], bindings)] : decl)]
|
|
1236
|
+
if ((node[0] === '.' || node[0] === '?.') && typeof node[1] === 'string' && typeof node[2] === 'string' && bindings.has(node[1])) {
|
|
1237
|
+
const mod = cloneAst(bindings.get(node[1]))
|
|
1238
|
+
return node[2] === 'default' ? mod : [node[0], mod, node[2]]
|
|
1239
|
+
}
|
|
1240
|
+
if (node[0] === ':') return [node[0], node[1], replaceInteropReads(node[2], bindings)]
|
|
1241
|
+
return node.map((part, i) => i === 0 ? part : replaceInteropReads(part, bindings))
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
function isGetPrototypeOfCall(node, aliases) {
|
|
1245
|
+
if (!Array.isArray(node) || node[0] !== '()') return false
|
|
1246
|
+
const callee = node[1]
|
|
1247
|
+
return (typeof callee === 'string' && aliases.get(callee) === 'Object.getPrototypeOf') ||
|
|
1248
|
+
objectBuiltinKey(callee) === 'Object.getPrototypeOf'
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
function commaZeroCall(callee, bindings) {
|
|
1252
|
+
if (!Array.isArray(callee) || callee[0] !== '()' || !Array.isArray(callee[1]) || callee[1][0] !== ',') return null
|
|
1253
|
+
const parts = callee[1].slice(1)
|
|
1254
|
+
if (parts.length !== 2 || !isZeroLiteral(parts[0])) return null
|
|
1255
|
+
const fn = replaceInteropReads(parts[1], bindings)
|
|
1256
|
+
return fn === parts[1] ? null : fn
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
// `defProp(obj, "key", descriptor)` -> `obj.key = value`; null drops `__esModule`.
|
|
1260
|
+
function staticDefineProperty(args) {
|
|
1261
|
+
if (args.length < 3) return undefined
|
|
1262
|
+
const [obj, keyExpr, desc] = args
|
|
1263
|
+
const key = stringLiteral(keyExpr)
|
|
1264
|
+
const props = objectProps(desc)
|
|
1265
|
+
if (typeof key !== 'string' || !props) return undefined
|
|
1266
|
+
if (key === '__esModule') return null
|
|
1267
|
+
const prop = name => props.find(p => Array.isArray(p) && p[0] === ':' && p[1] === name)?.[2]
|
|
1268
|
+
const value = prop('value')
|
|
1269
|
+
if (value !== undefined) return ['=', ['.', obj, key], value]
|
|
1270
|
+
const got = getterReturnExpr(prop('get'))
|
|
1271
|
+
return got !== null ? ['=', ['.', obj, key], got] : undefined
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
function stringLiteral(node) {
|
|
1275
|
+
return Array.isArray(node) && node[0] == null && typeof node[1] === 'string' ? node[1] : null
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
// Identifier references in `node`, excluding declaration names, member property
|
|
1279
|
+
// names and object keys — enough to decide whether a synthetic binding is live.
|
|
1280
|
+
function collectRefs(node, out) {
|
|
1281
|
+
if (typeof node === 'string') return void out.add(node)
|
|
1282
|
+
if (!Array.isArray(node)) return
|
|
1283
|
+
if (node[0] === 'let' || node[0] === 'const' || node[0] === 'var') {
|
|
1284
|
+
for (let i = 1; i < node.length; i++)
|
|
1285
|
+
if (Array.isArray(node[i]) && node[i][0] === '=') collectRefs(node[i][2], out)
|
|
1286
|
+
} else if ((node[0] === '.' || node[0] === '?.') && typeof node[2] === 'string') {
|
|
1287
|
+
collectRefs(node[1], out)
|
|
1288
|
+
} else if (node[0] === ':') {
|
|
1289
|
+
collectRefs(node[2], out)
|
|
1290
|
+
} else {
|
|
1291
|
+
for (let i = 1; i < node.length; i++) collectRefs(node[i], out)
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
|
|
674
1295
|
function astSeq(ast) {
|
|
675
1296
|
if (!Array.isArray(ast)) return null
|
|
676
1297
|
return ast[0] === ';' ? ast.slice(1).filter(Boolean) : [ast]
|
|
@@ -680,12 +1301,28 @@ function isObjectDefineProperty(node) {
|
|
|
680
1301
|
return Array.isArray(node) && node[0] === '.' && node[1] === 'Object' && node[2] === 'defineProperty'
|
|
681
1302
|
}
|
|
682
1303
|
|
|
1304
|
+
/** Unwrap an esbuild module binding to `[name, init]`. After hoistVars, a binding
|
|
1305
|
+
* reaches this pass either split into a bare `['=', name, init]` (RHS hoisted out
|
|
1306
|
+
* as a separate `let name;`) or kept as a single `['let', ['=', name, init]]` decl
|
|
1307
|
+
* (arrow RHS — see the `;` handler in hoistVars). The fold keys on name/init,
|
|
1308
|
+
* so it must see through both shapes. */
|
|
1309
|
+
function bindingOf(stmt) {
|
|
1310
|
+
if (!Array.isArray(stmt)) return null
|
|
1311
|
+
if (stmt[0] === '=' && typeof stmt[1] === 'string') return [stmt[1], stmt[2]]
|
|
1312
|
+
if ((stmt[0] === 'let' || stmt[0] === 'const' || stmt[0] === 'var') && stmt.length === 2 &&
|
|
1313
|
+
Array.isArray(stmt[1]) && stmt[1][0] === '=' && typeof stmt[1][1] === 'string')
|
|
1314
|
+
return [stmt[1][1], stmt[1][2]]
|
|
1315
|
+
return null
|
|
1316
|
+
}
|
|
1317
|
+
|
|
683
1318
|
function isDefPropAliasAssign(stmt, aliases) {
|
|
684
|
-
|
|
1319
|
+
const b = bindingOf(stmt)
|
|
1320
|
+
return b != null && aliases.has(b[0]) && isObjectDefineProperty(b[1])
|
|
685
1321
|
}
|
|
686
1322
|
|
|
687
1323
|
function isExportHelperAssign(stmt, helpers) {
|
|
688
|
-
|
|
1324
|
+
const b = bindingOf(stmt)
|
|
1325
|
+
return b != null && helpers.has(b[0])
|
|
689
1326
|
}
|
|
690
1327
|
|
|
691
1328
|
function containsDefinePropertyCall(node, aliases) {
|
|
@@ -730,6 +1367,8 @@ function getterReturnExpr(node) {
|
|
|
730
1367
|
if (params.length !== 0) return null
|
|
731
1368
|
const body = node[2]
|
|
732
1369
|
if (Array.isArray(body) && body[0] === '{}' && Array.isArray(body[1]) && body[1][0] === 'return') return body[1][1]
|
|
1370
|
+
if (Array.isArray(body) && body[0] === '{}' && Array.isArray(body[1]) && body[1][0] === ';' &&
|
|
1371
|
+
Array.isArray(body[1][1]) && body[1][1][0] === 'return') return body[1][1][1]
|
|
733
1372
|
if (Array.isArray(body) && body[0] === 'return') return body[1]
|
|
734
1373
|
return body
|
|
735
1374
|
}
|
|
@@ -752,6 +1391,9 @@ function canonicalizeObjectIdioms(node) {
|
|
|
752
1391
|
const hasOwnCall = objectHasOwnPropertyCall(out)
|
|
753
1392
|
if (hasOwnCall) return ['()', ['.', hasOwnCall.obj, 'hasOwnProperty'], hasOwnCall.key]
|
|
754
1393
|
|
|
1394
|
+
const mapString = arrayMapStringCallback(out)
|
|
1395
|
+
if (mapString) return mapString
|
|
1396
|
+
|
|
755
1397
|
if (out[0] === '&&') {
|
|
756
1398
|
const leftCtor = constructorIsObject(out[1])
|
|
757
1399
|
const rightKeys = objectKeysLengthZero(out[2])
|
|
@@ -765,16 +1407,31 @@ function canonicalizeObjectIdioms(node) {
|
|
|
765
1407
|
return out
|
|
766
1408
|
}
|
|
767
1409
|
|
|
1410
|
+
function arrayMapStringCallback(node) {
|
|
1411
|
+
if (!Array.isArray(node) || node[0] !== '()') return null
|
|
1412
|
+
const callee = node[1]
|
|
1413
|
+
if (!Array.isArray(callee) || callee[0] !== '.' || callee[2] !== 'map') return null
|
|
1414
|
+
const args = callArgs(node.slice(2))
|
|
1415
|
+
if (args.length !== 1 || args[0] !== 'String') return null
|
|
1416
|
+
return ['()', callee, ['=>', 'value', ['()', 'String', 'value']]]
|
|
1417
|
+
}
|
|
1418
|
+
|
|
768
1419
|
function objectHasOwnPropertyCall(node) {
|
|
769
1420
|
if (!Array.isArray(node) || node[0] !== '()') return null
|
|
770
1421
|
const callee = node[1]
|
|
771
1422
|
if (!Array.isArray(callee) || callee[0] !== '.' || callee[2] !== 'call') return null
|
|
772
|
-
if (!
|
|
1423
|
+
if (!isObjectHasOwnPropertyRef(callee[1])) return null
|
|
773
1424
|
const args = callArgs(node.slice(2))
|
|
774
1425
|
if (args.length < 2) return null
|
|
775
1426
|
return { obj: args[0], key: args[1] }
|
|
776
1427
|
}
|
|
777
1428
|
|
|
1429
|
+
function isObjectHasOwnPropertyRef(node) {
|
|
1430
|
+
if (!Array.isArray(node) || node[0] !== '.' || node[2] !== 'hasOwnProperty') return false
|
|
1431
|
+
if (node[1] === 'Object') return true
|
|
1432
|
+
return Array.isArray(node[1]) && node[1][0] === '.' && node[1][1] === 'Object' && node[1][2] === 'prototype'
|
|
1433
|
+
}
|
|
1434
|
+
|
|
778
1435
|
function constructorIsObject(node) {
|
|
779
1436
|
if (!Array.isArray(node) || (node[0] !== '===' && node[0] !== '==')) return null
|
|
780
1437
|
const left = constructorReceiver(node[1])
|
|
@@ -821,41 +1478,103 @@ function cloneAst(node) {
|
|
|
821
1478
|
return node.map(cloneAst)
|
|
822
1479
|
}
|
|
823
1480
|
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
1481
|
+
/** Flatten a switch clause body to a single node, dropping ASI position markers.
|
|
1482
|
+
* Unlike a plain statement list this keeps any `break` intact — transformSwitch
|
|
1483
|
+
* needs the breaks to gate fall-through; it rewrites them to a sticky flag. */
|
|
1484
|
+
function normalizeCaseBody(body) {
|
|
1485
|
+
if (!Array.isArray(body) || body[0] !== ';') return body
|
|
1486
|
+
const stmts = body.slice(1).filter(s => s != null && typeof s !== 'number')
|
|
1487
|
+
return stmts.length === 0 ? null : stmts.length === 1 ? stmts[0] : [';', ...stmts]
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
const SWITCH_BREAK_BOUNDARIES = new Set(['for', 'for-in', 'for-of', 'while', 'do', 'switch', '=>', 'function', 'class'])
|
|
1491
|
+
|
|
1492
|
+
function hasOwnSwitchBreak(node) {
|
|
1493
|
+
if (!Array.isArray(node)) return false
|
|
1494
|
+
if (node[0] === 'break') return true
|
|
1495
|
+
if (SWITCH_BREAK_BOUNDARIES.has(node[0])) return false
|
|
1496
|
+
for (let i = 1; i < node.length; i++) if (hasOwnSwitchBreak(node[i])) return true
|
|
1497
|
+
return false
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
function rewriteSwitchBreaks(node, flag) {
|
|
1501
|
+
if (!Array.isArray(node)) return node
|
|
1502
|
+
const op = node[0]
|
|
1503
|
+
if (op === 'break') return ['=', flag, [null, true]]
|
|
1504
|
+
if (SWITCH_BREAK_BOUNDARIES.has(op)) return node
|
|
1505
|
+
|
|
1506
|
+
if (op === ';') {
|
|
1507
|
+
const out = []
|
|
1508
|
+
const stmts = node.slice(1)
|
|
1509
|
+
for (let i = 0; i < stmts.length; i++) {
|
|
1510
|
+
const stmt = stmts[i]
|
|
1511
|
+
out.push(rewriteSwitchBreaks(stmt, flag))
|
|
1512
|
+
if (hasOwnSwitchBreak(stmt) && i < stmts.length - 1) {
|
|
1513
|
+
const tail = rewriteSwitchBreaks([';', ...stmts.slice(i + 1)], flag)
|
|
1514
|
+
out.push(['if', ['!', flag], tail])
|
|
1515
|
+
break
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
return out.length === 0 ? null : out.length === 1 ? out[0] : [';', ...out]
|
|
831
1519
|
}
|
|
832
|
-
if (body[0] !== ';') return body
|
|
833
1520
|
|
|
834
|
-
|
|
835
|
-
if (Array.isArray(stmts.at(-1)) && stmts.at(-1)[0] === 'break') stmts.pop()
|
|
836
|
-
return stmts.length === 0 ? null : stmts.length === 1 ? stmts[0] : [';', ...stmts]
|
|
1521
|
+
return node.map((part, i) => i === 0 ? part : rewriteSwitchBreaks(part, flag))
|
|
837
1522
|
}
|
|
838
1523
|
|
|
839
|
-
/** Transform switch
|
|
1524
|
+
/** Transform a switch into structured control flow with faithful fall-through.
|
|
1525
|
+
*
|
|
1526
|
+
* A pure if/else-if chain (the former lowering) can't express fall-through,
|
|
1527
|
+
* stacked labels, or a `default` clause that isn't last \u2014 it ran only the first
|
|
1528
|
+
* matching body. The correct model is two-phase, evaluated once with no goto:
|
|
1529
|
+
*
|
|
1530
|
+
* 1. ENTRY \u2014 compare the discriminant against each `case` label in source
|
|
1531
|
+
* order; the first `===` match fixes the entry index. No case matches \u2192
|
|
1532
|
+
* entry = the `default` clause's source index (or past-end if none).
|
|
1533
|
+
* 2. RUN \u2014 walk clauses in source order; `entry <= i` runs clause i, so every
|
|
1534
|
+
* clause from the entry onward executes (fall-through). A `break` flips the
|
|
1535
|
+
* sticky `brk` flag (via rewriteSwitchBreaks) and gates the rest.
|
|
1536
|
+
*
|
|
1537
|
+
* The discriminant is bound to a temp only when re-reading it isn't free/safe; a
|
|
1538
|
+
* bare identifier is compared directly \u2014 a synthetic temp would shed its STRING
|
|
1539
|
+
* val-type and mis-fold string `case`s to `false` under strict-=== folding. */
|
|
840
1540
|
let swIdx = 0
|
|
841
1541
|
function transformSwitch(discriminant, cases) {
|
|
842
1542
|
const disc = transform(discriminant)
|
|
843
|
-
const
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
const
|
|
1543
|
+
const simple = typeof disc === 'string' || (Array.isArray(disc) && disc[0] == null)
|
|
1544
|
+
const tmp = simple ? disc : `\uE000sw${swIdx++}`
|
|
1545
|
+
const start = `\uE000swst${swIdx++}`
|
|
1546
|
+
const needsBreakFlag = cases.some(c => hasOwnSwitchBreak(c[0] === 'case' ? c[2] : c[1]))
|
|
1547
|
+
const brk = needsBreakFlag ? `\uE000swbrk${swIdx++}` : null
|
|
1548
|
+
|
|
1549
|
+
const n = cases.length
|
|
1550
|
+
let defaultIdx = -1
|
|
1551
|
+
const bodies = cases.map((c, i) => {
|
|
1552
|
+
if (c[0] === 'default') { defaultIdx = i; return transform(c[1]) }
|
|
1553
|
+
return transform(c[2])
|
|
1554
|
+
})
|
|
1555
|
+
|
|
1556
|
+
const stmts = []
|
|
1557
|
+
if (!simple) stmts.push(['let', ['=', tmp, disc]])
|
|
1558
|
+
|
|
1559
|
+
// Phase 1 \u2014 entry index. Init to default's position (or n = "no clause runs"),
|
|
1560
|
+
// then let the first matching label override it via an if/else-if chain.
|
|
1561
|
+
stmts.push(['let', ['=', start, [null, defaultIdx >= 0 ? defaultIdx : n]]])
|
|
847
1562
|
let chain = null
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
const
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
} else if (c[0] === 'case') {
|
|
854
|
-
const cond = ['===', tmp, transform(c[1])]
|
|
855
|
-
const body = transform(c[2])
|
|
856
|
-
chain = chain != null ? ['if', cond, body, chain] : ['if', cond, body]
|
|
857
|
-
}
|
|
1563
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
1564
|
+
if (cases[i][0] !== 'case') continue
|
|
1565
|
+
const hit = ['=', start, [null, i]]
|
|
1566
|
+
const cond = ['===', tmp, transform(cases[i][1])]
|
|
1567
|
+
chain = chain != null ? ['if', cond, hit, chain] : ['if', cond, hit]
|
|
858
1568
|
}
|
|
859
1569
|
if (chain) stmts.push(chain)
|
|
1570
|
+
if (brk) stmts.push(['let', ['=', brk, [null, false]]])
|
|
1571
|
+
|
|
1572
|
+
// Phase 2 \u2014 run clauses from the entry index, falling through until a break.
|
|
1573
|
+
for (let i = 0; i < n; i++) {
|
|
1574
|
+
if (bodies[i] == null) continue
|
|
1575
|
+
const body = brk ? rewriteSwitchBreaks(bodies[i], brk) : bodies[i]
|
|
1576
|
+
const reached = ['<=', start, [null, i]]
|
|
1577
|
+
stmts.push(['if', brk ? ['&&', ['!', brk], reached] : reached, body])
|
|
1578
|
+
}
|
|
860
1579
|
return [';', ...stmts]
|
|
861
1580
|
}
|