jz 0.4.0 → 0.5.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 +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 +251 -118
- package/module/date.js +9 -5
- package/module/function.js +11 -1
- package/module/json.js +361 -55
- package/module/math.js +551 -128
- 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 +540 -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 +662 -196
- package/src/infer.js +548 -0
- package/src/ir.js +291 -23
- package/src/jzify.js +786 -94
- package/src/narrow.js +433 -251
- package/src/optimize.js +126 -122
- package/src/plan.js +703 -34
- package/src/prepare.js +822 -150
- 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,24 +321,48 @@ 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 */
|
|
@@ -276,6 +372,46 @@ const TYPED_ARRAYS = new Set(['Float64Array','Float32Array','Int32Array','Uint32
|
|
|
276
372
|
'Int16Array','Uint16Array','Int8Array','Uint8Array',
|
|
277
373
|
'ArrayBuffer','BigInt64Array','BigUint64Array','DataView'])
|
|
278
374
|
|
|
375
|
+
// Block-shape ops used to detect "this `{}` is a block body, not an object literal".
|
|
376
|
+
// Mirrors analyze.STMT_OPS — kept inline so jzify stays self-contained.
|
|
377
|
+
const JZ_BLOCK_OPS = new Set([';', 'let', 'const', 'var', 'return', 'if', 'for', 'for-in', 'for-of',
|
|
378
|
+
'while', 'do', 'break', 'continue', 'switch', 'throw', 'try', 'catch', 'finally',
|
|
379
|
+
'=', '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '>>=', '<<=', '>>>=', '||=', '&&=', '??=',
|
|
380
|
+
'++', '--', '()', 'function', 'class', 'import', 'export', 'label'])
|
|
381
|
+
const LABEL_BODY_OPS = new Set([';', 'if', 'for', 'for-in', 'for-of', 'while', 'do', 'switch', 'try', 'throw'])
|
|
382
|
+
|
|
383
|
+
/** Statically discriminate `x instanceof Ctor` when the LHS's syntactic shape
|
|
384
|
+
* already pins down its runtime type. Returns true/false or null (unknown).
|
|
385
|
+
* Matches the lhs forms a user might plausibly write: literal arrays, object
|
|
386
|
+
* literals, string/number literals, and `new C(...)` whose ctor name is known.
|
|
387
|
+
* Stays conservative — anything else returns null and falls back to the runtime
|
|
388
|
+
* predicate so behavior is never silently changed. */
|
|
389
|
+
function staticInstanceofFold(val, ctor) {
|
|
390
|
+
if (typeof ctor !== 'string' || !Array.isArray(val)) return null
|
|
391
|
+
// Unwrap grouping parens: `(expr)` parses as `['()', expr]`
|
|
392
|
+
if (val[0] === '()' && val.length === 2) return staticInstanceofFold(val[1], ctor)
|
|
393
|
+
// Array literal: `[1,2,3]` → `['[]', [',', …]]` (or `['[]']` for empty array)
|
|
394
|
+
if (val[0] === '[]' && val.length <= 2) return ctor === 'Array' || ctor === 'Object'
|
|
395
|
+
// Object literal: `{a:1}` → `['{}', [':', …], …]`
|
|
396
|
+
if (val[0] === '{}') return ctor === 'Object'
|
|
397
|
+
// Regex literal: `/x/` → `['//', …]`
|
|
398
|
+
if (val[0] === '//') return ctor === 'RegExp' || ctor === 'Object'
|
|
399
|
+
// `new C(...)` — `['new', ['()', 'C', args]]` or `['new', 'C']`
|
|
400
|
+
if (val[0] === 'new') {
|
|
401
|
+
const inner = val[1]
|
|
402
|
+
const cname = typeof inner === 'string' ? inner
|
|
403
|
+
: (Array.isArray(inner) && inner[0] === '()' && typeof inner[1] === 'string') ? inner[1]
|
|
404
|
+
: null
|
|
405
|
+
if (cname) return cname === ctor || (cname !== 'Object' && ctor === 'Object')
|
|
406
|
+
}
|
|
407
|
+
// Bare primitive: `[null, v]` where v is a primitive — never an instance per JS spec.
|
|
408
|
+
if (val[0] == null && val.length === 2) {
|
|
409
|
+
const v = val[1]
|
|
410
|
+
if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean' || v == null) return false
|
|
411
|
+
}
|
|
412
|
+
return null
|
|
413
|
+
}
|
|
414
|
+
|
|
279
415
|
// `arguments` lowering: regular `function` has implicit `arguments`; arrow doesn't.
|
|
280
416
|
// jzify converts function → arrow, so any `arguments` use must be rewritten to a rest param.
|
|
281
417
|
// Arrow functions inherit `arguments` from enclosing function — don't stop at '=>'.
|
|
@@ -373,6 +509,10 @@ function prependParamDecls(decl, body) {
|
|
|
373
509
|
}
|
|
374
510
|
|
|
375
511
|
const arrowParams = params => Array.isArray(params) && params[0] === '()' ? params : ['()', params]
|
|
512
|
+
// A method body from jessie is a bare statement / `;`-sequence — wrap it in a
|
|
513
|
+
// `{}` block so the `=>` handler treats it as a function body, not an expression
|
|
514
|
+
// (an unwrapped `;`-seq arrow body produces malformed IR).
|
|
515
|
+
const block = b => Array.isArray(b) && b[0] === '{}' ? b : ['{}', b]
|
|
376
516
|
|
|
377
517
|
// === class lowering ===
|
|
378
518
|
//
|
|
@@ -393,10 +533,19 @@ const arrowParams = params => Array.isArray(params) && params[0] === '()' ? para
|
|
|
393
533
|
// return selfN
|
|
394
534
|
// }
|
|
395
535
|
//
|
|
396
|
-
//
|
|
397
|
-
// `
|
|
398
|
-
//
|
|
536
|
+
// Simple inheritance is lowered too: `class D extends B` builds the instance
|
|
537
|
+
// from `B`'s factory — forwarding `super(...)` args, or the derived ctor params
|
|
538
|
+
// when the derived constructor is implicit — then applies D's own fields and
|
|
539
|
+
// methods over it.
|
|
540
|
+
//
|
|
541
|
+
// Out of scope (rejected with a clear message): full `super.foo` property
|
|
542
|
+
// semantics, getters/setters, non-constant computed member names. Private
|
|
543
|
+
// `#name` members are kept as the literal key string `#name` (jz allows it).
|
|
399
544
|
let classIdx = 0
|
|
545
|
+
let objThisIdx = 0
|
|
546
|
+
let staticClassIdx = 0
|
|
547
|
+
let classBaseIdx = 0
|
|
548
|
+
const DEFAULT_DERIVED_CTOR_ARITY = 8
|
|
400
549
|
|
|
401
550
|
const classBodyItems = (body) =>
|
|
402
551
|
body == null ? [] : Array.isArray(body) && body[0] === ';' ? body.slice(1) : [body]
|
|
@@ -413,39 +562,200 @@ function renameThis(node, to) {
|
|
|
413
562
|
return node.map(n => renameThis(n, to))
|
|
414
563
|
}
|
|
415
564
|
|
|
565
|
+
function usesThis(node) {
|
|
566
|
+
if (node === 'this') return true
|
|
567
|
+
if (!Array.isArray(node)) return false
|
|
568
|
+
if (node[0] === 'function' || node[0] === 'class') return false
|
|
569
|
+
if (node[0] === '.' || node[0] === '?.') return usesThis(node[1])
|
|
570
|
+
if (node[0] === ':') return usesThis(node[2])
|
|
571
|
+
return node.some(usesThis)
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function hasSuperProp(node) {
|
|
575
|
+
if (!Array.isArray(node)) return false
|
|
576
|
+
if ((node[0] === '.' || node[0] === '?.') && node[1] === 'super') return true
|
|
577
|
+
if (node[0] === '[]' && node[1] === 'super') return true
|
|
578
|
+
return node.some(hasSuperProp)
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function isSuperCall(node) {
|
|
582
|
+
return Array.isArray(node) && node[0] === '()' && node[1] === 'super'
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function literalStringKey(node) {
|
|
586
|
+
return Array.isArray(node) && node[0] == null && typeof node[1] === 'string' ? node[1] : null
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function constStringKey(node) {
|
|
590
|
+
if (typeof node === 'string') return node
|
|
591
|
+
const lit = literalStringKey(node)
|
|
592
|
+
if (lit != null) return lit
|
|
593
|
+
if (Array.isArray(node) && node[0] === '[]') return literalStringKey(node[1])
|
|
594
|
+
return null
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function superMethodName(callee) {
|
|
598
|
+
if (!Array.isArray(callee)) return null
|
|
599
|
+
if ((callee[0] === '.' || callee[0] === '?.') && callee[1] === 'super') return callee[2]
|
|
600
|
+
if (callee[0] === '[]' && callee[1] === 'super') return literalStringKey(callee[2])
|
|
601
|
+
return null
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function collectSuperMethodCalls(node, out = new Set()) {
|
|
605
|
+
if (!Array.isArray(node)) return out
|
|
606
|
+
if (node[0] === 'function' || node[0] === 'class') return out
|
|
607
|
+
if (node[0] === '()') {
|
|
608
|
+
const name = superMethodName(node[1])
|
|
609
|
+
if (name) out.add(name)
|
|
610
|
+
}
|
|
611
|
+
for (const n of node) collectSuperMethodCalls(n, out)
|
|
612
|
+
return out
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function rewriteSuperMethodCalls(node, baseMethodVars) {
|
|
616
|
+
if (!Array.isArray(node)) return node
|
|
617
|
+
if (node[0] === 'function' || node[0] === 'class') return node
|
|
618
|
+
if (node[0] === '()') {
|
|
619
|
+
const name = superMethodName(node[1])
|
|
620
|
+
if (name) {
|
|
621
|
+
const fn = baseMethodVars.get(name)
|
|
622
|
+
if (!fn) jzifyError(`super.${name} is not available on the base class`)
|
|
623
|
+
return ['()', fn, ...node.slice(2).map(n => rewriteSuperMethodCalls(n, baseMethodVars))]
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
return node.map(n => rewriteSuperMethodCalls(n, baseMethodVars))
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function splitCtorSuper(body) {
|
|
630
|
+
if (body == null) return { args: null, body }
|
|
631
|
+
if (isSuperCall(body)) return { args: body.slice(2), body: null }
|
|
632
|
+
if (Array.isArray(body) && body[0] === '{}') {
|
|
633
|
+
const inner = splitCtorSuper(body[1])
|
|
634
|
+
return { args: inner.args, body: ['{}', inner.body] }
|
|
635
|
+
}
|
|
636
|
+
if (Array.isArray(body) && body[0] === ';') {
|
|
637
|
+
const out = [';']
|
|
638
|
+
let args = null
|
|
639
|
+
for (const stmt of body.slice(1)) {
|
|
640
|
+
if (args == null && isSuperCall(stmt)) { args = stmt.slice(2); continue }
|
|
641
|
+
out.push(stmt)
|
|
642
|
+
}
|
|
643
|
+
return { args, body: out.length === 1 ? null : out.length === 2 ? out[1] : out }
|
|
644
|
+
}
|
|
645
|
+
return { args: null, body }
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// Object shorthand methods and arrow-valued properties both parse as `=>`.
|
|
649
|
+
// Stay conservative: only statement-shaped bodies are receiver methods here;
|
|
650
|
+
// expression-bodied arrows keep their lexical `this` and remain unsupported.
|
|
651
|
+
const OBJ_METHOD_BODY_OPS = new Set([';', 'return', 'if', 'for', 'for-in', 'for-of',
|
|
652
|
+
'while', 'do', 'switch', 'throw', 'try', 'break', 'continue'])
|
|
653
|
+
|
|
654
|
+
function objectLiteralProps(args) {
|
|
655
|
+
const raw = args.length === 1 && Array.isArray(args[0]) && args[0][0] === ',' ? args[0].slice(1) : args
|
|
656
|
+
return raw.filter(p => p != null)
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function isStatementBody(body) {
|
|
660
|
+
return Array.isArray(body) && OBJ_METHOD_BODY_OPS.has(body[0])
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function objectMethodUsesThis(prop) {
|
|
664
|
+
if (!Array.isArray(prop) || prop[0] !== ':' || typeof prop[1] !== 'string') return false
|
|
665
|
+
const value = prop[2]
|
|
666
|
+
if (!Array.isArray(value)) return false
|
|
667
|
+
if (value[0] === '=>' && isStatementBody(value[2])) return usesThis(value[2])
|
|
668
|
+
return false
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function lowerObjectLiteralThis(args) {
|
|
672
|
+
const props = objectLiteralProps(args)
|
|
673
|
+
if (props.length === 0 || !props.some(objectMethodUsesThis)) return null
|
|
674
|
+
if (!props.every(p => Array.isArray(p) && p[0] === ':' && typeof p[1] === 'string')) return null
|
|
675
|
+
|
|
676
|
+
const self = `obj${objThisIdx++}`
|
|
677
|
+
const litProps = props.map(p => {
|
|
678
|
+
const value = p[2]
|
|
679
|
+
if (objectMethodUsesThis(p)) {
|
|
680
|
+
return [':', p[1], transform(['=>', value[1], block(renameThis(value[2], self))])]
|
|
681
|
+
}
|
|
682
|
+
return [':', p[1], transform(value)]
|
|
683
|
+
})
|
|
684
|
+
const lit = ['{}', litProps.length === 1 ? litProps[0] : [',', ...litProps]]
|
|
685
|
+
return ['()', ['()', ['=>', null, ['{}', [';',
|
|
686
|
+
['let', ['=', self, lit]],
|
|
687
|
+
['return', self]
|
|
688
|
+
]]]], null]
|
|
689
|
+
}
|
|
690
|
+
|
|
416
691
|
function jzifyError(msg) { throw new Error(`jzify: ${msg}`) }
|
|
417
692
|
|
|
418
693
|
function lowerClass(name, heritage, body) {
|
|
419
|
-
if (heritage != null) jzifyError('`class … extends …` is not supported yet — flatten the hierarchy or compose explicitly')
|
|
420
694
|
let ctorParams = null, ctorBody = null
|
|
421
|
-
const methods = [], fields = []
|
|
695
|
+
const methods = [], fields = [], statics = []
|
|
422
696
|
for (const it of classBodyItems(body)) {
|
|
423
697
|
if (typeof it === 'string') { fields.push([it, null]); continue } // bare `x;`
|
|
424
698
|
if (!Array.isArray(it)) continue
|
|
699
|
+
const bareFieldName = constStringKey(it)
|
|
700
|
+
if (bareFieldName != null) { fields.push([bareFieldName, null]); continue }
|
|
425
701
|
if (it[0] === ':' && Array.isArray(it[2]) && it[2][0] === '=>') {
|
|
426
|
-
|
|
427
|
-
if (
|
|
428
|
-
|
|
702
|
+
const key = constStringKey(it[1])
|
|
703
|
+
if (key == null) jzifyError('non-constant computed class member names are not supported')
|
|
704
|
+
if (key === 'constructor') { ctorParams = it[2][1]; ctorBody = it[2][2] }
|
|
705
|
+
else methods.push([key, it[2][1], it[2][2]])
|
|
429
706
|
continue
|
|
430
707
|
}
|
|
431
708
|
if (it[0] === '=') {
|
|
432
709
|
const lhs = it[1]
|
|
433
|
-
if (Array.isArray(lhs) && lhs[0] === 'static')
|
|
434
|
-
|
|
435
|
-
|
|
710
|
+
if (Array.isArray(lhs) && lhs[0] === 'static') {
|
|
711
|
+
const key = constStringKey(lhs[1])
|
|
712
|
+
if (key == null) jzifyError('non-constant computed static class fields are not supported')
|
|
713
|
+
statics.push([key, it[2]])
|
|
714
|
+
continue
|
|
715
|
+
}
|
|
716
|
+
const key = constStringKey(lhs)
|
|
717
|
+
if (key == null) jzifyError('non-constant computed/destructured class fields are not supported')
|
|
718
|
+
fields.push([key, it[2]])
|
|
719
|
+
continue
|
|
720
|
+
}
|
|
721
|
+
if (it[0] === 'static') {
|
|
722
|
+
const key = constStringKey(it[1])
|
|
723
|
+
if (key != null) {
|
|
724
|
+
statics.push([key, null])
|
|
725
|
+
continue
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
if (it[0] === 'static' && typeof it[1] === 'string') {
|
|
729
|
+
statics.push([it[1], null])
|
|
730
|
+
continue
|
|
731
|
+
}
|
|
732
|
+
if (it[0] === 'static' && Array.isArray(it[1]) && it[1][0] === ':' && Array.isArray(it[1][2]) && it[1][2][0] === '=>') {
|
|
733
|
+
const key = constStringKey(it[1][1])
|
|
734
|
+
if (key == null) jzifyError('non-constant computed static class member names are not supported')
|
|
735
|
+
statics.push([key, it[1][2], true])
|
|
436
736
|
continue
|
|
437
737
|
}
|
|
438
738
|
if (it[0] === 'get' || it[0] === 'set') jzifyError('class getters/setters are not supported — jz objects have no accessors')
|
|
439
739
|
if (it[0] === 'static') jzifyError('`static` class members are not supported yet')
|
|
440
740
|
jzifyError(`unsupported class member ${JSON.stringify(it).slice(0, 60)}`)
|
|
441
741
|
}
|
|
742
|
+
const superMethods = heritage == null ? new Set() : new Set([
|
|
743
|
+
...collectSuperMethodCalls(ctorBody),
|
|
744
|
+
...fields.flatMap(([, init]) => init == null ? [] : [...collectSuperMethodCalls(init)]),
|
|
745
|
+
...methods.flatMap(([, , mbody]) => [...collectSuperMethodCalls(mbody)])
|
|
746
|
+
])
|
|
747
|
+
if (heritage != null) {
|
|
748
|
+
const dummySuperVars = new Map([...superMethods].map((k, i) => [k, `super_${i}`]))
|
|
749
|
+
const unsupportedSuperProp = node => node != null && hasSuperProp(rewriteSuperMethodCalls(node, dummySuperVars))
|
|
750
|
+
if (
|
|
751
|
+
unsupportedSuperProp(ctorBody) ||
|
|
752
|
+
fields.some(([, init]) => unsupportedSuperProp(init)) ||
|
|
753
|
+
methods.some(([, , mbody]) => unsupportedSuperProp(mbody))
|
|
754
|
+
)
|
|
755
|
+
jzifyError('`super` property access is not supported yet')
|
|
756
|
+
}
|
|
442
757
|
const self = `self${classIdx++}`
|
|
443
758
|
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
759
|
// Object literal: every declared field (its initializer inline when it doesn't
|
|
450
760
|
// touch `this`, else `undefined` and assigned below), every method as its
|
|
451
761
|
// self-capturing arrow. Declaring all fields up front fixes the object shape.
|
|
@@ -457,10 +767,39 @@ function lowerClass(name, heritage, body) {
|
|
|
457
767
|
for (const [mname, mparams, mbody] of methods)
|
|
458
768
|
litProps.push([':', mname, transform(['=>', mparams ?? ['()', null], block(renameThis(mbody, self))])])
|
|
459
769
|
const lit = ['{}', litProps.length === 0 ? null : litProps.length === 1 ? litProps[0] : [',', ...litProps]]
|
|
460
|
-
|
|
770
|
+
let params = ctorParams ?? ['()', null]
|
|
771
|
+
const dynamicBase = heritage != null && typeof heritage !== 'string'
|
|
772
|
+
const baseRef = heritage == null ? null : dynamicBase ? `base${classBaseIdx++}` : heritage
|
|
773
|
+
const stmts = []
|
|
774
|
+
if (heritage != null) {
|
|
775
|
+
const split = splitCtorSuper(ctorBody)
|
|
776
|
+
ctorBody = split.body
|
|
777
|
+
const defaultArgs = ctorParams == null
|
|
778
|
+
? Array.from({ length: DEFAULT_DERIVED_CTOR_ARITY }, (_, i) => `superArg${classIdx}_${i}`)
|
|
779
|
+
: null
|
|
780
|
+
const baseArgs = split.args ?? (defaultArgs ? [defaultArgs.length === 1 ? defaultArgs[0] : [',', ...defaultArgs]] : paramList(ctorParams))
|
|
781
|
+
stmts.push(['let', ['=', self, ['()', baseRef, ...baseArgs.map(transform)]]])
|
|
782
|
+
const superMethodVars = new Map()
|
|
783
|
+
let superIdx = 0
|
|
784
|
+
for (const mname of superMethods) {
|
|
785
|
+
const v = `super${classIdx}_${superIdx++}`
|
|
786
|
+
superMethodVars.set(mname, v)
|
|
787
|
+
stmts.push(['let', ['=', v, ['.', self, mname]]])
|
|
788
|
+
}
|
|
789
|
+
for (const [fname, init] of fields)
|
|
790
|
+
stmts.push(['=', ['.', self, fname], init != null ? transform(renameThis(rewriteSuperMethodCalls(init, superMethodVars), self)) : UNDEF])
|
|
791
|
+
for (const [mname, mparams, mbody] of methods)
|
|
792
|
+
stmts.push(['=', ['.', self, mname], transform(['=>', mparams ?? ['()', null], block(renameThis(rewriteSuperMethodCalls(mbody, superMethodVars), self))])])
|
|
793
|
+
ctorBody = rewriteSuperMethodCalls(ctorBody, superMethodVars)
|
|
794
|
+
if (defaultArgs) params = ['()', defaultArgs.length === 1 ? defaultArgs[0] : [',', ...defaultArgs]]
|
|
795
|
+
} else {
|
|
796
|
+
stmts.push(['let', ['=', self, lit]])
|
|
797
|
+
}
|
|
461
798
|
// `this`-dependent field initializers run, in declaration order, before the ctor.
|
|
462
|
-
|
|
463
|
-
|
|
799
|
+
if (heritage == null) {
|
|
800
|
+
for (const [fname, init] of deferred)
|
|
801
|
+
stmts.push(['=', ['.', self, fname], transform(renameThis(init, self))])
|
|
802
|
+
}
|
|
464
803
|
if (ctorBody != null) {
|
|
465
804
|
let cb = transform(renameThis(ctorBody, self))
|
|
466
805
|
if (Array.isArray(cb) && cb[0] === '{}') cb = cb[1]
|
|
@@ -468,15 +807,43 @@ function lowerClass(name, heritage, body) {
|
|
|
468
807
|
else if (cb != null) stmts.push(cb)
|
|
469
808
|
}
|
|
470
809
|
stmts.push(['return', self])
|
|
471
|
-
|
|
810
|
+
const factory = ['=>', arrowParams(params), ['{}', [';', ...stmts]]]
|
|
811
|
+
if (!dynamicBase && statics.length === 0) return factory
|
|
812
|
+
|
|
813
|
+
const cls = name || `class${staticClassIdx++}`
|
|
814
|
+
const staticStmts = []
|
|
815
|
+
if (dynamicBase) staticStmts.push(['let', ['=', baseRef, transform(heritage)]])
|
|
816
|
+
staticStmts.push(['let', ['=', cls, factory]])
|
|
817
|
+
for (const [sname, value, isMethod] of statics) {
|
|
818
|
+
const rhs = isMethod
|
|
819
|
+
? transform(['=>', value[1], block(renameThis(value[2], cls))])
|
|
820
|
+
: value == null ? UNDEF : transform(renameThis(value, cls))
|
|
821
|
+
staticStmts.push(['=', ['.', cls, sname], rhs])
|
|
822
|
+
}
|
|
823
|
+
staticStmts.push(['return', cls])
|
|
824
|
+
return ['()', ['()', ['=>', null, ['{}', [';', ...staticStmts]]]], null]
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// Array(a, b, …) / new Array(a, b, …) → array literal [a, b, …]; Array() → [].
|
|
828
|
+
// The single-argument Array(n) is a length constructor (n holes), not a
|
|
829
|
+
// literal — return null there so the caller keeps it as a constructor call.
|
|
830
|
+
function lowerArrayConstructor(arg) {
|
|
831
|
+
if (arg == null) return ['[]', null]
|
|
832
|
+
if (Array.isArray(arg) && arg[0] === ',' && arg.length > 2)
|
|
833
|
+
return ['[]', [',', ...arg.slice(1).map(transform)]]
|
|
834
|
+
return null
|
|
472
835
|
}
|
|
473
836
|
|
|
474
837
|
const handlers = {
|
|
475
838
|
// Named IIFE: (function name(p){b})(a) → let name = arrow; name(a)
|
|
476
839
|
'()'(callee, ...rest) {
|
|
840
|
+
if (callee === 'Array') {
|
|
841
|
+
const lit = lowerArrayConstructor(rest[0])
|
|
842
|
+
if (lit) return lit
|
|
843
|
+
}
|
|
477
844
|
if (Array.isArray(callee) && callee[0] === '()' && Array.isArray(callee[1]) && callee[1][0] === 'function' && callee[1][1]) {
|
|
478
845
|
const [, name, params, body] = callee[1]
|
|
479
|
-
const [p2, b2] = lowerArguments(params, body)
|
|
846
|
+
const [p2, b2] = lowerArguments(params, functionBodyBlock(body))
|
|
480
847
|
return [';', ['let', ['=', name, ['=>', arrowParams(p2), wrapArrowBody(b2)]]], ['()', name, ...rest.map(transform)]]
|
|
481
848
|
}
|
|
482
849
|
},
|
|
@@ -485,7 +852,7 @@ const handlers = {
|
|
|
485
852
|
// bound inside body per ES spec: `function f(){...f...}` → `(()=>{let f;f=arrow;return f})()`.
|
|
486
853
|
// Statement-form named functions are hoisted by transformScope before reaching here.
|
|
487
854
|
'function'(name, params, body) {
|
|
488
|
-
const [p2, b2] = lowerArguments(params, body)
|
|
855
|
+
const [p2, b2] = lowerArguments(params, functionBodyBlock(body))
|
|
489
856
|
const arrow = ['=>', p2, wrapArrowBody(b2)]
|
|
490
857
|
if (name) {
|
|
491
858
|
return ['()', ['()', ['=>', null, ['{}', [';',
|
|
@@ -498,7 +865,21 @@ const handlers = {
|
|
|
498
865
|
},
|
|
499
866
|
|
|
500
867
|
'=>'(params, body) {
|
|
501
|
-
|
|
868
|
+
// The subscript parser elides the `[';', stmt, null]` wrapper inside an
|
|
869
|
+
// arrow's `{` block when the block holds a single statement — the result
|
|
870
|
+
// `['{}', stmt]` is syntactically identical to an object-literal shape, and
|
|
871
|
+
// downstream `{}` handlers can no longer tell them apart. JS grammar makes
|
|
872
|
+
// it unambiguous: `=>` followed by `{` is always a block (use `=> ({...})`
|
|
873
|
+
// for an object return), so coerce every single-statement body to the
|
|
874
|
+
// canonical `['{}', [';', stmt]]` block form — `;`-wrapped, never bare.
|
|
875
|
+
let b = body
|
|
876
|
+
if (Array.isArray(b) && b[0] === '{}' && b.length === 2) {
|
|
877
|
+
const inner = b[1]
|
|
878
|
+
if (inner != null && !(Array.isArray(inner) && inner[0] === ';')) {
|
|
879
|
+
b = ['{}', [';', inner]]
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
const [p2, b2] = lowerArguments(params, b)
|
|
502
883
|
return ['=>', p2, transform(b2)]
|
|
503
884
|
},
|
|
504
885
|
|
|
@@ -514,17 +895,13 @@ const handlers = {
|
|
|
514
895
|
return ['let', ...args.map(transform)]
|
|
515
896
|
},
|
|
516
897
|
|
|
898
|
+
':'(label, body) {
|
|
899
|
+
if (typeof label === 'string' && Array.isArray(body) && LABEL_BODY_OPS.has(body[0]))
|
|
900
|
+
return ['label', label, transform(body)]
|
|
901
|
+
},
|
|
902
|
+
|
|
517
903
|
'='(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
|
-
}
|
|
904
|
+
if (isDestructurePat(lhs)) return ['=', transformPattern(lhs), transform(rhs)]
|
|
528
905
|
},
|
|
529
906
|
|
|
530
907
|
'switch'(disc, ...cases) {
|
|
@@ -546,32 +923,49 @@ const handlers = {
|
|
|
546
923
|
return transformSwitch(disc, clean)
|
|
547
924
|
},
|
|
548
925
|
|
|
549
|
-
//
|
|
550
|
-
|
|
551
|
-
|
|
926
|
+
// Equality keeps the JS loose/strict distinction (jz core now lowers both):
|
|
927
|
+
// `==`/`!=` stay loose, `===`/`!==` stay strict. A comparison against a prototype
|
|
928
|
+
// object (e.g. `x.constructor === Object`) folds to a boolean — jz has no
|
|
929
|
+
// prototype objects, so identity against one is decided statically.
|
|
930
|
+
'=='(a, b) { return isProto(a) || isProto(b) ? 1 : ['==', transform(a), transform(b)] },
|
|
931
|
+
'!='(a, b) { return isProto(a) || isProto(b) ? 0 : ['!=', transform(a), transform(b)] },
|
|
552
932
|
'==='(a, b) { if (isProto(a) || isProto(b)) return 1 },
|
|
553
933
|
'!=='(a, b) { if (isProto(a) || isProto(b)) return 0 },
|
|
554
934
|
|
|
555
935
|
// new → call (keep TypedArrays)
|
|
556
936
|
'new'(ctor, ...cargs) {
|
|
557
|
-
if (Array.isArray(ctor) && ctor[0] === '()' &&
|
|
558
|
-
|
|
937
|
+
if (Array.isArray(ctor) && ctor[0] === '()' && ctor[1] === 'Array') {
|
|
938
|
+
const lit = lowerArrayConstructor(ctor[2])
|
|
939
|
+
if (lit) return lit
|
|
559
940
|
}
|
|
560
941
|
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)]
|
|
942
|
+
if (typeof name === 'string' && (TYPED_ARRAYS.has(name) || name === 'Array' || name === 'RegExp')) return ['new', transform(ctor), ...cargs.map(transform)]
|
|
562
943
|
if (Array.isArray(ctor) && ctor[0] === '()') return transform(ctor)
|
|
563
944
|
// `new C(a)` → `C(a)`; `new C` (no parens) → `C()` — a 2-element `['()', X]`
|
|
564
945
|
// is grouping parens, so a no-arg call needs the explicit `null` arg slot.
|
|
565
946
|
return ['()', transform(ctor), ...(cargs.length ? cargs.map(transform) : [null])]
|
|
566
947
|
},
|
|
567
948
|
|
|
568
|
-
// instanceof → typeof / Array.isArray
|
|
949
|
+
// instanceof → typeof / Array.isArray / __is_* helpers. jzify lets us preserve
|
|
950
|
+
// constructor identity that strict-mode `instanceof` discards — Map, Set, and
|
|
951
|
+
// TypedArrays get dedicated typed predicates (__is_map / __is_set / __is_typed)
|
|
952
|
+
// that both compile to a runtime __ptr_type check and feed extractRefinements
|
|
953
|
+
// for downstream method-dispatch elision (e.g. `if (x instanceof Map) x.has(k)`
|
|
954
|
+
// resolves to __map_has, not the default __set_has fallback). Date / RegExp /
|
|
955
|
+
// Object stay on the weak typeof-object lowering — they share PTR.OBJECT and
|
|
956
|
+
// the JS runtime offers no cheaper discrimination.
|
|
569
957
|
'instanceof'(val, ctor) {
|
|
570
958
|
const t = transform(val)
|
|
571
959
|
const name = typeof ctor === 'string' ? ctor : (Array.isArray(ctor) && ctor[0] === '()' ? ctor[1] : null)
|
|
960
|
+
// Static fold: literal shape of LHS already discriminates against the constructor.
|
|
961
|
+
const fold = staticInstanceofFold(val, name)
|
|
962
|
+
if (fold != null) return [null, fold]
|
|
572
963
|
if (name === 'Array') return ['()', ['.', 'Array', 'isArray'], t]
|
|
573
|
-
if (name === '
|
|
574
|
-
if (
|
|
964
|
+
if (name === 'Map') return ['()', '__is_map', t]
|
|
965
|
+
if (name === 'Set') return ['()', '__is_set', t]
|
|
966
|
+
if (typeof name === 'string' && TYPED_ARRAYS.has(name) && name !== 'ArrayBuffer' && name !== 'DataView')
|
|
967
|
+
return ['()', '__is_typed', t]
|
|
968
|
+
// Object / ArrayBuffer / DataView / RegExp / Date / unknown: weak typeof-object check.
|
|
575
969
|
return ['===', ['typeof', t], [null, 'object']]
|
|
576
970
|
},
|
|
577
971
|
|
|
@@ -585,8 +979,27 @@ const handlers = {
|
|
|
585
979
|
['while', ['||', flag, transform(cond)], ['{}', [';', ['=', flag, [null, false]], transform(body)]]]]
|
|
586
980
|
},
|
|
587
981
|
|
|
588
|
-
// Block body: recurse as scope for hoisting
|
|
589
|
-
|
|
982
|
+
// Block body: recurse as scope for hoisting. transformScope reduces a single-statement
|
|
983
|
+
// sequence to its bare element; if the input WAS block-shaped (`;` list or a single
|
|
984
|
+
// statement op), we re-wrap the collapsed result in `[';', ...]` so prepare.js keeps
|
|
985
|
+
// routing the `{}` through the block branch instead of mistaking it for `{ expr }`.
|
|
986
|
+
'{}'(...args) {
|
|
987
|
+
const loweredObject = lowerObjectLiteralThis(args)
|
|
988
|
+
if (loweredObject) return loweredObject
|
|
989
|
+
|
|
990
|
+
return ['{}', ...args.map((a, i) => {
|
|
991
|
+
const t = transformScope(a) ?? a
|
|
992
|
+
if (i !== 0 || a == null) return t
|
|
993
|
+
const blockIn = Array.isArray(a) && JZ_BLOCK_OPS.has(a[0])
|
|
994
|
+
if (!blockIn || t == null) return t
|
|
995
|
+
// transformScope collapses a single-statement `;`-list to its bare element;
|
|
996
|
+
// re-wrap so the block always stays `['{}', [';', ...]]`. The `;` is the
|
|
997
|
+
// only reliable block marker — a bare statement op can desugar to an
|
|
998
|
+
// expression op downstream (postfix `c++` → `['-', ...]`) and lose its
|
|
999
|
+
// block identity, leaving `['{}', expr]` mistakable for an object literal.
|
|
1000
|
+
return Array.isArray(t) && t[0] === ';' ? t : [';', t]
|
|
1001
|
+
})]
|
|
1002
|
+
},
|
|
590
1003
|
|
|
591
1004
|
// Export: recurse into exported declaration. Statement-form `export function name`
|
|
592
1005
|
// and `export default function name` must be hoisted as const-arrows — otherwise
|
|
@@ -642,16 +1055,16 @@ function foldStaticExportHelpers(ast) {
|
|
|
642
1055
|
|
|
643
1056
|
const defPropAliases = new Set()
|
|
644
1057
|
for (const stmt of body) {
|
|
645
|
-
|
|
646
|
-
|
|
1058
|
+
const b = bindingOf(stmt)
|
|
1059
|
+
if (b && isObjectDefineProperty(b[1])) defPropAliases.add(b[0])
|
|
647
1060
|
}
|
|
648
1061
|
if (!defPropAliases.size) return ast
|
|
649
1062
|
|
|
650
1063
|
const helperNames = new Set()
|
|
651
1064
|
for (const stmt of body) {
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
helperNames.add(
|
|
1065
|
+
const b = bindingOf(stmt)
|
|
1066
|
+
if (b && Array.isArray(b[1]) && b[1][0] === '=>' && containsDefinePropertyCall(b[1], defPropAliases))
|
|
1067
|
+
helperNames.add(b[0])
|
|
655
1068
|
}
|
|
656
1069
|
if (!helperNames.size) return ast
|
|
657
1070
|
|
|
@@ -671,6 +1084,210 @@ function foldStaticExportHelpers(ast) {
|
|
|
671
1084
|
return rewritten.length === 0 ? null : rewritten.length === 1 ? rewritten[0] : [';', ...rewritten]
|
|
672
1085
|
}
|
|
673
1086
|
|
|
1087
|
+
// Esbuild's CommonJS/ESM interop helpers alias Object reflection built-ins into
|
|
1088
|
+
// locals (`var __create = Object.create`, `var __getOwnPropNames =
|
|
1089
|
+
// Object.getOwnPropertyNames`, ...). jz deliberately does not expose those
|
|
1090
|
+
// built-ins as first-class function values, but the helpers are static enough to
|
|
1091
|
+
// lower back to the supported direct calls and module reads.
|
|
1092
|
+
function foldStaticBundlerHelpers(ast) {
|
|
1093
|
+
const body = astSeq(ast)
|
|
1094
|
+
if (!body) return ast
|
|
1095
|
+
const binds = body.map(bindingOf) // [name, init] | null, index-aligned with body
|
|
1096
|
+
|
|
1097
|
+
// Local aliases of Object reflection built-ins: name -> canonical built-in.
|
|
1098
|
+
// esbuild's interop preamble always emits these (`var __defProp =
|
|
1099
|
+
// Object.defineProperty`, ...); their absence proves the input is not a
|
|
1100
|
+
// bundle, so the fold stays a strict no-op rather than guessing.
|
|
1101
|
+
const aliases = new Map()
|
|
1102
|
+
for (const b of binds) {
|
|
1103
|
+
const key = b && objectBuiltinKey(b[1])
|
|
1104
|
+
if (key) aliases.set(b[0], key)
|
|
1105
|
+
}
|
|
1106
|
+
if (!aliases.size) return ast
|
|
1107
|
+
|
|
1108
|
+
// __copyProps: an arrow driving both aliased getOwnPropertyNames + defineProperty.
|
|
1109
|
+
const copyHelpers = new Set()
|
|
1110
|
+
for (const b of binds)
|
|
1111
|
+
if (b && isArrow(b[1]) &&
|
|
1112
|
+
containsCall(b[1], c => aliases.get(c) === 'Object.getOwnPropertyNames') &&
|
|
1113
|
+
containsCall(b[1], c => aliases.get(c) === 'Object.defineProperty'))
|
|
1114
|
+
copyHelpers.add(b[0])
|
|
1115
|
+
|
|
1116
|
+
// __toESM: an arrow cloning a module behind a prototype, tagging default/__esModule.
|
|
1117
|
+
const interopHelpers = new Set()
|
|
1118
|
+
for (const b of binds)
|
|
1119
|
+
if (b && isArrow(b[1]) &&
|
|
1120
|
+
containsCall(b[1], c => aliases.get(c) === 'Object.create') &&
|
|
1121
|
+
containsCall(b[1], c => aliases.get(c) === 'Object.getPrototypeOf') &&
|
|
1122
|
+
containsCall(b[1], c => copyHelpers.has(c)) &&
|
|
1123
|
+
astSome(b[1], n => n === 'default') && astSome(b[1], n => n === '__esModule'))
|
|
1124
|
+
interopHelpers.add(b[0])
|
|
1125
|
+
|
|
1126
|
+
// Bindings produced by an interop-helper call: name -> wrapped module expression.
|
|
1127
|
+
const interopBindings = new Map()
|
|
1128
|
+
for (const b of binds)
|
|
1129
|
+
if (b && Array.isArray(b[1]) && b[1][0] === '()' && interopHelpers.has(b[1][1])) {
|
|
1130
|
+
const args = callArgs(b[1].slice(2))
|
|
1131
|
+
if (args.length) interopBindings.set(b[0], args[0])
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
let out = body.map(stmt => rewriteBundlerAliases(stmt, aliases, interopBindings))
|
|
1135
|
+
if (interopBindings.size) out = out.map(stmt => replaceInteropReads(stmt, interopBindings))
|
|
1136
|
+
out = out.map(stmt => rewriteBundlerAliases(stmt, aliases, interopBindings)).filter(s => s != null)
|
|
1137
|
+
|
|
1138
|
+
// Drop synthetic alias/helper bindings nothing references after rewriting.
|
|
1139
|
+
const synthetic = n => aliases.has(n) || copyHelpers.has(n) || interopHelpers.has(n) || interopBindings.has(n)
|
|
1140
|
+
const live = new Set()
|
|
1141
|
+
for (const stmt of out) {
|
|
1142
|
+
const b = bindingOf(stmt)
|
|
1143
|
+
if (!(b && synthetic(b[0]))) collectRefs(stmt, live)
|
|
1144
|
+
}
|
|
1145
|
+
out = out.filter(stmt => {
|
|
1146
|
+
const b = bindingOf(stmt)
|
|
1147
|
+
return !(b && synthetic(b[0]) && !live.has(b[0]))
|
|
1148
|
+
})
|
|
1149
|
+
|
|
1150
|
+
return out.length === 0 ? null : out.length === 1 ? out[0] : [';', ...out]
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
const isArrow = node => Array.isArray(node) && node[0] === '=>'
|
|
1154
|
+
|
|
1155
|
+
const OBJECT_BUILTINS = new Set(['create', 'getPrototypeOf', 'getOwnPropertyNames', 'getOwnPropertyDescriptor', 'defineProperty'])
|
|
1156
|
+
|
|
1157
|
+
// Canonical name of the Object reflection built-in `node` references, or null.
|
|
1158
|
+
function objectBuiltinKey(node) {
|
|
1159
|
+
if (!Array.isArray(node) || node[0] !== '.') return null
|
|
1160
|
+
if (node[1] === 'Object' && OBJECT_BUILTINS.has(node[2])) return 'Object.' + node[2]
|
|
1161
|
+
return isObjectHasOwnPropertyRef(node) ? 'Object.prototype.hasOwnProperty' : null
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// Deep `some` over AST children (skips the op slot, so op names never match).
|
|
1165
|
+
function astSome(node, pred) {
|
|
1166
|
+
if (pred(node)) return true
|
|
1167
|
+
if (!Array.isArray(node)) return false
|
|
1168
|
+
for (let i = 1; i < node.length; i++) if (astSome(node[i], pred)) return true
|
|
1169
|
+
return false
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// Does `node` contain a `()` call whose string callee satisfies `ok`?
|
|
1173
|
+
const containsCall = (node, ok) =>
|
|
1174
|
+
astSome(node, n => Array.isArray(n) && n[0] === '()' && typeof n[1] === 'string' && ok(n[1]))
|
|
1175
|
+
|
|
1176
|
+
function rewriteBundlerAliases(node, aliases, interopBindings) {
|
|
1177
|
+
if (!Array.isArray(node)) return node
|
|
1178
|
+
const rec = n => rewriteBundlerAliases(n, aliases, interopBindings)
|
|
1179
|
+
|
|
1180
|
+
if (node[0] === ';') {
|
|
1181
|
+
const out = [';']
|
|
1182
|
+
for (let i = 1; i < node.length; i++) {
|
|
1183
|
+
const child = rec(node[i])
|
|
1184
|
+
if (child != null) out.push(child)
|
|
1185
|
+
}
|
|
1186
|
+
return out.length === 1 ? null : out.length === 2 ? out[1] : out
|
|
1187
|
+
}
|
|
1188
|
+
if (node[0] === '{}' && node.length === 2) {
|
|
1189
|
+
const wasBlock = Array.isArray(node[1]) && JZ_BLOCK_OPS.has(node[1][0])
|
|
1190
|
+
const inner = rec(node[1])
|
|
1191
|
+
if (!wasBlock || inner == null) return ['{}', inner]
|
|
1192
|
+
const stayed = Array.isArray(inner) && JZ_BLOCK_OPS.has(inner[0])
|
|
1193
|
+
return ['{}', stayed ? inner : [';', inner]]
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
if (node[0] === '()') {
|
|
1197
|
+
const callee = node[1]
|
|
1198
|
+
const args = callArgs(node.slice(2))
|
|
1199
|
+
|
|
1200
|
+
if (typeof callee === 'string') {
|
|
1201
|
+
const key = aliases.get(callee)
|
|
1202
|
+
if (key === 'Object.defineProperty') {
|
|
1203
|
+
const define = staticDefineProperty(args)
|
|
1204
|
+
if (define !== undefined) return define
|
|
1205
|
+
}
|
|
1206
|
+
if (key === 'Object.getOwnPropertyNames' || key === 'Object.create') {
|
|
1207
|
+
if (key === 'Object.create' && isGetPrototypeOfCall(args[0], aliases)) return ['{}', null]
|
|
1208
|
+
return ['()', key, ...args.map(rec)]
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
// `__hasOwnProp.call(o, k)` -> `o.hasOwnProperty(k)`.
|
|
1212
|
+
if (Array.isArray(callee) && callee[0] === '.' && callee[2] === 'call' && args.length >= 2 &&
|
|
1213
|
+
typeof callee[1] === 'string' && aliases.get(callee[1]) === 'Object.prototype.hasOwnProperty')
|
|
1214
|
+
return ['()', ['.', rec(args[0]), 'hasOwnProperty'], rec(args[1])]
|
|
1215
|
+
// `(0, fn)(...)` comma-call resolving to an interop module read.
|
|
1216
|
+
const seqCall = commaZeroCall(callee, interopBindings)
|
|
1217
|
+
if (seqCall) return ['()', seqCall, ...args.map(rec)]
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
if (node[0] === '.' || node[0] === '?.') return [node[0], rec(node[1]), node[2]]
|
|
1221
|
+
if (node[0] === ':') return [node[0], node[1], rec(node[2])]
|
|
1222
|
+
return node.map((part, i) => i === 0 ? part : rec(part))
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
function replaceInteropReads(node, bindings) {
|
|
1226
|
+
if (typeof node === 'string' && bindings.has(node)) return cloneAst(bindings.get(node))
|
|
1227
|
+
if (!Array.isArray(node)) return node
|
|
1228
|
+
if (node[0] === '=' && typeof node[1] === 'string') return ['=', node[1], replaceInteropReads(node[2], bindings)]
|
|
1229
|
+
if (node[0] === 'let' || node[0] === 'const' || node[0] === 'var')
|
|
1230
|
+
return [node[0], ...node.slice(1).map(decl =>
|
|
1231
|
+
Array.isArray(decl) && decl[0] === '=' ? ['=', decl[1], replaceInteropReads(decl[2], bindings)] : decl)]
|
|
1232
|
+
if ((node[0] === '.' || node[0] === '?.') && typeof node[1] === 'string' && typeof node[2] === 'string' && bindings.has(node[1])) {
|
|
1233
|
+
const mod = cloneAst(bindings.get(node[1]))
|
|
1234
|
+
return node[2] === 'default' ? mod : [node[0], mod, node[2]]
|
|
1235
|
+
}
|
|
1236
|
+
if (node[0] === ':') return [node[0], node[1], replaceInteropReads(node[2], bindings)]
|
|
1237
|
+
return node.map((part, i) => i === 0 ? part : replaceInteropReads(part, bindings))
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
function isGetPrototypeOfCall(node, aliases) {
|
|
1241
|
+
if (!Array.isArray(node) || node[0] !== '()') return false
|
|
1242
|
+
const callee = node[1]
|
|
1243
|
+
return (typeof callee === 'string' && aliases.get(callee) === 'Object.getPrototypeOf') ||
|
|
1244
|
+
objectBuiltinKey(callee) === 'Object.getPrototypeOf'
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
function commaZeroCall(callee, bindings) {
|
|
1248
|
+
if (!Array.isArray(callee) || callee[0] !== '()' || !Array.isArray(callee[1]) || callee[1][0] !== ',') return null
|
|
1249
|
+
const parts = callee[1].slice(1)
|
|
1250
|
+
if (parts.length !== 2 || !isZeroLiteral(parts[0])) return null
|
|
1251
|
+
const fn = replaceInteropReads(parts[1], bindings)
|
|
1252
|
+
return fn === parts[1] ? null : fn
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
// `defProp(obj, "key", descriptor)` -> `obj.key = value`; null drops `__esModule`.
|
|
1256
|
+
function staticDefineProperty(args) {
|
|
1257
|
+
if (args.length < 3) return undefined
|
|
1258
|
+
const [obj, keyExpr, desc] = args
|
|
1259
|
+
const key = stringLiteral(keyExpr)
|
|
1260
|
+
const props = objectProps(desc)
|
|
1261
|
+
if (typeof key !== 'string' || !props) return undefined
|
|
1262
|
+
if (key === '__esModule') return null
|
|
1263
|
+
const prop = name => props.find(p => Array.isArray(p) && p[0] === ':' && p[1] === name)?.[2]
|
|
1264
|
+
const value = prop('value')
|
|
1265
|
+
if (value !== undefined) return ['=', ['.', obj, key], value]
|
|
1266
|
+
const got = getterReturnExpr(prop('get'))
|
|
1267
|
+
return got !== null ? ['=', ['.', obj, key], got] : undefined
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
function stringLiteral(node) {
|
|
1271
|
+
return Array.isArray(node) && node[0] == null && typeof node[1] === 'string' ? node[1] : null
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
// Identifier references in `node`, excluding declaration names, member property
|
|
1275
|
+
// names and object keys — enough to decide whether a synthetic binding is live.
|
|
1276
|
+
function collectRefs(node, out) {
|
|
1277
|
+
if (typeof node === 'string') return void out.add(node)
|
|
1278
|
+
if (!Array.isArray(node)) return
|
|
1279
|
+
if (node[0] === 'let' || node[0] === 'const' || node[0] === 'var') {
|
|
1280
|
+
for (let i = 1; i < node.length; i++)
|
|
1281
|
+
if (Array.isArray(node[i]) && node[i][0] === '=') collectRefs(node[i][2], out)
|
|
1282
|
+
} else if ((node[0] === '.' || node[0] === '?.') && typeof node[2] === 'string') {
|
|
1283
|
+
collectRefs(node[1], out)
|
|
1284
|
+
} else if (node[0] === ':') {
|
|
1285
|
+
collectRefs(node[2], out)
|
|
1286
|
+
} else {
|
|
1287
|
+
for (let i = 1; i < node.length; i++) collectRefs(node[i], out)
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
|
|
674
1291
|
function astSeq(ast) {
|
|
675
1292
|
if (!Array.isArray(ast)) return null
|
|
676
1293
|
return ast[0] === ';' ? ast.slice(1).filter(Boolean) : [ast]
|
|
@@ -680,12 +1297,28 @@ function isObjectDefineProperty(node) {
|
|
|
680
1297
|
return Array.isArray(node) && node[0] === '.' && node[1] === 'Object' && node[2] === 'defineProperty'
|
|
681
1298
|
}
|
|
682
1299
|
|
|
1300
|
+
/** Unwrap an esbuild module binding to `[name, init]`. After hoistVars, a binding
|
|
1301
|
+
* reaches this pass either split into a bare `['=', name, init]` (RHS hoisted out
|
|
1302
|
+
* as a separate `let name;`) or kept as a single `['let', ['=', name, init]]` decl
|
|
1303
|
+
* (arrow RHS — see the `;` handler in hoistVars). The fold keys on name/init,
|
|
1304
|
+
* so it must see through both shapes. */
|
|
1305
|
+
function bindingOf(stmt) {
|
|
1306
|
+
if (!Array.isArray(stmt)) return null
|
|
1307
|
+
if (stmt[0] === '=' && typeof stmt[1] === 'string') return [stmt[1], stmt[2]]
|
|
1308
|
+
if ((stmt[0] === 'let' || stmt[0] === 'const' || stmt[0] === 'var') && stmt.length === 2 &&
|
|
1309
|
+
Array.isArray(stmt[1]) && stmt[1][0] === '=' && typeof stmt[1][1] === 'string')
|
|
1310
|
+
return [stmt[1][1], stmt[1][2]]
|
|
1311
|
+
return null
|
|
1312
|
+
}
|
|
1313
|
+
|
|
683
1314
|
function isDefPropAliasAssign(stmt, aliases) {
|
|
684
|
-
|
|
1315
|
+
const b = bindingOf(stmt)
|
|
1316
|
+
return b != null && aliases.has(b[0]) && isObjectDefineProperty(b[1])
|
|
685
1317
|
}
|
|
686
1318
|
|
|
687
1319
|
function isExportHelperAssign(stmt, helpers) {
|
|
688
|
-
|
|
1320
|
+
const b = bindingOf(stmt)
|
|
1321
|
+
return b != null && helpers.has(b[0])
|
|
689
1322
|
}
|
|
690
1323
|
|
|
691
1324
|
function containsDefinePropertyCall(node, aliases) {
|
|
@@ -730,6 +1363,8 @@ function getterReturnExpr(node) {
|
|
|
730
1363
|
if (params.length !== 0) return null
|
|
731
1364
|
const body = node[2]
|
|
732
1365
|
if (Array.isArray(body) && body[0] === '{}' && Array.isArray(body[1]) && body[1][0] === 'return') return body[1][1]
|
|
1366
|
+
if (Array.isArray(body) && body[0] === '{}' && Array.isArray(body[1]) && body[1][0] === ';' &&
|
|
1367
|
+
Array.isArray(body[1][1]) && body[1][1][0] === 'return') return body[1][1][1]
|
|
733
1368
|
if (Array.isArray(body) && body[0] === 'return') return body[1]
|
|
734
1369
|
return body
|
|
735
1370
|
}
|
|
@@ -752,6 +1387,9 @@ function canonicalizeObjectIdioms(node) {
|
|
|
752
1387
|
const hasOwnCall = objectHasOwnPropertyCall(out)
|
|
753
1388
|
if (hasOwnCall) return ['()', ['.', hasOwnCall.obj, 'hasOwnProperty'], hasOwnCall.key]
|
|
754
1389
|
|
|
1390
|
+
const mapString = arrayMapStringCallback(out)
|
|
1391
|
+
if (mapString) return mapString
|
|
1392
|
+
|
|
755
1393
|
if (out[0] === '&&') {
|
|
756
1394
|
const leftCtor = constructorIsObject(out[1])
|
|
757
1395
|
const rightKeys = objectKeysLengthZero(out[2])
|
|
@@ -765,16 +1403,31 @@ function canonicalizeObjectIdioms(node) {
|
|
|
765
1403
|
return out
|
|
766
1404
|
}
|
|
767
1405
|
|
|
1406
|
+
function arrayMapStringCallback(node) {
|
|
1407
|
+
if (!Array.isArray(node) || node[0] !== '()') return null
|
|
1408
|
+
const callee = node[1]
|
|
1409
|
+
if (!Array.isArray(callee) || callee[0] !== '.' || callee[2] !== 'map') return null
|
|
1410
|
+
const args = callArgs(node.slice(2))
|
|
1411
|
+
if (args.length !== 1 || args[0] !== 'String') return null
|
|
1412
|
+
return ['()', callee, ['=>', 'value', ['()', 'String', 'value']]]
|
|
1413
|
+
}
|
|
1414
|
+
|
|
768
1415
|
function objectHasOwnPropertyCall(node) {
|
|
769
1416
|
if (!Array.isArray(node) || node[0] !== '()') return null
|
|
770
1417
|
const callee = node[1]
|
|
771
1418
|
if (!Array.isArray(callee) || callee[0] !== '.' || callee[2] !== 'call') return null
|
|
772
|
-
if (!
|
|
1419
|
+
if (!isObjectHasOwnPropertyRef(callee[1])) return null
|
|
773
1420
|
const args = callArgs(node.slice(2))
|
|
774
1421
|
if (args.length < 2) return null
|
|
775
1422
|
return { obj: args[0], key: args[1] }
|
|
776
1423
|
}
|
|
777
1424
|
|
|
1425
|
+
function isObjectHasOwnPropertyRef(node) {
|
|
1426
|
+
if (!Array.isArray(node) || node[0] !== '.' || node[2] !== 'hasOwnProperty') return false
|
|
1427
|
+
if (node[1] === 'Object') return true
|
|
1428
|
+
return Array.isArray(node[1]) && node[1][0] === '.' && node[1][1] === 'Object' && node[1][2] === 'prototype'
|
|
1429
|
+
}
|
|
1430
|
+
|
|
778
1431
|
function constructorIsObject(node) {
|
|
779
1432
|
if (!Array.isArray(node) || (node[0] !== '===' && node[0] !== '==')) return null
|
|
780
1433
|
const left = constructorReceiver(node[1])
|
|
@@ -836,24 +1489,63 @@ function stripTerminalSwitchBreak(body) {
|
|
|
836
1489
|
return stmts.length === 0 ? null : stmts.length === 1 ? stmts[0] : [';', ...stmts]
|
|
837
1490
|
}
|
|
838
1491
|
|
|
1492
|
+
const SWITCH_BREAK_BOUNDARIES = new Set(['for', 'for-in', 'for-of', 'while', 'do', 'switch', '=>', 'function', 'class'])
|
|
1493
|
+
|
|
1494
|
+
function hasOwnSwitchBreak(node) {
|
|
1495
|
+
if (!Array.isArray(node)) return false
|
|
1496
|
+
if (node[0] === 'break') return true
|
|
1497
|
+
if (SWITCH_BREAK_BOUNDARIES.has(node[0])) return false
|
|
1498
|
+
for (let i = 1; i < node.length; i++) if (hasOwnSwitchBreak(node[i])) return true
|
|
1499
|
+
return false
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
function rewriteSwitchBreaks(node, flag) {
|
|
1503
|
+
if (!Array.isArray(node)) return node
|
|
1504
|
+
const op = node[0]
|
|
1505
|
+
if (op === 'break') return ['=', flag, [null, true]]
|
|
1506
|
+
if (SWITCH_BREAK_BOUNDARIES.has(op)) return node
|
|
1507
|
+
|
|
1508
|
+
if (op === ';') {
|
|
1509
|
+
const out = []
|
|
1510
|
+
const stmts = node.slice(1)
|
|
1511
|
+
for (let i = 0; i < stmts.length; i++) {
|
|
1512
|
+
const stmt = stmts[i]
|
|
1513
|
+
out.push(rewriteSwitchBreaks(stmt, flag))
|
|
1514
|
+
if (hasOwnSwitchBreak(stmt) && i < stmts.length - 1) {
|
|
1515
|
+
const tail = rewriteSwitchBreaks([';', ...stmts.slice(i + 1)], flag)
|
|
1516
|
+
out.push(['if', ['!', flag], tail])
|
|
1517
|
+
break
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
return out.length === 0 ? null : out.length === 1 ? out[0] : [';', ...out]
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
return node.map((part, i) => i === 0 ? part : rewriteSwitchBreaks(part, flag))
|
|
1524
|
+
}
|
|
1525
|
+
|
|
839
1526
|
/** Transform switch statement to if/else chain. */
|
|
840
1527
|
let swIdx = 0
|
|
841
1528
|
function transformSwitch(discriminant, cases) {
|
|
842
1529
|
const disc = transform(discriminant)
|
|
843
1530
|
const tmp = `\uE000sw${swIdx++}`
|
|
1531
|
+
const needsBreakFlag = cases.some(c => hasOwnSwitchBreak(c[0] === 'case' ? c[2] : c[1]))
|
|
1532
|
+
const brk = needsBreakFlag ? `\uE000swbrk${swIdx++}` : null
|
|
844
1533
|
|
|
845
1534
|
// Collect case/default
|
|
846
1535
|
const stmts = [['let', ['=', tmp, disc]]]
|
|
1536
|
+
if (brk) stmts.push(['let', ['=', brk, [null, false]]])
|
|
847
1537
|
let chain = null
|
|
848
1538
|
|
|
849
1539
|
for (let i = cases.length - 1; i >= 0; i--) {
|
|
850
1540
|
const c = cases[i]
|
|
851
1541
|
if (c[0] === 'default') {
|
|
852
|
-
|
|
1542
|
+
const body = transform(c[1])
|
|
1543
|
+
chain = brk ? rewriteSwitchBreaks(body, brk) : body
|
|
853
1544
|
} else if (c[0] === 'case') {
|
|
854
1545
|
const cond = ['===', tmp, transform(c[1])]
|
|
855
1546
|
const body = transform(c[2])
|
|
856
|
-
|
|
1547
|
+
const lowered = brk ? rewriteSwitchBreaks(body, brk) : body
|
|
1548
|
+
chain = chain != null ? ['if', cond, lowered, chain] : ['if', cond, lowered]
|
|
857
1549
|
}
|
|
858
1550
|
}
|
|
859
1551
|
if (chain) stmts.push(chain)
|