jz 0.3.1 → 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 +277 -258
- package/cli.js +10 -52
- package/index.js +181 -29
- package/{src/host.js → interop.js} +291 -88
- package/module/array.js +195 -76
- package/module/collection.js +249 -20
- package/module/console.js +1 -1
- package/module/core.js +267 -119
- package/module/date.js +9 -5
- package/module/function.js +11 -1
- package/module/json.js +367 -61
- package/module/math.js +568 -128
- package/module/number.js +390 -227
- package/module/object.js +352 -39
- package/module/regex.js +123 -16
- package/module/schema.js +15 -1
- package/module/string.js +555 -96
- package/module/timer.js +1 -1
- package/module/typedarray.js +674 -57
- package/package.json +12 -6
- 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 +1872 -487
- package/src/assemble.js +58 -20
- package/src/autoload.js +13 -1
- package/src/codegen.js +177 -0
- package/src/compile.js +462 -186
- package/src/ctx.js +85 -18
- package/src/emit.js +668 -197
- package/src/infer.js +548 -0
- package/src/ir.js +302 -27
- package/src/jzify.js +898 -259
- package/src/narrow.js +455 -246
- package/src/optimize.js +263 -99
- package/src/plan.js +713 -35
- package/src/prepare.js +818 -293
- 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/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(transformScope(ast))
|
|
37
|
+
return foldStaticBundlerHelpers(foldStaticExportHelpers(canonicalizeObjectIdioms(transformScope(ast))))
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
/**
|
|
@@ -71,16 +74,27 @@ 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
|
-
|
|
94
|
+
const normalizedHead = normalizeForDeclHead(head, names) || normalizeForCommaHead(head, names)
|
|
95
|
+
if (normalizedHead) {
|
|
96
|
+
h2 = normalizedHead
|
|
97
|
+
} else if (Array.isArray(head) && head[0] === 'var' && Array.isArray(head[1]) &&
|
|
84
98
|
(head[1][0] === 'in' || head[1][0] === 'of') && typeof head[1][1] === 'string') {
|
|
85
99
|
names.add(head[1][1])
|
|
86
100
|
h2 = [head[1][0], head[1][1], hoistVars(head[1][2], names)]
|
|
@@ -106,25 +120,84 @@ function hoistVars(node, names) {
|
|
|
106
120
|
if (decls.length === 1) return decls[0]
|
|
107
121
|
return [',', ...decls]
|
|
108
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
|
+
}
|
|
109
135
|
// Filter null returns from `;` sequences (bare-var no-ops). `{}` is left
|
|
110
136
|
// to recurse normally — it may be either a block or an object literal,
|
|
111
137
|
// and we don't want to clobber `['{}', null]` (empty object literal).
|
|
112
138
|
if (op === ';') {
|
|
113
139
|
const out = [op]
|
|
114
140
|
for (let i = 1; i < node.length; i++) {
|
|
115
|
-
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)
|
|
116
157
|
if (c != null) out.push(c)
|
|
117
158
|
}
|
|
118
159
|
if (out.length === 1) return null
|
|
119
160
|
if (out.length === 2) return out[1]
|
|
120
161
|
return out
|
|
121
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
|
+
}
|
|
122
175
|
const out = new Array(node.length)
|
|
123
176
|
out[0] = op
|
|
124
177
|
for (let i = 1; i < node.length; i++) out[i] = hoistVars(node[i], names)
|
|
125
178
|
return out
|
|
126
179
|
}
|
|
127
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
|
+
|
|
128
201
|
function prependDecls(body, names) {
|
|
129
202
|
const decl = ['let', ...names]
|
|
130
203
|
if (Array.isArray(body) && body[0] === ';') return [';', decl, ...body.slice(1)]
|
|
@@ -137,9 +210,51 @@ function prependDecls(body, names) {
|
|
|
137
210
|
return body == null ? decl : [';', decl, body]
|
|
138
211
|
}
|
|
139
212
|
|
|
213
|
+
function normalizeForDeclHead(head, names) {
|
|
214
|
+
if (!Array.isArray(head) || (head[0] !== 'var' && head[0] !== 'let' && head[0] !== 'const')) return null
|
|
215
|
+
const kind = head[0]
|
|
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)]
|
|
234
|
+
}
|
|
235
|
+
return null
|
|
236
|
+
}
|
|
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
|
+
|
|
247
|
+
function normalizeForDecl(kind, name, names) {
|
|
248
|
+
if (kind === 'var') {
|
|
249
|
+
names.add(name)
|
|
250
|
+
return name
|
|
251
|
+
}
|
|
252
|
+
return [kind, name]
|
|
253
|
+
}
|
|
254
|
+
|
|
140
255
|
/** Convert a named function declaration to a hoisted const arrow */
|
|
141
256
|
function hoistFnDecl(name, params, body) {
|
|
142
|
-
const [p2, b2] = lowerArguments(params, body)
|
|
257
|
+
const [p2, b2] = lowerArguments(params, functionBodyBlock(body))
|
|
143
258
|
const decl = ['const', ['=', name, ['=>', p2, wrapArrowBody(b2)]]]
|
|
144
259
|
decl._hoisted = true
|
|
145
260
|
return decl
|
|
@@ -161,25 +276,6 @@ function transformScope(node) {
|
|
|
161
276
|
const hoisted = [], rest = []
|
|
162
277
|
for (let i = 0; i < args.length; i++) {
|
|
163
278
|
const stmt = args[i]
|
|
164
|
-
// Workaround for subscript parser ASI bug: multiline named IIFE
|
|
165
|
-
// `(function name(){...})();` is parsed as two statements when there are
|
|
166
|
-
// newlines inside the function body. Reconstruct the single-statement IIFE
|
|
167
|
-
// so the () handler can desugar it correctly.
|
|
168
|
-
if (Array.isArray(stmt) && stmt[0] === '()' &&
|
|
169
|
-
Array.isArray(stmt[1]) && stmt[1][0] === 'function' && stmt[1][1] &&
|
|
170
|
-
i + 1 < args.length && Array.isArray(args[i + 1]) && args[i + 1][0] === '()') {
|
|
171
|
-
const merged = ['()', ['()', stmt[1]], args[i + 1][1] ?? null]
|
|
172
|
-
const t = transform(merged)
|
|
173
|
-
if (t != null) {
|
|
174
|
-
if (Array.isArray(t) && t[0] === ';') {
|
|
175
|
-
for (const s of t.slice(1)) { if (s != null) rest.push(s) }
|
|
176
|
-
} else {
|
|
177
|
-
rest.push(t)
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
i++
|
|
181
|
-
continue
|
|
182
|
-
}
|
|
183
279
|
// Statement-form named function declaration: hoist directly (skip expression handler)
|
|
184
280
|
if (Array.isArray(stmt) && stmt[0] === 'function' && stmt[1]) {
|
|
185
281
|
hoisted.push(hoistFnDecl(stmt[1], stmt[2], stmt[3]))
|
|
@@ -225,24 +321,48 @@ function transformScope(node) {
|
|
|
225
321
|
* wins; a later redeclaration keeps only its initializer, as a plain assignment.
|
|
226
322
|
*/
|
|
227
323
|
function dedupeRedecls(stmts) {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
: 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
|
|
232
327
|
const seen = new Set(), out = []
|
|
233
328
|
for (const s of stmts) {
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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)
|
|
238
343
|
}
|
|
239
344
|
return out
|
|
240
345
|
}
|
|
241
346
|
|
|
242
|
-
/** 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. */
|
|
243
353
|
function wrapArrowBody(body) {
|
|
244
354
|
const t = transformScope(body)
|
|
245
|
-
|
|
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]]
|
|
246
366
|
}
|
|
247
367
|
|
|
248
368
|
/** Prototype identity check: X.prototype.Y */
|
|
@@ -252,6 +372,46 @@ const TYPED_ARRAYS = new Set(['Float64Array','Float32Array','Int32Array','Uint32
|
|
|
252
372
|
'Int16Array','Uint16Array','Int8Array','Uint8Array',
|
|
253
373
|
'ArrayBuffer','BigInt64Array','BigUint64Array','DataView'])
|
|
254
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
|
+
|
|
255
415
|
// `arguments` lowering: regular `function` has implicit `arguments`; arrow doesn't.
|
|
256
416
|
// jzify converts function → arrow, so any `arguments` use must be rewritten to a rest param.
|
|
257
417
|
// Arrow functions inherit `arguments` from enclosing function — don't stop at '=>'.
|
|
@@ -349,6 +509,10 @@ function prependParamDecls(decl, body) {
|
|
|
349
509
|
}
|
|
350
510
|
|
|
351
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]
|
|
352
516
|
|
|
353
517
|
// === class lowering ===
|
|
354
518
|
//
|
|
@@ -369,10 +533,19 @@ const arrowParams = params => Array.isArray(params) && params[0] === '()' ? para
|
|
|
369
533
|
// return selfN
|
|
370
534
|
// }
|
|
371
535
|
//
|
|
372
|
-
//
|
|
373
|
-
// `
|
|
374
|
-
//
|
|
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).
|
|
375
544
|
let classIdx = 0
|
|
545
|
+
let objThisIdx = 0
|
|
546
|
+
let staticClassIdx = 0
|
|
547
|
+
let classBaseIdx = 0
|
|
548
|
+
const DEFAULT_DERIVED_CTOR_ARITY = 8
|
|
376
549
|
|
|
377
550
|
const classBodyItems = (body) =>
|
|
378
551
|
body == null ? [] : Array.isArray(body) && body[0] === ';' ? body.slice(1) : [body]
|
|
@@ -389,39 +562,200 @@ function renameThis(node, to) {
|
|
|
389
562
|
return node.map(n => renameThis(n, to))
|
|
390
563
|
}
|
|
391
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
|
+
|
|
392
691
|
function jzifyError(msg) { throw new Error(`jzify: ${msg}`) }
|
|
393
692
|
|
|
394
693
|
function lowerClass(name, heritage, body) {
|
|
395
|
-
if (heritage != null) jzifyError('`class … extends …` is not supported yet — flatten the hierarchy or compose explicitly')
|
|
396
694
|
let ctorParams = null, ctorBody = null
|
|
397
|
-
const methods = [], fields = []
|
|
695
|
+
const methods = [], fields = [], statics = []
|
|
398
696
|
for (const it of classBodyItems(body)) {
|
|
399
697
|
if (typeof it === 'string') { fields.push([it, null]); continue } // bare `x;`
|
|
400
698
|
if (!Array.isArray(it)) continue
|
|
699
|
+
const bareFieldName = constStringKey(it)
|
|
700
|
+
if (bareFieldName != null) { fields.push([bareFieldName, null]); continue }
|
|
401
701
|
if (it[0] === ':' && Array.isArray(it[2]) && it[2][0] === '=>') {
|
|
402
|
-
|
|
403
|
-
if (
|
|
404
|
-
|
|
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]])
|
|
405
706
|
continue
|
|
406
707
|
}
|
|
407
708
|
if (it[0] === '=') {
|
|
408
709
|
const lhs = it[1]
|
|
409
|
-
if (Array.isArray(lhs) && lhs[0] === 'static')
|
|
410
|
-
|
|
411
|
-
|
|
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])
|
|
412
736
|
continue
|
|
413
737
|
}
|
|
414
738
|
if (it[0] === 'get' || it[0] === 'set') jzifyError('class getters/setters are not supported — jz objects have no accessors')
|
|
415
739
|
if (it[0] === 'static') jzifyError('`static` class members are not supported yet')
|
|
416
740
|
jzifyError(`unsupported class member ${JSON.stringify(it).slice(0, 60)}`)
|
|
417
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
|
+
}
|
|
418
757
|
const self = `self${classIdx++}`
|
|
419
758
|
const UNDEF = [] // jessie's node for `undefined`
|
|
420
|
-
// A class member body from jessie is a bare statement / `;`-sequence — wrap it
|
|
421
|
-
// in a `{}` block so the `=>` handler treats it as a function body, not an
|
|
422
|
-
// expression (an unwrapped `;`-seq arrow body produces malformed IR).
|
|
423
|
-
const block = b => Array.isArray(b) && b[0] === '{}' ? b : ['{}', b]
|
|
424
|
-
const usesThis = n => n === 'this' || (Array.isArray(n) && n[0] !== 'function' && n[0] !== 'class' && n.some(usesThis))
|
|
425
759
|
// Object literal: every declared field (its initializer inline when it doesn't
|
|
426
760
|
// touch `this`, else `undefined` and assigned below), every method as its
|
|
427
761
|
// self-capturing arrow. Declaring all fields up front fixes the object shape.
|
|
@@ -433,10 +767,39 @@ function lowerClass(name, heritage, body) {
|
|
|
433
767
|
for (const [mname, mparams, mbody] of methods)
|
|
434
768
|
litProps.push([':', mname, transform(['=>', mparams ?? ['()', null], block(renameThis(mbody, self))])])
|
|
435
769
|
const lit = ['{}', litProps.length === 0 ? null : litProps.length === 1 ? litProps[0] : [',', ...litProps]]
|
|
436
|
-
|
|
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
|
+
}
|
|
437
798
|
// `this`-dependent field initializers run, in declaration order, before the ctor.
|
|
438
|
-
|
|
439
|
-
|
|
799
|
+
if (heritage == null) {
|
|
800
|
+
for (const [fname, init] of deferred)
|
|
801
|
+
stmts.push(['=', ['.', self, fname], transform(renameThis(init, self))])
|
|
802
|
+
}
|
|
440
803
|
if (ctorBody != null) {
|
|
441
804
|
let cb = transform(renameThis(ctorBody, self))
|
|
442
805
|
if (Array.isArray(cb) && cb[0] === '{}') cb = cb[1]
|
|
@@ -444,15 +807,43 @@ function lowerClass(name, heritage, body) {
|
|
|
444
807
|
else if (cb != null) stmts.push(cb)
|
|
445
808
|
}
|
|
446
809
|
stmts.push(['return', self])
|
|
447
|
-
|
|
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
|
|
448
835
|
}
|
|
449
836
|
|
|
450
837
|
const handlers = {
|
|
451
838
|
// Named IIFE: (function name(p){b})(a) → let name = arrow; name(a)
|
|
452
839
|
'()'(callee, ...rest) {
|
|
840
|
+
if (callee === 'Array') {
|
|
841
|
+
const lit = lowerArrayConstructor(rest[0])
|
|
842
|
+
if (lit) return lit
|
|
843
|
+
}
|
|
453
844
|
if (Array.isArray(callee) && callee[0] === '()' && Array.isArray(callee[1]) && callee[1][0] === 'function' && callee[1][1]) {
|
|
454
845
|
const [, name, params, body] = callee[1]
|
|
455
|
-
const [p2, b2] = lowerArguments(params, body)
|
|
846
|
+
const [p2, b2] = lowerArguments(params, functionBodyBlock(body))
|
|
456
847
|
return [';', ['let', ['=', name, ['=>', arrowParams(p2), wrapArrowBody(b2)]]], ['()', name, ...rest.map(transform)]]
|
|
457
848
|
}
|
|
458
849
|
},
|
|
@@ -461,7 +852,7 @@ const handlers = {
|
|
|
461
852
|
// bound inside body per ES spec: `function f(){...f...}` → `(()=>{let f;f=arrow;return f})()`.
|
|
462
853
|
// Statement-form named functions are hoisted by transformScope before reaching here.
|
|
463
854
|
'function'(name, params, body) {
|
|
464
|
-
const [p2, b2] = lowerArguments(params, body)
|
|
855
|
+
const [p2, b2] = lowerArguments(params, functionBodyBlock(body))
|
|
465
856
|
const arrow = ['=>', p2, wrapArrowBody(b2)]
|
|
466
857
|
if (name) {
|
|
467
858
|
return ['()', ['()', ['=>', null, ['{}', [';',
|
|
@@ -474,7 +865,21 @@ const handlers = {
|
|
|
474
865
|
},
|
|
475
866
|
|
|
476
867
|
'=>'(params, body) {
|
|
477
|
-
|
|
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)
|
|
478
883
|
return ['=>', p2, transform(b2)]
|
|
479
884
|
},
|
|
480
885
|
|
|
@@ -490,60 +895,77 @@ const handlers = {
|
|
|
490
895
|
return ['let', ...args.map(transform)]
|
|
491
896
|
},
|
|
492
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
|
+
|
|
493
903
|
'='(lhs, rhs) {
|
|
494
|
-
|
|
495
|
-
if (Array.isArray(lhs) && lhs[0] === '.' && Array.isArray(rhs) && rhs[0] === '=') {
|
|
496
|
-
const targets = []
|
|
497
|
-
let cur = ['=', lhs, rhs]
|
|
498
|
-
while (Array.isArray(cur) && cur[0] === '=') { targets.push(cur[1]); cur = cur[2] }
|
|
499
|
-
const val = transform(cur)
|
|
500
|
-
const stmts = []
|
|
501
|
-
for (let i = targets.length - 1; i >= 0; i--) stmts.push(['=', transform(targets[i]), val])
|
|
502
|
-
return stmts.length === 1 ? stmts[0] : [';', ...stmts]
|
|
503
|
-
}
|
|
904
|
+
if (isDestructurePat(lhs)) return ['=', transformPattern(lhs), transform(rhs)]
|
|
504
905
|
},
|
|
505
906
|
|
|
506
907
|
'switch'(disc, ...cases) {
|
|
507
908
|
const clean = cases.map(c => {
|
|
508
909
|
if (c[0] === 'case' && Array.isArray(c[2]) && c[2][0] === ';') {
|
|
509
910
|
const body = c[2].slice(1).filter(s => typeof s !== 'number')
|
|
510
|
-
|
|
911
|
+
const stripped = stripTerminalSwitchBreak(body.length === 1 ? body[0] : [';', ...body])
|
|
912
|
+
return ['case', c[1], stripped]
|
|
511
913
|
}
|
|
512
914
|
if (c[0] === 'default' && Array.isArray(c[1]) && c[1][0] === ';') {
|
|
513
915
|
const body = c[1].slice(1).filter(s => s != null && typeof s !== 'number')
|
|
514
|
-
|
|
916
|
+
const stripped = stripTerminalSwitchBreak(body.length === 1 ? body[0] : [';', ...body])
|
|
917
|
+
return ['default', stripped]
|
|
515
918
|
}
|
|
919
|
+
if (c[0] === 'case') return ['case', c[1], stripTerminalSwitchBreak(c[2])]
|
|
920
|
+
if (c[0] === 'default') return ['default', stripTerminalSwitchBreak(c[1])]
|
|
516
921
|
return c
|
|
517
922
|
})
|
|
518
923
|
return transformSwitch(disc, clean)
|
|
519
924
|
},
|
|
520
925
|
|
|
521
|
-
//
|
|
522
|
-
|
|
523
|
-
|
|
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)] },
|
|
524
932
|
'==='(a, b) { if (isProto(a) || isProto(b)) return 1 },
|
|
525
933
|
'!=='(a, b) { if (isProto(a) || isProto(b)) return 0 },
|
|
526
934
|
|
|
527
935
|
// new → call (keep TypedArrays)
|
|
528
936
|
'new'(ctor, ...cargs) {
|
|
529
|
-
if (Array.isArray(ctor) && ctor[0] === '()' &&
|
|
530
|
-
|
|
937
|
+
if (Array.isArray(ctor) && ctor[0] === '()' && ctor[1] === 'Array') {
|
|
938
|
+
const lit = lowerArrayConstructor(ctor[2])
|
|
939
|
+
if (lit) return lit
|
|
531
940
|
}
|
|
532
941
|
const name = typeof ctor === 'string' ? ctor : (Array.isArray(ctor) && ctor[0] === '()' ? ctor[1] : null)
|
|
533
|
-
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)]
|
|
534
943
|
if (Array.isArray(ctor) && ctor[0] === '()') return transform(ctor)
|
|
535
944
|
// `new C(a)` → `C(a)`; `new C` (no parens) → `C()` — a 2-element `['()', X]`
|
|
536
945
|
// is grouping parens, so a no-arg call needs the explicit `null` arg slot.
|
|
537
946
|
return ['()', transform(ctor), ...(cargs.length ? cargs.map(transform) : [null])]
|
|
538
947
|
},
|
|
539
948
|
|
|
540
|
-
// 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.
|
|
541
957
|
'instanceof'(val, ctor) {
|
|
542
958
|
const t = transform(val)
|
|
543
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]
|
|
544
963
|
if (name === 'Array') return ['()', ['.', 'Array', 'isArray'], t]
|
|
545
|
-
if (name === '
|
|
546
|
-
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.
|
|
547
969
|
return ['===', ['typeof', t], [null, 'object']]
|
|
548
970
|
},
|
|
549
971
|
|
|
@@ -557,8 +979,27 @@ const handlers = {
|
|
|
557
979
|
['while', ['||', flag, transform(cond)], ['{}', [';', ['=', flag, [null, false]], transform(body)]]]]
|
|
558
980
|
},
|
|
559
981
|
|
|
560
|
-
// Block body: recurse as scope for hoisting
|
|
561
|
-
|
|
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
|
+
},
|
|
562
1003
|
|
|
563
1004
|
// Export: recurse into exported declaration. Statement-form `export function name`
|
|
564
1005
|
// and `export default function name` must be hoisted as const-arrows — otherwise
|
|
@@ -589,7 +1030,10 @@ function transform(node) {
|
|
|
589
1030
|
const [op, ...args] = node
|
|
590
1031
|
if (op == null) return node
|
|
591
1032
|
const h = handlers[op]
|
|
592
|
-
|
|
1033
|
+
// A handler that returns nullish (including no `return`) means "no rewrite at
|
|
1034
|
+
// this node" — fall through to a generic recurse. `??` (not `||`) so handlers
|
|
1035
|
+
// like `'==='` can legitimately return `0`.
|
|
1036
|
+
return (h && h(...args)) ?? [op, ...args.map(transform)]
|
|
593
1037
|
}
|
|
594
1038
|
|
|
595
1039
|
// Esbuild emits a small ESM helper:
|
|
@@ -611,16 +1055,16 @@ function foldStaticExportHelpers(ast) {
|
|
|
611
1055
|
|
|
612
1056
|
const defPropAliases = new Set()
|
|
613
1057
|
for (const stmt of body) {
|
|
614
|
-
|
|
615
|
-
|
|
1058
|
+
const b = bindingOf(stmt)
|
|
1059
|
+
if (b && isObjectDefineProperty(b[1])) defPropAliases.add(b[0])
|
|
616
1060
|
}
|
|
617
1061
|
if (!defPropAliases.size) return ast
|
|
618
1062
|
|
|
619
1063
|
const helperNames = new Set()
|
|
620
1064
|
for (const stmt of body) {
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
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])
|
|
624
1068
|
}
|
|
625
1069
|
if (!helperNames.size) return ast
|
|
626
1070
|
|
|
@@ -640,6 +1084,210 @@ function foldStaticExportHelpers(ast) {
|
|
|
640
1084
|
return rewritten.length === 0 ? null : rewritten.length === 1 ? rewritten[0] : [';', ...rewritten]
|
|
641
1085
|
}
|
|
642
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
|
+
|
|
643
1291
|
function astSeq(ast) {
|
|
644
1292
|
if (!Array.isArray(ast)) return null
|
|
645
1293
|
return ast[0] === ';' ? ast.slice(1).filter(Boolean) : [ast]
|
|
@@ -649,12 +1297,28 @@ function isObjectDefineProperty(node) {
|
|
|
649
1297
|
return Array.isArray(node) && node[0] === '.' && node[1] === 'Object' && node[2] === 'defineProperty'
|
|
650
1298
|
}
|
|
651
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
|
+
|
|
652
1314
|
function isDefPropAliasAssign(stmt, aliases) {
|
|
653
|
-
|
|
1315
|
+
const b = bindingOf(stmt)
|
|
1316
|
+
return b != null && aliases.has(b[0]) && isObjectDefineProperty(b[1])
|
|
654
1317
|
}
|
|
655
1318
|
|
|
656
1319
|
function isExportHelperAssign(stmt, helpers) {
|
|
657
|
-
|
|
1320
|
+
const b = bindingOf(stmt)
|
|
1321
|
+
return b != null && helpers.has(b[0])
|
|
658
1322
|
}
|
|
659
1323
|
|
|
660
1324
|
function containsDefinePropertyCall(node, aliases) {
|
|
@@ -699,6 +1363,8 @@ function getterReturnExpr(node) {
|
|
|
699
1363
|
if (params.length !== 0) return null
|
|
700
1364
|
const body = node[2]
|
|
701
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]
|
|
702
1368
|
if (Array.isArray(body) && body[0] === 'return') return body[1]
|
|
703
1369
|
return body
|
|
704
1370
|
}
|
|
@@ -713,202 +1379,175 @@ function replaceStaticExportReads(node, rewrites) {
|
|
|
713
1379
|
return node.map((part, i) => i === 0 ? part : replaceStaticExportReads(part, rewrites))
|
|
714
1380
|
}
|
|
715
1381
|
|
|
1382
|
+
function canonicalizeObjectIdioms(node) {
|
|
1383
|
+
if (node == null || typeof node !== 'object' || !Array.isArray(node)) return node
|
|
1384
|
+
|
|
1385
|
+
const out = node.map((part, i) => i === 0 ? part : canonicalizeObjectIdioms(part))
|
|
1386
|
+
|
|
1387
|
+
const hasOwnCall = objectHasOwnPropertyCall(out)
|
|
1388
|
+
if (hasOwnCall) return ['()', ['.', hasOwnCall.obj, 'hasOwnProperty'], hasOwnCall.key]
|
|
1389
|
+
|
|
1390
|
+
const mapString = arrayMapStringCallback(out)
|
|
1391
|
+
if (mapString) return mapString
|
|
1392
|
+
|
|
1393
|
+
if (out[0] === '&&') {
|
|
1394
|
+
const leftCtor = constructorIsObject(out[1])
|
|
1395
|
+
const rightKeys = objectKeysLengthZero(out[2])
|
|
1396
|
+
if (leftCtor && rightKeys && astEqual(leftCtor.obj, rightKeys.obj)) return out[2]
|
|
1397
|
+
|
|
1398
|
+
const leftKeys = objectKeysLengthZero(out[1])
|
|
1399
|
+
const rightCtor = constructorIsObject(out[2])
|
|
1400
|
+
if (leftKeys && rightCtor && astEqual(leftKeys.obj, rightCtor.obj)) return out[1]
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
return out
|
|
1404
|
+
}
|
|
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
|
+
|
|
1415
|
+
function objectHasOwnPropertyCall(node) {
|
|
1416
|
+
if (!Array.isArray(node) || node[0] !== '()') return null
|
|
1417
|
+
const callee = node[1]
|
|
1418
|
+
if (!Array.isArray(callee) || callee[0] !== '.' || callee[2] !== 'call') return null
|
|
1419
|
+
if (!isObjectHasOwnPropertyRef(callee[1])) return null
|
|
1420
|
+
const args = callArgs(node.slice(2))
|
|
1421
|
+
if (args.length < 2) return null
|
|
1422
|
+
return { obj: args[0], key: args[1] }
|
|
1423
|
+
}
|
|
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
|
+
|
|
1431
|
+
function constructorIsObject(node) {
|
|
1432
|
+
if (!Array.isArray(node) || (node[0] !== '===' && node[0] !== '==')) return null
|
|
1433
|
+
const left = constructorReceiver(node[1])
|
|
1434
|
+
if (left && node[2] === 'Object') return { obj: left }
|
|
1435
|
+
const right = constructorReceiver(node[2])
|
|
1436
|
+
if (right && node[1] === 'Object') return { obj: right }
|
|
1437
|
+
return null
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
function constructorReceiver(node) {
|
|
1441
|
+
return Array.isArray(node) && node[0] === '.' && node[2] === 'constructor' ? node[1] : null
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
function objectKeysLengthZero(node) {
|
|
1445
|
+
if (!Array.isArray(node) || (node[0] !== '===' && node[0] !== '==')) return null
|
|
1446
|
+
const left = objectKeysLengthReceiver(node[1])
|
|
1447
|
+
if (left && isZeroLiteral(node[2])) return { obj: left }
|
|
1448
|
+
const right = objectKeysLengthReceiver(node[2])
|
|
1449
|
+
if (right && isZeroLiteral(node[1])) return { obj: right }
|
|
1450
|
+
return null
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
function objectKeysLengthReceiver(node) {
|
|
1454
|
+
if (!Array.isArray(node) || node[0] !== '.' || node[2] !== 'length') return null
|
|
1455
|
+
const call = node[1]
|
|
1456
|
+
if (!Array.isArray(call) || call[0] !== '()') return null
|
|
1457
|
+
const callee = call[1]
|
|
1458
|
+
if (!Array.isArray(callee) || callee[0] !== '.' || callee[1] !== 'Object' || callee[2] !== 'keys') return null
|
|
1459
|
+
const args = callArgs(call.slice(2))
|
|
1460
|
+
return args.length === 1 ? args[0] : null
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
function isZeroLiteral(node) {
|
|
1464
|
+
return Array.isArray(node) && node[0] == null && node[1] === 0
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
function astEqual(a, b) {
|
|
1468
|
+
return JSON.stringify(a) === JSON.stringify(b)
|
|
1469
|
+
}
|
|
1470
|
+
|
|
716
1471
|
function cloneAst(node) {
|
|
717
1472
|
if (node == null || typeof node !== 'object') return node
|
|
718
1473
|
if (!Array.isArray(node)) return node
|
|
719
1474
|
return node.map(cloneAst)
|
|
720
1475
|
}
|
|
721
1476
|
|
|
1477
|
+
function stripTerminalSwitchBreak(body) {
|
|
1478
|
+
if (!Array.isArray(body)) return body
|
|
1479
|
+
if (body[0] === 'break') return null
|
|
1480
|
+
if (body[0] === '{}') {
|
|
1481
|
+
const inner = stripTerminalSwitchBreak(body[1])
|
|
1482
|
+
if (inner == null) return ['{}', [';']]
|
|
1483
|
+
return ['{}', Array.isArray(inner) && inner[0] === ';' ? inner : [';', inner]]
|
|
1484
|
+
}
|
|
1485
|
+
if (body[0] !== ';') return body
|
|
1486
|
+
|
|
1487
|
+
const stmts = body.slice(1)
|
|
1488
|
+
if (Array.isArray(stmts.at(-1)) && stmts.at(-1)[0] === 'break') stmts.pop()
|
|
1489
|
+
return stmts.length === 0 ? null : stmts.length === 1 ? stmts[0] : [';', ...stmts]
|
|
1490
|
+
}
|
|
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
|
+
|
|
722
1526
|
/** Transform switch statement to if/else chain. */
|
|
723
1527
|
let swIdx = 0
|
|
724
1528
|
function transformSwitch(discriminant, cases) {
|
|
725
1529
|
const disc = transform(discriminant)
|
|
726
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
|
|
727
1533
|
|
|
728
1534
|
// Collect case/default
|
|
729
1535
|
const stmts = [['let', ['=', tmp, disc]]]
|
|
1536
|
+
if (brk) stmts.push(['let', ['=', brk, [null, false]]])
|
|
730
1537
|
let chain = null
|
|
731
1538
|
|
|
732
1539
|
for (let i = cases.length - 1; i >= 0; i--) {
|
|
733
1540
|
const c = cases[i]
|
|
734
1541
|
if (c[0] === 'default') {
|
|
735
|
-
|
|
1542
|
+
const body = transform(c[1])
|
|
1543
|
+
chain = brk ? rewriteSwitchBreaks(body, brk) : body
|
|
736
1544
|
} else if (c[0] === 'case') {
|
|
737
1545
|
const cond = ['===', tmp, transform(c[1])]
|
|
738
1546
|
const body = transform(c[2])
|
|
739
|
-
|
|
1547
|
+
const lowered = brk ? rewriteSwitchBreaks(body, brk) : body
|
|
1548
|
+
chain = chain != null ? ['if', cond, lowered, chain] : ['if', cond, lowered]
|
|
740
1549
|
}
|
|
741
1550
|
}
|
|
742
1551
|
if (chain) stmts.push(chain)
|
|
743
1552
|
return [';', ...stmts]
|
|
744
1553
|
}
|
|
745
|
-
|
|
746
|
-
// === AST → jz source codegen ===
|
|
747
|
-
|
|
748
|
-
const INDENT = ' '
|
|
749
|
-
const prec = { '=': 1, '+=': 1, '-=': 1, '*=': 1, '/=': 1, '%=': 1, '&=': 1, '|=': 1, '^=': 1, '>>=': 1, '<<=': 1, '>>>=': 1, '||=': 1, '&&=': 1,
|
|
750
|
-
'??': 2, '||': 3, '&&': 4, '|': 5, '^': 6, '&': 7, '===': 8, '!==': 8, '==': 8, '!=': 8,
|
|
751
|
-
'<': 9, '>': 9, '<=': 9, '>=': 9, '<<': 10, '>>': 10, '>>>': 10,
|
|
752
|
-
'+': 11, '-': 11, '*': 12, '/': 12, '%': 12, '**': 13 }
|
|
753
|
-
|
|
754
|
-
/** Wrap statement in { } if not already a block */
|
|
755
|
-
function wrapBlock(node, depth) {
|
|
756
|
-
if (Array.isArray(node) && node[0] === '{}') return codegen(node, depth)
|
|
757
|
-
return '{ ' + codegen(node, depth) + '; }'
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
/** Generate jz source from AST. Enforces semicolons. */
|
|
761
|
-
export function codegen(node, depth = 0) {
|
|
762
|
-
if (node == null) return ''
|
|
763
|
-
if (typeof node === 'number') return String(node)
|
|
764
|
-
if (typeof node === 'bigint') return node + 'n'
|
|
765
|
-
if (typeof node === 'string') return node
|
|
766
|
-
if (!Array.isArray(node)) return String(node)
|
|
767
|
-
|
|
768
|
-
const [op, ...a] = node
|
|
769
|
-
const ind = INDENT.repeat(depth), ind1 = INDENT.repeat(depth + 1)
|
|
770
|
-
|
|
771
|
-
// Literal: [, value]
|
|
772
|
-
if (op == null) return typeof a[0] === 'string' ? JSON.stringify(a[0]) : a[0] == null ? 'null' : String(a[0]) + (typeof a[0] === 'bigint' ? 'n' : '')
|
|
773
|
-
|
|
774
|
-
// Statements
|
|
775
|
-
if (op === ';') return a.map(s => codegen(s, depth)).filter(Boolean).join(';\n' + ind) + ';'
|
|
776
|
-
if (op === '{}') {
|
|
777
|
-
// Discriminate object literal / destructuring pattern from block.
|
|
778
|
-
// Object: `:` key-value, `,` of object-pattern items (id / `:` / `...` / `= default`),
|
|
779
|
-
// lone string shorthand. Empty `{}` outputs the same string either way.
|
|
780
|
-
const body = a[0]
|
|
781
|
-
const isObjItem = (n) => typeof n === 'string' ||
|
|
782
|
-
(Array.isArray(n) && (n[0] === ':' || n[0] === '...' || n[0] === 'as' ||
|
|
783
|
-
(n[0] === '=' && typeof n[1] === 'string')))
|
|
784
|
-
const isObj = body == null ? false
|
|
785
|
-
: typeof body === 'string' ? true
|
|
786
|
-
: Array.isArray(body) && (body[0] === ':' || body[0] === '...' || body[0] === 'as' ||
|
|
787
|
-
(body[0] === ',' && body.slice(1).every(isObjItem)))
|
|
788
|
-
if (isObj) {
|
|
789
|
-
if (typeof body === 'string') return '{ ' + body + ' }'
|
|
790
|
-
if (body[0] === ',') return '{ ' + body.slice(1).map(x => codegen(x)).join(', ') + ' }'
|
|
791
|
-
return '{ ' + codegen(body) + ' }'
|
|
792
|
-
}
|
|
793
|
-
// Block: body is null, a single statement, or [';', ...stmts]
|
|
794
|
-
const stmts = body == null ? [] : (Array.isArray(body) && body[0] === ';' ? body.slice(1) : [body])
|
|
795
|
-
const rendered = stmts.map(s => codegen(s, depth + 1)).filter(Boolean).join(';\n' + ind1)
|
|
796
|
-
return '{\n' + ind1 + rendered + (rendered ? ';' : '') + '\n' + ind + '}'
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
// Declarations
|
|
800
|
-
if (op === 'let' || op === 'const') return op + ' ' + a.map(d => codegen(d, depth)).join(', ')
|
|
801
|
-
if (op === 'export') { const inner = codegen(a[0], depth); return inner ? 'export ' + inner : '' }
|
|
802
|
-
if (op === 'default') return 'default ' + codegen(a[0], depth)
|
|
803
|
-
|
|
804
|
-
// Control flow
|
|
805
|
-
if (op === 'if') {
|
|
806
|
-
const cond = codegen(a[0]), then = wrapBlock(a[1], depth)
|
|
807
|
-
return a[2] != null
|
|
808
|
-
? 'if (' + cond + ') ' + then + ' else ' + wrapBlock(a[2], depth)
|
|
809
|
-
: 'if (' + cond + ') ' + then
|
|
810
|
-
}
|
|
811
|
-
if (op === 'while') return 'while (' + codegen(a[0]) + ') ' + wrapBlock(a[1], depth)
|
|
812
|
-
if (op === 'for') {
|
|
813
|
-
if (a.length === 2) { // ['for', head, body] — subscript shape
|
|
814
|
-
const [head, body] = a
|
|
815
|
-
if (Array.isArray(head) && (head[0] === 'of' || head[0] === 'in'))
|
|
816
|
-
return 'for (' + codegen(head[1]) + ' ' + head[0] + ' ' + codegen(head[2]) + ') ' + wrapBlock(body, depth)
|
|
817
|
-
// ['let'/'const', ['in'/'of', name, obj]] — subscript wraps var→let around in/of
|
|
818
|
-
if (Array.isArray(head) && (head[0] === 'let' || head[0] === 'const') && Array.isArray(head[1]) && (head[1][0] === 'in' || head[1][0] === 'of'))
|
|
819
|
-
return 'for (' + head[0] + ' ' + codegen(head[1][1]) + ' ' + head[1][0] + ' ' + codegen(head[1][2]) + ') ' + wrapBlock(body, depth)
|
|
820
|
-
// C-style head [';', init, cond, update] is positional — empty slots are valid,
|
|
821
|
-
// must not flow through the generic `;` joiner (which adds newlines + a trailing `;`).
|
|
822
|
-
if (Array.isArray(head) && head[0] === ';')
|
|
823
|
-
return 'for (' + (head[1] == null ? '' : codegen(head[1])) + '; ' + (head[2] == null ? '' : codegen(head[2])) + '; ' + (head[3] == null ? '' : codegen(head[3])) + ') ' + wrapBlock(body, depth)
|
|
824
|
-
return 'for (' + codegen(head) + ') ' + wrapBlock(body, depth)
|
|
825
|
-
}
|
|
826
|
-
return 'for (' + (codegen(a[0]) || '') + '; ' + (codegen(a[1]) || '') + '; ' + (codegen(a[2]) || '') + ') ' + wrapBlock(a[3], depth)
|
|
827
|
-
}
|
|
828
|
-
if (op === 'return') return 'return ' + codegen(a[0])
|
|
829
|
-
if (op === 'throw') return 'throw ' + codegen(a[0])
|
|
830
|
-
if (op === 'break') return 'break'
|
|
831
|
-
if (op === 'continue') return 'continue'
|
|
832
|
-
// catch with optional binding: ['catch', tryBlock, catchBody] or ['catch', tryBlock, paramName, catchBody]
|
|
833
|
-
if (op === 'catch') {
|
|
834
|
-
if (a.length === 3) return 'try ' + codegen(a[0], depth) + ' catch (' + a[1] + ') ' + codegen(a[2], depth)
|
|
835
|
-
return 'try ' + codegen(a[0], depth) + ' catch ' + codegen(a[1], depth)
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
// Arrow
|
|
839
|
-
if (op === '=>') {
|
|
840
|
-
// Params: already wrapped in () by parser, or bare name
|
|
841
|
-
const p = a[0]
|
|
842
|
-
const params = Array.isArray(p) && p[0] === '()' ? codegen(p) : '(' + codegen(p) + ')'
|
|
843
|
-
const body = a[1]
|
|
844
|
-
const isBlock = Array.isArray(body) && (body[0] === '{}' || body[0] === ';' || body[0] === 'return')
|
|
845
|
-
const bodyStr = Array.isArray(body) && body[0] !== '{}' && isBlock
|
|
846
|
-
? '{ ' + codegen(body, depth) + '; }'
|
|
847
|
-
: codegen(body, depth)
|
|
848
|
-
return params + ' => ' + bodyStr
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
// Grouping parens / function call
|
|
852
|
-
if (op === '()') {
|
|
853
|
-
if (a.length === 1) return '(' + (a[0] == null ? '' : codegen(a[0])) + ')'
|
|
854
|
-
return codegen(a[0]) + '(' + a.slice(1).map(x => codegen(x)).join(', ') + ')'
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
// Property access
|
|
858
|
-
if (op === '.') return codegen(a[0]) + '.' + a[1]
|
|
859
|
-
if (op === '?.') return codegen(a[0]) + '?.' + a[1]
|
|
860
|
-
if (op === '?.[]') return codegen(a[0]) + '?.[' + codegen(a[1]) + ']'
|
|
861
|
-
if (op === '?.()') return codegen(a[0]) + '?.(' + a.slice(1).map(x => codegen(x)).join(', ') + ')'
|
|
862
|
-
if (op === '[]') {
|
|
863
|
-
// Array literal: ['[]', body] (length 2 → a.length 1). body may be null (empty),
|
|
864
|
-
// a single element, or a [',', ...items] sequence.
|
|
865
|
-
if (a.length === 1) {
|
|
866
|
-
if (a[0] == null) return '[]'
|
|
867
|
-
const body = a[0]
|
|
868
|
-
if (Array.isArray(body) && body[0] === ',') return '[' + body.slice(1).map(x => codegen(x)).join(', ') + ']'
|
|
869
|
-
return '[' + codegen(body) + ']'
|
|
870
|
-
}
|
|
871
|
-
// Subscript: ['[]', obj, idx]
|
|
872
|
-
return codegen(a[0]) + '[' + codegen(a[1]) + ']'
|
|
873
|
-
}
|
|
874
|
-
if (op === ':') return codegen(a[0]) + ': ' + codegen(a[1])
|
|
875
|
-
if (op === 'str') return JSON.stringify(a[0])
|
|
876
|
-
if (op === '//') return '/' + a[0] + '/' + (a[1] || '')
|
|
877
|
-
|
|
878
|
-
// Comma
|
|
879
|
-
if (op === ',') return a.map(x => codegen(x)).join(', ')
|
|
880
|
-
// Template literal: alternating string/expr parts. String parts are [null, "str"], expr parts are AST nodes.
|
|
881
|
-
if (op === '`') return '`' + a.map(p => {
|
|
882
|
-
if (Array.isArray(p) && p[0] == null && typeof p[1] === 'string') return p[1].replace(/[`\\$]/g, c => '\\' + c)
|
|
883
|
-
return '${' + codegen(p) + '}'
|
|
884
|
-
}).join('') + '`'
|
|
885
|
-
|
|
886
|
-
// Spread
|
|
887
|
-
if (op === '...') return '...' + codegen(a[0])
|
|
888
|
-
|
|
889
|
-
// Import / export rename
|
|
890
|
-
if (op === 'import') return 'import ' + codegen(a[0])
|
|
891
|
-
if (op === 'from') return codegen(a[0]) + ' from ' + codegen(a[1])
|
|
892
|
-
if (op === 'as') return codegen(a[0]) + ' as ' + codegen(a[1])
|
|
893
|
-
|
|
894
|
-
// Unary prefix
|
|
895
|
-
if (a.length === 1) {
|
|
896
|
-
if (op === '++' || op === '--') return a[0] == null ? op : op + codegen(a[0])
|
|
897
|
-
if (op === 'typeof') return 'typeof ' + codegen(a[0])
|
|
898
|
-
if (op === 'u-') return '-' + codegen(a[0])
|
|
899
|
-
if (op === 'u+') return '+' + codegen(a[0])
|
|
900
|
-
return op + codegen(a[0])
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
// Postfix
|
|
904
|
-
if (a.length === 2 && a[1] === null) return codegen(a[0]) + op
|
|
905
|
-
|
|
906
|
-
// Binary
|
|
907
|
-
if (a.length === 2 && prec[op]) return codegen(a[0]) + ' ' + op + ' ' + codegen(a[1])
|
|
908
|
-
|
|
909
|
-
// Ternary
|
|
910
|
-
if (op === '?' || op === '?:') return codegen(a[0]) + ' ? ' + codegen(a[1]) + ' : ' + codegen(a[2])
|
|
911
|
-
|
|
912
|
-
// Fallback
|
|
913
|
-
return op + '(' + a.map(x => codegen(x)).join(', ') + ')'
|
|
914
|
-
}
|