jz 0.5.1 → 0.7.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.
Files changed (86) hide show
  1. package/README.md +315 -318
  2. package/bench/README.md +369 -0
  3. package/bench/bench.svg +102 -0
  4. package/cli.js +104 -30
  5. package/dist/interop.js +1 -0
  6. package/dist/jz.js +6024 -0
  7. package/index.d.ts +126 -0
  8. package/index.js +362 -84
  9. package/interop.js +159 -186
  10. package/jz.svg +5 -0
  11. package/jzify/arguments.js +97 -0
  12. package/jzify/bundler.js +382 -0
  13. package/jzify/classes.js +328 -0
  14. package/jzify/hoist-vars.js +177 -0
  15. package/jzify/index.js +51 -0
  16. package/jzify/names.js +37 -0
  17. package/jzify/switch.js +106 -0
  18. package/jzify/transform.js +349 -0
  19. package/layout.js +184 -0
  20. package/module/array.js +377 -176
  21. package/module/collection.js +639 -145
  22. package/module/console.js +55 -43
  23. package/module/core.js +264 -153
  24. package/module/date.js +15 -3
  25. package/module/function.js +73 -6
  26. package/module/index.js +2 -1
  27. package/module/json.js +226 -61
  28. package/module/math.js +551 -187
  29. package/module/number.js +327 -60
  30. package/module/object.js +474 -184
  31. package/module/regex.js +255 -25
  32. package/module/schema.js +24 -6
  33. package/module/simd.js +117 -0
  34. package/module/string.js +621 -226
  35. package/module/symbol.js +1 -1
  36. package/module/timer.js +9 -14
  37. package/module/typedarray.js +459 -73
  38. package/package.json +58 -14
  39. package/src/abi/index.js +39 -23
  40. package/src/abi/string.js +38 -41
  41. package/src/ast.js +460 -0
  42. package/src/autoload.js +29 -24
  43. package/src/bridge.js +111 -0
  44. package/src/compile/analyze-scans.js +824 -0
  45. package/src/compile/analyze.js +1600 -0
  46. package/src/compile/emit-assign.js +411 -0
  47. package/src/compile/emit.js +3512 -0
  48. package/src/compile/flow-types.js +103 -0
  49. package/src/{compile.js → compile/index.js} +778 -128
  50. package/src/{infer.js → compile/infer.js} +62 -98
  51. package/src/compile/loop-divmod.js +155 -0
  52. package/src/{narrow.js → compile/narrow.js} +409 -106
  53. package/src/compile/peel-stencil.js +261 -0
  54. package/src/compile/plan/advise.js +370 -0
  55. package/src/compile/plan/common.js +150 -0
  56. package/src/compile/plan/index.js +122 -0
  57. package/src/compile/plan/inline.js +682 -0
  58. package/src/compile/plan/literals.js +1199 -0
  59. package/src/compile/plan/loops.js +472 -0
  60. package/src/compile/plan/scope.js +649 -0
  61. package/src/compile/program-facts.js +423 -0
  62. package/src/ctx.js +220 -64
  63. package/src/ir.js +589 -172
  64. package/src/kind-traits.js +132 -0
  65. package/src/kind.js +524 -0
  66. package/src/op-policy.js +57 -0
  67. package/src/{optimize.js → optimize/index.js} +1432 -473
  68. package/src/optimize/vectorize.js +4660 -0
  69. package/src/param-reps.js +65 -0
  70. package/src/parse.js +44 -0
  71. package/src/{prepare.js → prepare/index.js} +649 -205
  72. package/src/prepare/lift-iife.js +149 -0
  73. package/src/reps.js +116 -0
  74. package/src/resolve.js +12 -3
  75. package/src/static.js +208 -0
  76. package/src/type.js +651 -0
  77. package/src/{assemble.js → wat/assemble.js} +275 -55
  78. package/src/wat/optimize.js +3938 -0
  79. package/transform.js +21 -0
  80. package/wasi.js +47 -5
  81. package/src/analyze.js +0 -3818
  82. package/src/emit.js +0 -3040
  83. package/src/jzify.js +0 -1580
  84. package/src/plan.js +0 -2132
  85. package/src/vectorize.js +0 -1088
  86. /package/src/{codegen.js → wat/codegen.js} +0 -0
package/src/jzify.js DELETED
@@ -1,1580 +0,0 @@
1
- /**
2
- * jzify — Transform JS AST into jz-compatible form.
3
- *
4
- * Crockford-aligned: eliminates bad parts, enforces good practices.
5
- * Runs before prepare() as an AST→AST pass.
6
- *
7
- * Transforms:
8
- * function name(args) { body } → const name = (args) => { body }
9
- * var → let
10
- * switch → if/else chain
11
- * new X(args) → X(args) (for known safe constructors)
12
- * == → ===, != → !==
13
- *
14
- * Hoisting: function declarations are collected and moved to the top
15
- * of their scope (module or block), preserving semantics.
16
- *
17
- * @module jzify
18
- */
19
-
20
- /**
21
- * Transform AST in-place. Returns transformed AST.
22
- * @param {Array} ast - subscript/jessie parsed AST
23
- * @returns {Array} Transformed AST
24
- */
25
- export default function jzify(ast) {
26
- swIdx = 0
27
- argsIdx = 0
28
- doIdx = 0
29
- classIdx = 0
30
- objThisIdx = 0
31
- staticClassIdx = 0
32
- classBaseIdx = 0
33
- // Hoist module-level vars: any `var x` inside nested blocks bubbles up.
34
- const names = new Set()
35
- ast = hoistVars(ast, names)
36
- if (names.size) ast = prependDecls(ast, names)
37
- return foldStaticBundlerHelpers(foldStaticExportHelpers(canonicalizeObjectIdioms(transformScope(ast))))
38
- }
39
-
40
- /**
41
- * Walk function/script body, replacing `var` declarations with assignments and
42
- * collecting names. Does not cross function/arrow boundaries — nested functions
43
- * get their own hoist pass when wrapArrowBody processes them.
44
- *
45
- * ['var', 'x'] → null (bare decl, no-op)
46
- * ['var', ['=', x, init]] → ['=', x, init]
47
- * ['var', ['=', x, 1], ['=', y, 2]] → [',', ['=', x, 1], ['=', y, 2]]
48
- * ['var', 'x', 'y'] → null
49
- * ['in', ['var', x], obj] → ['in', x, obj] (for-in head)
50
- */
51
- function hoistVars(node, names) {
52
- if (node == null || !Array.isArray(node)) return node
53
- const op = node[0]
54
- // Nested function/arrow: hoist within its own scope, prepend let-decl, return new node.
55
- if (op === 'function') {
56
- const inner = new Set()
57
- let body = hoistVars(node[3], inner)
58
- if (inner.size) body = prependDecls(body, inner)
59
- return ['function', node[1], node[2], body]
60
- }
61
- if (op === '=>') {
62
- const inner = new Set()
63
- let body = hoistVars(node[2], inner)
64
- if (inner.size) body = prependDecls(body, inner)
65
- return ['=>', node[1], body]
66
- }
67
- if (op === 'in' || op === 'of') {
68
- let lhs = node[1]
69
- if (Array.isArray(lhs) && lhs[0] === 'var' && typeof lhs[1] === 'string' && lhs.length === 2) {
70
- names.add(lhs[1])
71
- lhs = lhs[1]
72
- } else {
73
- lhs = hoistVars(lhs, names)
74
- }
75
- return [op, lhs, hoistVars(node[2], names)]
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
- }
82
- if (op === '=' && Array.isArray(node[1]) && node[1][0] === 'var' && typeof node[1][1] === 'string' && node[1].length === 2) {
83
- names.add(node[1][1])
84
- return ['=', node[1][1], hoistVars(node[2], names)]
85
- }
86
- if (op === '=' && isDestructurePat(node[1])) {
87
- return ['=', hoistPattern(node[1], names), hoistVars(node[2], names)]
88
- }
89
- // For-head `;` is positional (init; cond; update), not a statement sequence.
90
- // Recurse into each slot but never filter nulls — empty slots are valid.
91
- if (op === 'for') {
92
- const head = node[1]
93
- let h2
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]) &&
98
- (head[1][0] === 'in' || head[1][0] === 'of') && typeof head[1][1] === 'string') {
99
- names.add(head[1][1])
100
- h2 = [head[1][0], head[1][1], hoistVars(head[1][2], names)]
101
- } else if (Array.isArray(head) && head[0] === ';') {
102
- h2 = [';']
103
- for (let i = 1; i < head.length; i++) h2.push(hoistVars(head[i], names))
104
- } else {
105
- h2 = hoistVars(head, names)
106
- }
107
- return ['for', h2, hoistVars(node[2], names)]
108
- }
109
- if (op === 'var') {
110
- const decls = []
111
- for (let i = 1; i < node.length; i++) {
112
- const d = node[i]
113
- if (typeof d === 'string') { names.add(d); continue }
114
- if (Array.isArray(d) && d[0] === '=' && typeof d[1] === 'string') {
115
- names.add(d[1])
116
- decls.push(['=', d[1], hoistVars(d[2], names)])
117
- }
118
- }
119
- if (decls.length === 0) return null
120
- if (decls.length === 1) return decls[0]
121
- return [',', ...decls]
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
- }
135
- // Filter null returns from `;` sequences (bare-var no-ops). `{}` is left
136
- // to recurse normally — it may be either a block or an object literal,
137
- // and we don't want to clobber `['{}', null]` (empty object literal).
138
- if (op === ';') {
139
- const out = [op]
140
- for (let i = 1; i < node.length; i++) {
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)
157
- if (c != null) out.push(c)
158
- }
159
- if (out.length === 1) return null
160
- if (out.length === 2) return out[1]
161
- return out
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
- }
175
- const out = new Array(node.length)
176
- out[0] = op
177
- for (let i = 1; i < node.length; i++) out[i] = hoistVars(node[i], names)
178
- return out
179
- }
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
-
201
- function prependDecls(body, names) {
202
- const decl = ['let', ...names]
203
- if (Array.isArray(body) && body[0] === ';') return [';', decl, ...body.slice(1)]
204
- if (Array.isArray(body) && body[0] === '{}') {
205
- const inner = body[1]
206
- if (Array.isArray(inner) && inner[0] === ';') return ['{}', [';', decl, ...inner.slice(1)]]
207
- if (inner == null) return ['{}', decl]
208
- return ['{}', [';', decl, inner]]
209
- }
210
- return body == null ? decl : [';', decl, body]
211
- }
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
-
255
- /** Convert a named function declaration to a hoisted const arrow */
256
- function hoistFnDecl(name, params, body) {
257
- const [p2, b2] = lowerArguments(params, functionBodyBlock(body))
258
- const decl = ['const', ['=', name, ['=>', p2, wrapArrowBody(b2)]]]
259
- decl._hoisted = true
260
- return decl
261
- }
262
-
263
- /** Transform a scope (module top-level or block body). Collects hoisted functions. */
264
- function transformScope(node) {
265
- if (!Array.isArray(node)) return transform(node)
266
-
267
- const [op, ...args] = node
268
-
269
- // Single named function-statement at scope position: hoist as const arrow
270
- if (op === 'function' && args[0]) return hoistFnDecl(...args)
271
- // Single statement-form class declaration: bind the factory (no hoisting — classes are TDZ)
272
- if (op === 'class' && args[0]) return ['let', ['=', args[0], lowerClass(...args)]]
273
-
274
- // Statement sequence: collect hoisted functions
275
- if (op === ';') {
276
- const hoisted = [], rest = []
277
- for (let i = 0; i < args.length; i++) {
278
- const stmt = args[i]
279
- // Statement-form named function declaration: hoist directly (skip expression handler)
280
- if (Array.isArray(stmt) && stmt[0] === 'function' && stmt[1]) {
281
- hoisted.push(hoistFnDecl(stmt[1], stmt[2], stmt[3]))
282
- continue
283
- }
284
- // Statement-form class declaration: bind the factory in place (not hoisted — TDZ)
285
- if (Array.isArray(stmt) && stmt[0] === 'class' && stmt[1]) {
286
- rest.push(['let', ['=', stmt[1], lowerClass(stmt[1], stmt[2], stmt[3])]])
287
- continue
288
- }
289
- const t = transform(stmt)
290
- if (t == null) continue
291
- // Hoist function declarations to top of scope
292
- if (Array.isArray(t) && t[0] === 'const' && t._hoisted) {
293
- hoisted.push(t)
294
- } else if (Array.isArray(t) && t[0] === ';') {
295
- // Flatten nested ; from multi-statement transforms
296
- for (const s of t.slice(1)) {
297
- if (s != null) {
298
- if (Array.isArray(s) && s[0] === 'const' && s._hoisted) hoisted.push(s)
299
- else rest.push(s)
300
- }
301
- }
302
- } else {
303
- rest.push(t)
304
- }
305
- }
306
- // Hoist functions AFTER imports (imports must be processed first for scope resolution)
307
- const imports = rest.filter(s => Array.isArray(s) && s[0] === 'import')
308
- const nonImports = rest.filter(s => !(Array.isArray(s) && s[0] === 'import'))
309
- const all = dedupeRedecls([...imports, ...hoisted, ...nonImports])
310
- return all.length === 0 ? null : all.length === 1 ? all[0] : [';', ...all]
311
- }
312
-
313
- return transform(node)
314
- }
315
-
316
- /**
317
- * Drop redundant re-declarations of the same name within one scope's statement
318
- * list. JS allows `function f(){} var f;`, `var x; var x;`, `var x = 1; var x;` —
319
- * jzify lowers `function`→`const` and `var`→`let`, which would otherwise emit two
320
- * bindings for one slot (and a typed-slot clash in codegen). The first declaration
321
- * wins; a later redeclaration keeps only its initializer, as a plain assignment.
322
- */
323
- function dedupeRedecls(stmts) {
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
327
- const seen = new Set(), out = []
328
- for (const s of stmts) {
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)
343
- }
344
- return out
345
- }
346
-
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. */
353
- function wrapArrowBody(body) {
354
- const t = transformScope(body)
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]]
366
- }
367
-
368
- /** Prototype identity check: X.prototype.Y */
369
- const isProto = n => Array.isArray(n) && n[0] === '.' && Array.isArray(n[1]) && n[1][0] === '.' && n[1][2] === 'prototype'
370
-
371
- /** `obj.M <eq> Ctor.prototype.M` is not a real reference comparison — jz has no
372
- * prototype objects — it asks whether `obj` overrides `M`, which is exactly
373
- * `obj.hasOwnProperty('M')`. Returns that call node when the shape matches
374
- * (a member access `obj.M` against a same-named prototype method), else null. */
375
- const methodOverrideHasOwn = (a, b) => {
376
- const proto = isProto(a) ? a : isProto(b) ? b : null
377
- if (!proto) return null
378
- const other = proto === a ? b : a
379
- if (!Array.isArray(other) || other[0] !== '.' || other[2] !== proto[2]) return null
380
- return ['()', ['.', transform(other[1]), 'hasOwnProperty'], [null, proto[2]]]
381
- }
382
-
383
- const TYPED_ARRAYS = new Set(['Float64Array','Float32Array','Int32Array','Uint32Array',
384
- 'Int16Array','Uint16Array','Int8Array','Uint8Array',
385
- 'ArrayBuffer','BigInt64Array','BigUint64Array','DataView'])
386
-
387
- // Block-shape ops used to detect "this `{}` is a block body, not an object literal".
388
- // Mirrors analyze.STMT_OPS — kept inline so jzify stays self-contained.
389
- const JZ_BLOCK_OPS = new Set([';', 'let', 'const', 'var', 'return', 'if', 'for', 'for-in', 'for-of',
390
- 'while', 'do', 'break', 'continue', 'switch', 'throw', 'try', 'catch', 'finally',
391
- '=', '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '>>=', '<<=', '>>>=', '||=', '&&=', '??=',
392
- '++', '--', '()', 'function', 'class', 'import', 'export', 'label'])
393
- const LABEL_BODY_OPS = new Set([';', 'if', 'for', 'for-in', 'for-of', 'while', 'do', 'switch', 'try', 'throw'])
394
-
395
- /** Statically discriminate `x instanceof Ctor` when the LHS's syntactic shape
396
- * already pins down its runtime type. Returns true/false or null (unknown).
397
- * Matches the lhs forms a user might plausibly write: literal arrays, object
398
- * literals, string/number literals, and `new C(...)` whose ctor name is known.
399
- * Stays conservative — anything else returns null and falls back to the runtime
400
- * predicate so behavior is never silently changed. */
401
- function staticInstanceofFold(val, ctor) {
402
- if (typeof ctor !== 'string' || !Array.isArray(val)) return null
403
- // Unwrap grouping parens: `(expr)` parses as `['()', expr]`
404
- if (val[0] === '()' && val.length === 2) return staticInstanceofFold(val[1], ctor)
405
- // Array literal: `[1,2,3]` → `['[]', [',', …]]` (or `['[]']` for empty array)
406
- if (val[0] === '[]' && val.length <= 2) return ctor === 'Array' || ctor === 'Object'
407
- // Object literal: `{a:1}` → `['{}', [':', …], …]`
408
- if (val[0] === '{}') return ctor === 'Object'
409
- // Regex literal: `/x/` → `['//', …]`
410
- if (val[0] === '//') return ctor === 'RegExp' || ctor === 'Object'
411
- // `new C(...)` — `['new', ['()', 'C', args]]` or `['new', 'C']`
412
- if (val[0] === 'new') {
413
- const inner = val[1]
414
- const cname = typeof inner === 'string' ? inner
415
- : (Array.isArray(inner) && inner[0] === '()' && typeof inner[1] === 'string') ? inner[1]
416
- : null
417
- if (cname) return cname === ctor || (cname !== 'Object' && ctor === 'Object')
418
- }
419
- // Bare primitive: `[null, v]` where v is a primitive — never an instance per JS spec.
420
- if (val[0] == null && val.length === 2) {
421
- const v = val[1]
422
- if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean' || v == null) return false
423
- }
424
- return null
425
- }
426
-
427
- // `arguments` lowering: regular `function` has implicit `arguments`; arrow doesn't.
428
- // jzify converts function → arrow, so any `arguments` use must be rewritten to a rest param.
429
- // Arrow functions inherit `arguments` from enclosing function — don't stop at '=>'.
430
- // Nested `function` introduces its own `arguments` — stop recursion there.
431
- let argsIdx = 0
432
- let doIdx = 0
433
-
434
- function usesArguments(node) {
435
- if (node === 'arguments') return true
436
- if (!Array.isArray(node)) return false
437
- if (node[0] === 'function') return false
438
- if (node[0] === '.' || node[0] === '?.') return usesArguments(node[1])
439
- if (node[0] === ':') return usesArguments(node[2])
440
- for (let i = 1; i < node.length; i++) if (usesArguments(node[i])) return true
441
- return false
442
- }
443
-
444
- // `arguments` is the implicit object only if the function body doesn't declare a
445
- // local of that name. Scan the body's own statement list (not nested scopes) for
446
- // `var/let/const arguments` — a regular `function` with `var arguments;` just has
447
- // an ordinary local, no arguments object.
448
- function bindsArguments(body) {
449
- const isArgDecl = s => Array.isArray(s) && (s[0] === 'var' || s[0] === 'let' || s[0] === 'const') &&
450
- s.slice(1).some(d => d === 'arguments' || (Array.isArray(d) && d[0] === '=' && d[1] === 'arguments'))
451
- let n = body
452
- if (Array.isArray(n) && n[0] === '{}') n = n[1]
453
- if (Array.isArray(n) && n[0] === ';') return n.slice(1).some(isArgDecl)
454
- return isArgDecl(n)
455
- }
456
-
457
- function renameArguments(node, to) {
458
- if (node === 'arguments') return to
459
- if (!Array.isArray(node)) return node
460
- if (node[0] === 'function') return node
461
- if (node[0] === '.' || node[0] === '?.')
462
- return [node[0], renameArguments(node[1], to), node[2]]
463
- if (node[0] === ':')
464
- return [node[0], node[1], renameArguments(node[2], to)]
465
- return node.map(n => renameArguments(n, to))
466
- }
467
-
468
- function paramList(params) {
469
- if (params == null) return []
470
- if (Array.isArray(params)) {
471
- if (params[0] === '()') {
472
- const inner = params[1]
473
- if (inner == null) return []
474
- if (Array.isArray(inner) && inner[0] === ',') return inner.slice(1)
475
- return [inner]
476
- }
477
- if (params[0] === ',') return params.slice(1)
478
- }
479
- return [params]
480
- }
481
-
482
- // Destructuring pattern as a parameter — `[a,b]` / `{a,b}` (optionally with a
483
- // default). Plain `=` defaults and `...rest` are handled natively by emit, so
484
- // they don't by themselves force lowering.
485
- const isDestructurePat = p => Array.isArray(p) && (p[0] === '[]' || p[0] === '{}' || (p[0] === '=' && isDestructurePat(p[1])))
486
-
487
- function lowerArguments(params, body) {
488
- // A function body that declares its own `arguments` local: it's an ordinary
489
- // variable, not the implicit object \u2014 rename it out of jz's reserved set,
490
- // no rest param synthesized.
491
- if (bindsArguments(body)) body = renameArguments(body, `\uE001arg${argsIdx++}`)
492
- const paramsNeedLowering = paramList(params).some(isDestructurePat)
493
- const usesArgsObj = usesArguments(params) || usesArguments(body)
494
- if (!paramsNeedLowering && !usesArgsObj) return [params, body]
495
- const name = `\uE001arg${argsIdx++}`
496
- const decls = []
497
- for (const [idx, param] of paramList(params).entries()) {
498
- if (Array.isArray(param) && param[0] === '...') {
499
- decls.push(['=', param[1], ['()', ['.', name, 'slice'], [null, idx]]])
500
- continue
501
- }
502
- if (Array.isArray(param) && param[0] === '=') {
503
- decls.push(['=', param[1], ['??', ['[]', name, [null, idx]], renameArguments(param[2], name)]])
504
- continue
505
- }
506
- decls.push(['=', param, ['[]', name, [null, idx]]])
507
- }
508
- const renamed = usesArgsObj ? renameArguments(body, name) : body
509
- return [['()', ['...', name]], decls.length ? prependParamDecls(['let', ...decls], renamed) : renamed]
510
- }
511
-
512
- function prependParamDecls(decl, body) {
513
- if (Array.isArray(body) && body[0] === '{}') {
514
- const inner = body[1]
515
- if (Array.isArray(inner) && inner[0] === ';') return ['{}', [';', decl, ...inner.slice(1)]]
516
- if (inner == null) return ['{}', decl]
517
- return ['{}', [';', decl, inner]]
518
- }
519
- if (Array.isArray(body) && (body[0] === ';' || body[0] === 'return')) return [';', decl, body]
520
- return ['{}', [';', decl, ['return', body]]]
521
- }
522
-
523
- const arrowParams = params => Array.isArray(params) && params[0] === '()' ? params : ['()', params]
524
- // A method body from jessie is a bare statement / `;`-sequence — wrap it in a
525
- // `{}` block so the `=>` handler treats it as a function body, not an expression
526
- // (an unwrapped `;`-seq arrow body produces malformed IR).
527
- const block = b => Array.isArray(b) && b[0] === '{}' ? b : ['{}', b]
528
-
529
- // === class lowering ===
530
- //
531
- // A class is lowered to a factory arrow. Instance state is a plain object;
532
- // methods are per-instance arrows capturing it (so `obj.m()` keeps working
533
- // without a separate `this` argument); `this` is renamed to that object;
534
- // `new C(a)` is already turned into `C(a)` by the `new` handler.
535
- //
536
- // class Point { x = 0; y; constructor(a,b){ this.x = a; this.y = b }
537
- // dist(){ return Math.hypot(this.x, this.y) } }
538
- // →
539
- // let Point = (a, b) => {
540
- // let selfN = { x: undefined, y: undefined,
541
- // dist: () => Math.hypot(selfN.x, selfN.y) }
542
- // selfN.x = 0 // field initializers, in declaration order
543
- // selfN.x = a // then the constructor body
544
- // selfN.y = b
545
- // return selfN
546
- // }
547
- //
548
- // Simple inheritance is lowered too: `class D extends B` builds the instance
549
- // from `B`'s factory — forwarding `super(...)` args, or the derived ctor params
550
- // when the derived constructor is implicit — then applies D's own fields and
551
- // methods over it.
552
- //
553
- // Out of scope (rejected with a clear message): full `super.foo` property
554
- // semantics, getters/setters, non-constant computed member names. Private
555
- // `#name` members are kept as the literal key string `#name` (jz allows it).
556
- let classIdx = 0
557
- let objThisIdx = 0
558
- let staticClassIdx = 0
559
- let classBaseIdx = 0
560
- const DEFAULT_DERIVED_CTOR_ARITY = 8
561
-
562
- const classBodyItems = (body) =>
563
- body == null ? [] : Array.isArray(body) && body[0] === ';' ? body.slice(1) : [body]
564
-
565
- // Rename `this` → `to`, not crossing into a nested `function`/`class` (those
566
- // rebind `this`); arrows inherit `this`, so they are crossed. Property *names*
567
- // (`obj.this`, `{this: …}` value-side only) are left alone.
568
- function renameThis(node, to) {
569
- if (node === 'this') return to
570
- if (!Array.isArray(node)) return node
571
- if (node[0] === 'function' || node[0] === 'class') return node
572
- if (node[0] === '.' || node[0] === '?.') return [node[0], renameThis(node[1], to), node[2]]
573
- if (node[0] === ':') return [node[0], node[1], renameThis(node[2], to)]
574
- return node.map(n => renameThis(n, to))
575
- }
576
-
577
- function usesThis(node) {
578
- if (node === 'this') return true
579
- if (!Array.isArray(node)) return false
580
- if (node[0] === 'function' || node[0] === 'class') return false
581
- if (node[0] === '.' || node[0] === '?.') return usesThis(node[1])
582
- if (node[0] === ':') return usesThis(node[2])
583
- return node.some(usesThis)
584
- }
585
-
586
- function hasSuperProp(node) {
587
- if (!Array.isArray(node)) return false
588
- if ((node[0] === '.' || node[0] === '?.') && node[1] === 'super') return true
589
- if (node[0] === '[]' && node[1] === 'super') return true
590
- return node.some(hasSuperProp)
591
- }
592
-
593
- function isSuperCall(node) {
594
- return Array.isArray(node) && node[0] === '()' && node[1] === 'super'
595
- }
596
-
597
- function literalStringKey(node) {
598
- return Array.isArray(node) && node[0] == null && typeof node[1] === 'string' ? node[1] : null
599
- }
600
-
601
- function constStringKey(node) {
602
- if (typeof node === 'string') return node
603
- const lit = literalStringKey(node)
604
- if (lit != null) return lit
605
- if (Array.isArray(node) && node[0] === '[]') return literalStringKey(node[1])
606
- return null
607
- }
608
-
609
- function superMethodName(callee) {
610
- if (!Array.isArray(callee)) return null
611
- if ((callee[0] === '.' || callee[0] === '?.') && callee[1] === 'super') return callee[2]
612
- if (callee[0] === '[]' && callee[1] === 'super') return literalStringKey(callee[2])
613
- return null
614
- }
615
-
616
- function collectSuperMethodCalls(node, out = new Set()) {
617
- if (!Array.isArray(node)) return out
618
- if (node[0] === 'function' || node[0] === 'class') return out
619
- if (node[0] === '()') {
620
- const name = superMethodName(node[1])
621
- if (name) out.add(name)
622
- }
623
- for (const n of node) collectSuperMethodCalls(n, out)
624
- return out
625
- }
626
-
627
- function rewriteSuperMethodCalls(node, baseMethodVars) {
628
- if (!Array.isArray(node)) return node
629
- if (node[0] === 'function' || node[0] === 'class') return node
630
- if (node[0] === '()') {
631
- const name = superMethodName(node[1])
632
- if (name) {
633
- const fn = baseMethodVars.get(name)
634
- if (!fn) jzifyError(`super.${name} is not available on the base class`)
635
- return ['()', fn, ...node.slice(2).map(n => rewriteSuperMethodCalls(n, baseMethodVars))]
636
- }
637
- }
638
- return node.map(n => rewriteSuperMethodCalls(n, baseMethodVars))
639
- }
640
-
641
- function splitCtorSuper(body) {
642
- if (body == null) return { args: null, body }
643
- if (isSuperCall(body)) return { args: body.slice(2), body: null }
644
- if (Array.isArray(body) && body[0] === '{}') {
645
- const inner = splitCtorSuper(body[1])
646
- return { args: inner.args, body: ['{}', inner.body] }
647
- }
648
- if (Array.isArray(body) && body[0] === ';') {
649
- const out = [';']
650
- let args = null
651
- for (const stmt of body.slice(1)) {
652
- if (args == null && isSuperCall(stmt)) { args = stmt.slice(2); continue }
653
- out.push(stmt)
654
- }
655
- return { args, body: out.length === 1 ? null : out.length === 2 ? out[1] : out }
656
- }
657
- return { args: null, body }
658
- }
659
-
660
- // Object shorthand methods and arrow-valued properties both parse as `=>`.
661
- // Stay conservative: only statement-shaped bodies are receiver methods here;
662
- // expression-bodied arrows keep their lexical `this` and remain unsupported.
663
- const OBJ_METHOD_BODY_OPS = new Set([';', 'return', 'if', 'for', 'for-in', 'for-of',
664
- 'while', 'do', 'switch', 'throw', 'try', 'break', 'continue'])
665
-
666
- function objectLiteralProps(args) {
667
- const raw = args.length === 1 && Array.isArray(args[0]) && args[0][0] === ',' ? args[0].slice(1) : args
668
- return raw.filter(p => p != null)
669
- }
670
-
671
- function isStatementBody(body) {
672
- return Array.isArray(body) && OBJ_METHOD_BODY_OPS.has(body[0])
673
- }
674
-
675
- function objectMethodUsesThis(prop) {
676
- if (!Array.isArray(prop) || prop[0] !== ':' || typeof prop[1] !== 'string') return false
677
- const value = prop[2]
678
- if (!Array.isArray(value)) return false
679
- if (value[0] === '=>' && isStatementBody(value[2])) return usesThis(value[2])
680
- return false
681
- }
682
-
683
- function lowerObjectLiteralThis(args) {
684
- const props = objectLiteralProps(args)
685
- if (props.length === 0 || !props.some(objectMethodUsesThis)) return null
686
- if (!props.every(p => Array.isArray(p) && p[0] === ':' && typeof p[1] === 'string')) return null
687
-
688
- const self = `obj${objThisIdx++}`
689
- const litProps = props.map(p => {
690
- const value = p[2]
691
- if (objectMethodUsesThis(p)) {
692
- return [':', p[1], transform(['=>', value[1], block(renameThis(value[2], self))])]
693
- }
694
- return [':', p[1], transform(value)]
695
- })
696
- const lit = ['{}', litProps.length === 1 ? litProps[0] : [',', ...litProps]]
697
- return ['()', ['()', ['=>', null, ['{}', [';',
698
- ['let', ['=', self, lit]],
699
- ['return', self]
700
- ]]]], null]
701
- }
702
-
703
- function jzifyError(msg) { throw new Error(`jzify: ${msg}`) }
704
-
705
- function lowerClass(name, heritage, body) {
706
- let ctorParams = null, ctorBody = null
707
- const methods = [], fields = [], statics = []
708
- for (const it of classBodyItems(body)) {
709
- if (typeof it === 'string') { fields.push([it, null]); continue } // bare `x;`
710
- if (!Array.isArray(it)) continue
711
- const bareFieldName = constStringKey(it)
712
- if (bareFieldName != null) { fields.push([bareFieldName, null]); continue }
713
- if (it[0] === ':' && Array.isArray(it[2]) && it[2][0] === '=>') {
714
- const key = constStringKey(it[1])
715
- if (key == null) jzifyError('non-constant computed class member names are not supported')
716
- if (key === 'constructor') { ctorParams = it[2][1]; ctorBody = it[2][2] }
717
- else methods.push([key, it[2][1], it[2][2]])
718
- continue
719
- }
720
- if (it[0] === '=') {
721
- const lhs = it[1]
722
- if (Array.isArray(lhs) && lhs[0] === 'static') {
723
- const key = constStringKey(lhs[1])
724
- if (key == null) jzifyError('non-constant computed static class fields are not supported')
725
- statics.push([key, it[2]])
726
- continue
727
- }
728
- const key = constStringKey(lhs)
729
- if (key == null) jzifyError('non-constant computed/destructured class fields are not supported')
730
- fields.push([key, it[2]])
731
- continue
732
- }
733
- if (it[0] === 'static') {
734
- const key = constStringKey(it[1])
735
- if (key != null) {
736
- statics.push([key, null])
737
- continue
738
- }
739
- }
740
- if (it[0] === 'static' && typeof it[1] === 'string') {
741
- statics.push([it[1], null])
742
- continue
743
- }
744
- if (it[0] === 'static' && Array.isArray(it[1]) && it[1][0] === ':' && Array.isArray(it[1][2]) && it[1][2][0] === '=>') {
745
- const key = constStringKey(it[1][1])
746
- if (key == null) jzifyError('non-constant computed static class member names are not supported')
747
- statics.push([key, it[1][2], true])
748
- continue
749
- }
750
- if (it[0] === 'get' || it[0] === 'set') jzifyError('class getters/setters are not supported — jz objects have no accessors')
751
- if (it[0] === 'static') jzifyError('`static` class members are not supported yet')
752
- jzifyError(`unsupported class member ${JSON.stringify(it).slice(0, 60)}`)
753
- }
754
- const superMethods = heritage == null ? new Set() : new Set([
755
- ...collectSuperMethodCalls(ctorBody),
756
- ...fields.flatMap(([, init]) => init == null ? [] : [...collectSuperMethodCalls(init)]),
757
- ...methods.flatMap(([, , mbody]) => [...collectSuperMethodCalls(mbody)])
758
- ])
759
- if (heritage != null) {
760
- const dummySuperVars = new Map([...superMethods].map((k, i) => [k, `super_${i}`]))
761
- const unsupportedSuperProp = node => node != null && hasSuperProp(rewriteSuperMethodCalls(node, dummySuperVars))
762
- if (
763
- unsupportedSuperProp(ctorBody) ||
764
- fields.some(([, init]) => unsupportedSuperProp(init)) ||
765
- methods.some(([, , mbody]) => unsupportedSuperProp(mbody))
766
- )
767
- jzifyError('`super` property access is not supported yet')
768
- }
769
- const self = `self${classIdx++}`
770
- const UNDEF = [] // jessie's node for `undefined`
771
- // Object literal: every declared field (its initializer inline when it doesn't
772
- // touch `this`, else `undefined` and assigned below), every method as its
773
- // self-capturing arrow. Declaring all fields up front fixes the object shape.
774
- const litProps = [], deferred = []
775
- for (const [fname, init] of fields) {
776
- if (init != null && !usesThis(init)) litProps.push([':', fname, transform(init)])
777
- else { litProps.push([':', fname, UNDEF]); if (init != null) deferred.push([fname, init]) }
778
- }
779
- for (const [mname, mparams, mbody] of methods)
780
- litProps.push([':', mname, transform(['=>', mparams ?? ['()', null], block(renameThis(mbody, self))])])
781
- const lit = ['{}', litProps.length === 0 ? null : litProps.length === 1 ? litProps[0] : [',', ...litProps]]
782
- let params = ctorParams ?? ['()', null]
783
- const dynamicBase = heritage != null && typeof heritage !== 'string'
784
- const baseRef = heritage == null ? null : dynamicBase ? `base${classBaseIdx++}` : heritage
785
- const stmts = []
786
- if (heritage != null) {
787
- const split = splitCtorSuper(ctorBody)
788
- ctorBody = split.body
789
- const defaultArgs = ctorParams == null
790
- ? Array.from({ length: DEFAULT_DERIVED_CTOR_ARITY }, (_, i) => `superArg${classIdx}_${i}`)
791
- : null
792
- const baseArgs = split.args ?? (defaultArgs ? [defaultArgs.length === 1 ? defaultArgs[0] : [',', ...defaultArgs]] : paramList(ctorParams))
793
- stmts.push(['let', ['=', self, ['()', baseRef, ...baseArgs.map(transform)]]])
794
- const superMethodVars = new Map()
795
- let superIdx = 0
796
- for (const mname of superMethods) {
797
- const v = `super${classIdx}_${superIdx++}`
798
- superMethodVars.set(mname, v)
799
- stmts.push(['let', ['=', v, ['.', self, mname]]])
800
- }
801
- for (const [fname, init] of fields)
802
- stmts.push(['=', ['.', self, fname], init != null ? transform(renameThis(rewriteSuperMethodCalls(init, superMethodVars), self)) : UNDEF])
803
- for (const [mname, mparams, mbody] of methods)
804
- stmts.push(['=', ['.', self, mname], transform(['=>', mparams ?? ['()', null], block(renameThis(rewriteSuperMethodCalls(mbody, superMethodVars), self))])])
805
- ctorBody = rewriteSuperMethodCalls(ctorBody, superMethodVars)
806
- if (defaultArgs) params = ['()', defaultArgs.length === 1 ? defaultArgs[0] : [',', ...defaultArgs]]
807
- } else {
808
- stmts.push(['let', ['=', self, lit]])
809
- }
810
- // `this`-dependent field initializers run, in declaration order, before the ctor.
811
- if (heritage == null) {
812
- for (const [fname, init] of deferred)
813
- stmts.push(['=', ['.', self, fname], transform(renameThis(init, self))])
814
- }
815
- if (ctorBody != null) {
816
- let cb = transform(renameThis(ctorBody, self))
817
- if (Array.isArray(cb) && cb[0] === '{}') cb = cb[1]
818
- if (Array.isArray(cb) && cb[0] === ';') stmts.push(...cb.slice(1).filter(s => s != null))
819
- else if (cb != null) stmts.push(cb)
820
- }
821
- stmts.push(['return', self])
822
- const factory = ['=>', arrowParams(params), ['{}', [';', ...stmts]]]
823
- if (!dynamicBase && statics.length === 0) return factory
824
-
825
- const cls = name || `class${staticClassIdx++}`
826
- const staticStmts = []
827
- if (dynamicBase) staticStmts.push(['let', ['=', baseRef, transform(heritage)]])
828
- staticStmts.push(['let', ['=', cls, factory]])
829
- for (const [sname, value, isMethod] of statics) {
830
- const rhs = isMethod
831
- ? transform(['=>', value[1], block(renameThis(value[2], cls))])
832
- : value == null ? UNDEF : transform(renameThis(value, cls))
833
- staticStmts.push(['=', ['.', cls, sname], rhs])
834
- }
835
- staticStmts.push(['return', cls])
836
- return ['()', ['()', ['=>', null, ['{}', [';', ...staticStmts]]]], null]
837
- }
838
-
839
- // Array(a, b, …) / new Array(a, b, …) → array literal [a, b, …]; Array() → [].
840
- // The single-argument Array(n) is a length constructor (n holes), not a
841
- // literal — return null there so the caller keeps it as a constructor call.
842
- function lowerArrayConstructor(arg) {
843
- if (arg == null) return ['[]', null]
844
- if (Array.isArray(arg) && arg[0] === ',' && arg.length > 2)
845
- return ['[]', [',', ...arg.slice(1).map(transform)]]
846
- return null
847
- }
848
-
849
- const handlers = {
850
- // Named IIFE: (function name(p){b})(a) → let name = arrow; name(a)
851
- '()'(callee, ...rest) {
852
- if (callee === 'Array') {
853
- const lit = lowerArrayConstructor(rest[0])
854
- if (lit) return lit
855
- }
856
- if (Array.isArray(callee) && callee[0] === '()' && Array.isArray(callee[1]) && callee[1][0] === 'function' && callee[1][1]) {
857
- const [, name, params, body] = callee[1]
858
- const [p2, b2] = lowerArguments(params, functionBodyBlock(body))
859
- return [';', ['let', ['=', name, ['=>', arrowParams(p2), wrapArrowBody(b2)]]], ['()', name, ...rest.map(transform)]]
860
- }
861
- },
862
-
863
- // function → arrow. Named function expression desugars to IIFE so the name is
864
- // bound inside body per ES spec: `function f(){...f...}` → `(()=>{let f;f=arrow;return f})()`.
865
- // Statement-form named functions are hoisted by transformScope before reaching here.
866
- 'function'(name, params, body) {
867
- const [p2, b2] = lowerArguments(params, functionBodyBlock(body))
868
- const arrow = ['=>', p2, wrapArrowBody(b2)]
869
- if (name) {
870
- return ['()', ['()', ['=>', null, ['{}', [';',
871
- ['let', name],
872
- ['=', name, arrow],
873
- ['return', name]
874
- ]]]], null]
875
- }
876
- return arrow
877
- },
878
-
879
- '=>'(params, body) {
880
- // The subscript parser elides the `[';', stmt, null]` wrapper inside an
881
- // arrow's `{` block when the block holds a single statement — the result
882
- // `['{}', stmt]` is syntactically identical to an object-literal shape, and
883
- // downstream `{}` handlers can no longer tell them apart. JS grammar makes
884
- // it unambiguous: `=>` followed by `{` is always a block (use `=> ({...})`
885
- // for an object return), so coerce every single-statement body to the
886
- // canonical `['{}', [';', stmt]]` block form — `;`-wrapped, never bare.
887
- let b = body
888
- if (Array.isArray(b) && b[0] === '{}' && b.length === 2) {
889
- const inner = b[1]
890
- if (inner != null && !(Array.isArray(inner) && inner[0] === ';')) {
891
- b = ['{}', [';', inner]]
892
- }
893
- }
894
- const [p2, b2] = lowerArguments(params, b)
895
- return ['=>', p2, transform(b2)]
896
- },
897
-
898
- // Class in expression position → its factory arrow. (A named class
899
- // expression's own inner binding is dropped — rare; statement-form
900
- // `class C {}` is handled by transformScope, which keeps the binding.)
901
- 'class'(name, heritage, body) { return lowerClass(name, heritage, body) },
902
-
903
- // `var` is hoisted away before transform reaches here. If one slips through
904
- // (e.g. raw subscript output without going via jzify entry/wrapArrowBody),
905
- // fall back to treating it as `let`.
906
- 'var'(...args) {
907
- return ['let', ...args.map(transform)]
908
- },
909
-
910
- ':'(label, body) {
911
- if (typeof label === 'string' && Array.isArray(body) && LABEL_BODY_OPS.has(body[0]))
912
- return ['label', label, transform(body)]
913
- },
914
-
915
- '='(lhs, rhs) {
916
- if (isDestructurePat(lhs)) return ['=', transformPattern(lhs), transform(rhs)]
917
- },
918
-
919
- 'switch'(disc, ...cases) {
920
- const clean = cases.map(c => {
921
- if (c[0] === 'case') return ['case', c[1], normalizeCaseBody(c[2])]
922
- if (c[0] === 'default') return ['default', normalizeCaseBody(c[1])]
923
- return c
924
- })
925
- return transformSwitch(disc, clean)
926
- },
927
-
928
- // Equality keeps the JS loose/strict distinction (jz core now lowers both):
929
- // `==`/`!=` stay loose, `===`/`!==` stay strict. A comparison against a prototype
930
- // object folds to a boolean — jz has no prototype objects, so identity against one
931
- // is decided statically. The one exception is `obj.M <eq> Ctor.prototype.M`: that
932
- // probes whether `obj` overrides the builtin `M`, so it lowers to a runtime
933
- // `obj.hasOwnProperty('M')` (equal ⇒ not overridden, unequal ⇒ overridden).
934
- '=='(a, b) { const own = methodOverrideHasOwn(a, b); if (own) return ['!', own]; return isProto(a) || isProto(b) ? 1 : ['==', transform(a), transform(b)] },
935
- '!='(a, b) { const own = methodOverrideHasOwn(a, b); if (own) return own; return isProto(a) || isProto(b) ? 0 : ['!=', transform(a), transform(b)] },
936
- '==='(a, b) { const own = methodOverrideHasOwn(a, b); if (own) return ['!', own]; if (isProto(a) || isProto(b)) return 1 },
937
- '!=='(a, b) { const own = methodOverrideHasOwn(a, b); if (own) return own; if (isProto(a) || isProto(b)) return 0 },
938
-
939
- // new → call (keep TypedArrays)
940
- 'new'(ctor, ...cargs) {
941
- if (Array.isArray(ctor) && ctor[0] === '()' && ctor[1] === 'Array') {
942
- const lit = lowerArrayConstructor(ctor[2])
943
- if (lit) return lit
944
- }
945
- const name = typeof ctor === 'string' ? ctor : (Array.isArray(ctor) && ctor[0] === '()' ? ctor[1] : null)
946
- if (typeof name === 'string' && (TYPED_ARRAYS.has(name) || name === 'Array' || name === 'RegExp')) return ['new', transform(ctor), ...cargs.map(transform)]
947
- if (Array.isArray(ctor) && ctor[0] === '()') return transform(ctor)
948
- // `new C(a)` → `C(a)`; `new C` (no parens) → `C()` — a 2-element `['()', X]`
949
- // is grouping parens, so a no-arg call needs the explicit `null` arg slot.
950
- return ['()', transform(ctor), ...(cargs.length ? cargs.map(transform) : [null])]
951
- },
952
-
953
- // instanceof → typeof / Array.isArray / __is_* helpers. jzify lets us preserve
954
- // constructor identity that strict-mode `instanceof` discards — Map, Set, and
955
- // TypedArrays get dedicated typed predicates (__is_map / __is_set / __is_typed)
956
- // that both compile to a runtime __ptr_type check and feed extractRefinements
957
- // for downstream method-dispatch elision (e.g. `if (x instanceof Map) x.has(k)`
958
- // resolves to __map_has, not the default __set_has fallback). Date / RegExp /
959
- // Object stay on the weak typeof-object lowering — they share PTR.OBJECT and
960
- // the JS runtime offers no cheaper discrimination.
961
- 'instanceof'(val, ctor) {
962
- const t = transform(val)
963
- const name = typeof ctor === 'string' ? ctor : (Array.isArray(ctor) && ctor[0] === '()' ? ctor[1] : null)
964
- // Static fold: literal shape of LHS already discriminates against the constructor.
965
- const fold = staticInstanceofFold(val, name)
966
- if (fold != null) return [null, fold]
967
- if (name === 'Array') return ['()', ['.', 'Array', 'isArray'], t]
968
- if (name === 'Map') return ['()', '__is_map', t]
969
- if (name === 'Set') return ['()', '__is_set', t]
970
- if (typeof name === 'string' && TYPED_ARRAYS.has(name) && name !== 'ArrayBuffer' && name !== 'DataView')
971
- return ['()', '__is_typed', t]
972
- // Object / ArrayBuffer / DataView / RegExp / Date / unknown: weak typeof-object check.
973
- return ['===', ['typeof', t], [null, 'object']]
974
- },
975
-
976
- // do { body } while (cond) → let _once = true; while (_once || cond) { _once = false; body }
977
- // Avoids body duplication and preserves continue: `continue` jumps back to the
978
- // while condition after the one-shot flag has been cleared.
979
- 'do'(body, cond) {
980
- const flag = `do${doIdx++}`
981
- return [';',
982
- ['let', ['=', flag, [null, true]]],
983
- ['while', ['||', flag, transform(cond)], ['{}', [';', ['=', flag, [null, false]], transform(body)]]]]
984
- },
985
-
986
- // Block body: recurse as scope for hoisting. transformScope reduces a single-statement
987
- // sequence to its bare element; if the input WAS block-shaped (`;` list or a single
988
- // statement op), we re-wrap the collapsed result in `[';', ...]` so prepare.js keeps
989
- // routing the `{}` through the block branch instead of mistaking it for `{ expr }`.
990
- '{}'(...args) {
991
- const loweredObject = lowerObjectLiteralThis(args)
992
- if (loweredObject) return loweredObject
993
-
994
- return ['{}', ...args.map((a, i) => {
995
- const t = transformScope(a) ?? a
996
- if (i !== 0 || a == null) return t
997
- const blockIn = Array.isArray(a) && JZ_BLOCK_OPS.has(a[0])
998
- if (!blockIn || t == null) return t
999
- // transformScope collapses a single-statement `;`-list to its bare element;
1000
- // re-wrap so the block always stays `['{}', [';', ...]]`. The `;` is the
1001
- // only reliable block marker — a bare statement op can desugar to an
1002
- // expression op downstream (postfix `c++` → `['-', ...]`) and lose its
1003
- // block identity, leaving `['{}', expr]` mistakable for an object literal.
1004
- return Array.isArray(t) && t[0] === ';' ? t : [';', t]
1005
- })]
1006
- },
1007
-
1008
- // Export: recurse into exported declaration. Statement-form `export function name`
1009
- // and `export default function name` must be hoisted as const-arrows — otherwise
1010
- // the generic `function` handler wraps them in a named-IIFE (correct for *expressions*,
1011
- // wrong for declarations), producing `export ['()', IIFE]` which has no exportable binding.
1012
- 'export'(inner) {
1013
- if (Array.isArray(inner) && inner[0] === 'function' && inner[1]) {
1014
- return ['export', hoistFnDecl(inner[1], inner[2], inner[3])]
1015
- }
1016
- // `export class C {}` → `export let C = factory`; named class keeps its binding.
1017
- if (Array.isArray(inner) && inner[0] === 'class' && inner[1]) {
1018
- return ['export', ['let', ['=', inner[1], lowerClass(inner[1], inner[2], inner[3])]]]
1019
- }
1020
- if (Array.isArray(inner) && inner[0] === 'default' && Array.isArray(inner[1]) && inner[1][0] === 'function' && inner[1][1]) {
1021
- const decl = hoistFnDecl(inner[1][1], inner[1][2], inner[1][3])
1022
- return [';', decl, ['export', ['default', inner[1][1]]]]
1023
- }
1024
- if (Array.isArray(inner) && inner[0] === 'default' && Array.isArray(inner[1]) && inner[1][0] === 'class' && inner[1][1]) {
1025
- return [';', ['let', ['=', inner[1][1], lowerClass(inner[1][1], inner[1][2], inner[1][3])]], ['export', ['default', inner[1][1]]]]
1026
- }
1027
- return ['export', transform(inner)]
1028
- },
1029
- }
1030
-
1031
- /** Transform a single AST node recursively. */
1032
- function transform(node) {
1033
- if (node == null || typeof node !== 'object' || !Array.isArray(node)) return node
1034
- const [op, ...args] = node
1035
- if (op == null) return node
1036
- const h = handlers[op]
1037
- // A handler that returns nullish (including no `return`) means "no rewrite at
1038
- // this node" — fall through to a generic recurse. `??` (not `||`) so handlers
1039
- // like `'==='` can legitimately return `0`.
1040
- return (h && h(...args)) ?? [op, ...args.map(transform)]
1041
- }
1042
-
1043
- // Esbuild emits a small ESM helper:
1044
- //
1045
- // var __defProp = Object.defineProperty;
1046
- // var __export = (target, all) => {
1047
- // for (var name in all)
1048
- // __defProp(target, name, { get: all[name], enumerable: true });
1049
- // };
1050
- // __export(src_exports, { default: () => value });
1051
- // use(src_exports.default);
1052
- //
1053
- // Full descriptor/prototype semantics are outside JZ's fixed-shape object model.
1054
- // This pass instead recognizes the static helper pattern and rewrites reads of
1055
- // the synthetic export object to the real binding.
1056
- function foldStaticExportHelpers(ast) {
1057
- const body = astSeq(ast)
1058
- if (!body) return ast
1059
-
1060
- const defPropAliases = new Set()
1061
- for (const stmt of body) {
1062
- const b = bindingOf(stmt)
1063
- if (b && isObjectDefineProperty(b[1])) defPropAliases.add(b[0])
1064
- }
1065
- if (!defPropAliases.size) return ast
1066
-
1067
- const helperNames = new Set()
1068
- for (const stmt of body) {
1069
- const b = bindingOf(stmt)
1070
- if (b && Array.isArray(b[1]) && b[1][0] === '=>' && containsDefinePropertyCall(b[1], defPropAliases))
1071
- helperNames.add(b[0])
1072
- }
1073
- if (!helperNames.size) return ast
1074
-
1075
- const rewrites = new Map()
1076
- const removable = new Set()
1077
- for (const stmt of body) {
1078
- const ex = staticExportCall(stmt, helperNames)
1079
- if (!ex) continue
1080
- for (const [key, value] of ex.props) rewrites.set(`${ex.target}.${key}`, value)
1081
- removable.add(stmt)
1082
- }
1083
- if (!rewrites.size) return ast
1084
-
1085
- const rewritten = body
1086
- .filter(stmt => !removable.has(stmt) && !isDefPropAliasAssign(stmt, defPropAliases) && !isExportHelperAssign(stmt, helperNames))
1087
- .map(stmt => replaceStaticExportReads(stmt, rewrites))
1088
- return rewritten.length === 0 ? null : rewritten.length === 1 ? rewritten[0] : [';', ...rewritten]
1089
- }
1090
-
1091
- // Esbuild's CommonJS/ESM interop helpers alias Object reflection built-ins into
1092
- // locals (`var __create = Object.create`, `var __getOwnPropNames =
1093
- // Object.getOwnPropertyNames`, ...). jz deliberately does not expose those
1094
- // built-ins as first-class function values, but the helpers are static enough to
1095
- // lower back to the supported direct calls and module reads.
1096
- function foldStaticBundlerHelpers(ast) {
1097
- const body = astSeq(ast)
1098
- if (!body) return ast
1099
- const binds = body.map(bindingOf) // [name, init] | null, index-aligned with body
1100
-
1101
- // Local aliases of Object reflection built-ins: name -> canonical built-in.
1102
- // esbuild's interop preamble always emits these (`var __defProp =
1103
- // Object.defineProperty`, ...); their absence proves the input is not a
1104
- // bundle, so the fold stays a strict no-op rather than guessing.
1105
- const aliases = new Map()
1106
- for (const b of binds) {
1107
- const key = b && objectBuiltinKey(b[1])
1108
- if (key) aliases.set(b[0], key)
1109
- }
1110
- if (!aliases.size) return ast
1111
-
1112
- // __copyProps: an arrow driving both aliased getOwnPropertyNames + defineProperty.
1113
- const copyHelpers = new Set()
1114
- for (const b of binds)
1115
- if (b && isArrow(b[1]) &&
1116
- containsCall(b[1], c => aliases.get(c) === 'Object.getOwnPropertyNames') &&
1117
- containsCall(b[1], c => aliases.get(c) === 'Object.defineProperty'))
1118
- copyHelpers.add(b[0])
1119
-
1120
- // __toESM: an arrow cloning a module behind a prototype, tagging default/__esModule.
1121
- const interopHelpers = new Set()
1122
- for (const b of binds)
1123
- if (b && isArrow(b[1]) &&
1124
- containsCall(b[1], c => aliases.get(c) === 'Object.create') &&
1125
- containsCall(b[1], c => aliases.get(c) === 'Object.getPrototypeOf') &&
1126
- containsCall(b[1], c => copyHelpers.has(c)) &&
1127
- astSome(b[1], n => n === 'default') && astSome(b[1], n => n === '__esModule'))
1128
- interopHelpers.add(b[0])
1129
-
1130
- // Bindings produced by an interop-helper call: name -> wrapped module expression.
1131
- const interopBindings = new Map()
1132
- for (const b of binds)
1133
- if (b && Array.isArray(b[1]) && b[1][0] === '()' && interopHelpers.has(b[1][1])) {
1134
- const args = callArgs(b[1].slice(2))
1135
- if (args.length) interopBindings.set(b[0], args[0])
1136
- }
1137
-
1138
- let out = body.map(stmt => rewriteBundlerAliases(stmt, aliases, interopBindings))
1139
- if (interopBindings.size) out = out.map(stmt => replaceInteropReads(stmt, interopBindings))
1140
- out = out.map(stmt => rewriteBundlerAliases(stmt, aliases, interopBindings)).filter(s => s != null)
1141
-
1142
- // Drop synthetic alias/helper bindings nothing references after rewriting.
1143
- const synthetic = n => aliases.has(n) || copyHelpers.has(n) || interopHelpers.has(n) || interopBindings.has(n)
1144
- const live = new Set()
1145
- for (const stmt of out) {
1146
- const b = bindingOf(stmt)
1147
- if (!(b && synthetic(b[0]))) collectRefs(stmt, live)
1148
- }
1149
- out = out.filter(stmt => {
1150
- const b = bindingOf(stmt)
1151
- return !(b && synthetic(b[0]) && !live.has(b[0]))
1152
- })
1153
-
1154
- return out.length === 0 ? null : out.length === 1 ? out[0] : [';', ...out]
1155
- }
1156
-
1157
- const isArrow = node => Array.isArray(node) && node[0] === '=>'
1158
-
1159
- const OBJECT_BUILTINS = new Set(['create', 'getPrototypeOf', 'getOwnPropertyNames', 'getOwnPropertyDescriptor', 'defineProperty'])
1160
-
1161
- // Canonical name of the Object reflection built-in `node` references, or null.
1162
- function objectBuiltinKey(node) {
1163
- if (!Array.isArray(node) || node[0] !== '.') return null
1164
- if (node[1] === 'Object' && OBJECT_BUILTINS.has(node[2])) return 'Object.' + node[2]
1165
- return isObjectHasOwnPropertyRef(node) ? 'Object.prototype.hasOwnProperty' : null
1166
- }
1167
-
1168
- // Deep `some` over AST children (skips the op slot, so op names never match).
1169
- function astSome(node, pred) {
1170
- if (pred(node)) return true
1171
- if (!Array.isArray(node)) return false
1172
- for (let i = 1; i < node.length; i++) if (astSome(node[i], pred)) return true
1173
- return false
1174
- }
1175
-
1176
- // Does `node` contain a `()` call whose string callee satisfies `ok`?
1177
- const containsCall = (node, ok) =>
1178
- astSome(node, n => Array.isArray(n) && n[0] === '()' && typeof n[1] === 'string' && ok(n[1]))
1179
-
1180
- function rewriteBundlerAliases(node, aliases, interopBindings) {
1181
- if (!Array.isArray(node)) return node
1182
- const rec = n => rewriteBundlerAliases(n, aliases, interopBindings)
1183
-
1184
- if (node[0] === ';') {
1185
- const out = [';']
1186
- for (let i = 1; i < node.length; i++) {
1187
- const child = rec(node[i])
1188
- if (child != null) out.push(child)
1189
- }
1190
- return out.length === 1 ? null : out.length === 2 ? out[1] : out
1191
- }
1192
- if (node[0] === '{}' && node.length === 2) {
1193
- const wasBlock = Array.isArray(node[1]) && JZ_BLOCK_OPS.has(node[1][0])
1194
- const inner = rec(node[1])
1195
- if (!wasBlock || inner == null) return ['{}', inner]
1196
- const stayed = Array.isArray(inner) && JZ_BLOCK_OPS.has(inner[0])
1197
- return ['{}', stayed ? inner : [';', inner]]
1198
- }
1199
-
1200
- if (node[0] === '()') {
1201
- const callee = node[1]
1202
- const args = callArgs(node.slice(2))
1203
-
1204
- if (typeof callee === 'string') {
1205
- const key = aliases.get(callee)
1206
- if (key === 'Object.defineProperty') {
1207
- const define = staticDefineProperty(args)
1208
- if (define !== undefined) return define
1209
- }
1210
- if (key === 'Object.getOwnPropertyNames' || key === 'Object.create') {
1211
- if (key === 'Object.create' && isGetPrototypeOfCall(args[0], aliases)) return ['{}', null]
1212
- return ['()', key, ...args.map(rec)]
1213
- }
1214
- }
1215
- // `__hasOwnProp.call(o, k)` -> `o.hasOwnProperty(k)`.
1216
- if (Array.isArray(callee) && callee[0] === '.' && callee[2] === 'call' && args.length >= 2 &&
1217
- typeof callee[1] === 'string' && aliases.get(callee[1]) === 'Object.prototype.hasOwnProperty')
1218
- return ['()', ['.', rec(args[0]), 'hasOwnProperty'], rec(args[1])]
1219
- // `(0, fn)(...)` comma-call resolving to an interop module read.
1220
- const seqCall = commaZeroCall(callee, interopBindings)
1221
- if (seqCall) return ['()', seqCall, ...args.map(rec)]
1222
- }
1223
-
1224
- if (node[0] === '.' || node[0] === '?.') return [node[0], rec(node[1]), node[2]]
1225
- if (node[0] === ':') return [node[0], node[1], rec(node[2])]
1226
- return node.map((part, i) => i === 0 ? part : rec(part))
1227
- }
1228
-
1229
- function replaceInteropReads(node, bindings) {
1230
- if (typeof node === 'string' && bindings.has(node)) return cloneAst(bindings.get(node))
1231
- if (!Array.isArray(node)) return node
1232
- if (node[0] === '=' && typeof node[1] === 'string') return ['=', node[1], replaceInteropReads(node[2], bindings)]
1233
- if (node[0] === 'let' || node[0] === 'const' || node[0] === 'var')
1234
- return [node[0], ...node.slice(1).map(decl =>
1235
- Array.isArray(decl) && decl[0] === '=' ? ['=', decl[1], replaceInteropReads(decl[2], bindings)] : decl)]
1236
- if ((node[0] === '.' || node[0] === '?.') && typeof node[1] === 'string' && typeof node[2] === 'string' && bindings.has(node[1])) {
1237
- const mod = cloneAst(bindings.get(node[1]))
1238
- return node[2] === 'default' ? mod : [node[0], mod, node[2]]
1239
- }
1240
- if (node[0] === ':') return [node[0], node[1], replaceInteropReads(node[2], bindings)]
1241
- return node.map((part, i) => i === 0 ? part : replaceInteropReads(part, bindings))
1242
- }
1243
-
1244
- function isGetPrototypeOfCall(node, aliases) {
1245
- if (!Array.isArray(node) || node[0] !== '()') return false
1246
- const callee = node[1]
1247
- return (typeof callee === 'string' && aliases.get(callee) === 'Object.getPrototypeOf') ||
1248
- objectBuiltinKey(callee) === 'Object.getPrototypeOf'
1249
- }
1250
-
1251
- function commaZeroCall(callee, bindings) {
1252
- if (!Array.isArray(callee) || callee[0] !== '()' || !Array.isArray(callee[1]) || callee[1][0] !== ',') return null
1253
- const parts = callee[1].slice(1)
1254
- if (parts.length !== 2 || !isZeroLiteral(parts[0])) return null
1255
- const fn = replaceInteropReads(parts[1], bindings)
1256
- return fn === parts[1] ? null : fn
1257
- }
1258
-
1259
- // `defProp(obj, "key", descriptor)` -> `obj.key = value`; null drops `__esModule`.
1260
- function staticDefineProperty(args) {
1261
- if (args.length < 3) return undefined
1262
- const [obj, keyExpr, desc] = args
1263
- const key = stringLiteral(keyExpr)
1264
- const props = objectProps(desc)
1265
- if (typeof key !== 'string' || !props) return undefined
1266
- if (key === '__esModule') return null
1267
- const prop = name => props.find(p => Array.isArray(p) && p[0] === ':' && p[1] === name)?.[2]
1268
- const value = prop('value')
1269
- if (value !== undefined) return ['=', ['.', obj, key], value]
1270
- const got = getterReturnExpr(prop('get'))
1271
- return got !== null ? ['=', ['.', obj, key], got] : undefined
1272
- }
1273
-
1274
- function stringLiteral(node) {
1275
- return Array.isArray(node) && node[0] == null && typeof node[1] === 'string' ? node[1] : null
1276
- }
1277
-
1278
- // Identifier references in `node`, excluding declaration names, member property
1279
- // names and object keys — enough to decide whether a synthetic binding is live.
1280
- function collectRefs(node, out) {
1281
- if (typeof node === 'string') return void out.add(node)
1282
- if (!Array.isArray(node)) return
1283
- if (node[0] === 'let' || node[0] === 'const' || node[0] === 'var') {
1284
- for (let i = 1; i < node.length; i++)
1285
- if (Array.isArray(node[i]) && node[i][0] === '=') collectRefs(node[i][2], out)
1286
- } else if ((node[0] === '.' || node[0] === '?.') && typeof node[2] === 'string') {
1287
- collectRefs(node[1], out)
1288
- } else if (node[0] === ':') {
1289
- collectRefs(node[2], out)
1290
- } else {
1291
- for (let i = 1; i < node.length; i++) collectRefs(node[i], out)
1292
- }
1293
- }
1294
-
1295
- function astSeq(ast) {
1296
- if (!Array.isArray(ast)) return null
1297
- return ast[0] === ';' ? ast.slice(1).filter(Boolean) : [ast]
1298
- }
1299
-
1300
- function isObjectDefineProperty(node) {
1301
- return Array.isArray(node) && node[0] === '.' && node[1] === 'Object' && node[2] === 'defineProperty'
1302
- }
1303
-
1304
- /** Unwrap an esbuild module binding to `[name, init]`. After hoistVars, a binding
1305
- * reaches this pass either split into a bare `['=', name, init]` (RHS hoisted out
1306
- * as a separate `let name;`) or kept as a single `['let', ['=', name, init]]` decl
1307
- * (arrow RHS — see the `;` handler in hoistVars). The fold keys on name/init,
1308
- * so it must see through both shapes. */
1309
- function bindingOf(stmt) {
1310
- if (!Array.isArray(stmt)) return null
1311
- if (stmt[0] === '=' && typeof stmt[1] === 'string') return [stmt[1], stmt[2]]
1312
- if ((stmt[0] === 'let' || stmt[0] === 'const' || stmt[0] === 'var') && stmt.length === 2 &&
1313
- Array.isArray(stmt[1]) && stmt[1][0] === '=' && typeof stmt[1][1] === 'string')
1314
- return [stmt[1][1], stmt[1][2]]
1315
- return null
1316
- }
1317
-
1318
- function isDefPropAliasAssign(stmt, aliases) {
1319
- const b = bindingOf(stmt)
1320
- return b != null && aliases.has(b[0]) && isObjectDefineProperty(b[1])
1321
- }
1322
-
1323
- function isExportHelperAssign(stmt, helpers) {
1324
- const b = bindingOf(stmt)
1325
- return b != null && helpers.has(b[0])
1326
- }
1327
-
1328
- function containsDefinePropertyCall(node, aliases) {
1329
- if (!Array.isArray(node)) return false
1330
- if (node[0] === '()' && (aliases.has(node[1]) || isObjectDefineProperty(node[1]))) return true
1331
- for (let i = 1; i < node.length; i++) if (containsDefinePropertyCall(node[i], aliases)) return true
1332
- return false
1333
- }
1334
-
1335
- function staticExportCall(stmt, helpers) {
1336
- if (!Array.isArray(stmt) || stmt[0] !== '()' || !helpers.has(stmt[1])) return null
1337
- const args = callArgs(stmt.slice(2))
1338
- if (args.length !== 2 || typeof args[0] !== 'string') return null
1339
- const props = objectProps(args[1])
1340
- if (!props) return null
1341
- const out = []
1342
- for (const prop of props) {
1343
- if (!Array.isArray(prop) || prop[0] !== ':' || typeof prop[1] !== 'string') return null
1344
- const value = getterReturnExpr(prop[2])
1345
- if (!value) return null
1346
- out.push([prop[1], value])
1347
- }
1348
- return { target: args[0], props: out }
1349
- }
1350
-
1351
- function callArgs(args) {
1352
- if (args.length === 1 && Array.isArray(args[0]) && args[0][0] === ',') return args[0].slice(1)
1353
- return args.filter(a => a != null)
1354
- }
1355
-
1356
- function objectProps(node) {
1357
- if (!Array.isArray(node) || node[0] !== '{}') return null
1358
- const body = node[1]
1359
- if (body == null) return []
1360
- if (Array.isArray(body) && body[0] === ',') return body.slice(1)
1361
- return [body]
1362
- }
1363
-
1364
- function getterReturnExpr(node) {
1365
- if (!Array.isArray(node) || node[0] !== '=>') return null
1366
- const params = paramList(node[1])
1367
- if (params.length !== 0) return null
1368
- const body = node[2]
1369
- if (Array.isArray(body) && body[0] === '{}' && Array.isArray(body[1]) && body[1][0] === 'return') return body[1][1]
1370
- if (Array.isArray(body) && body[0] === '{}' && Array.isArray(body[1]) && body[1][0] === ';' &&
1371
- Array.isArray(body[1][1]) && body[1][1][0] === 'return') return body[1][1][1]
1372
- if (Array.isArray(body) && body[0] === 'return') return body[1]
1373
- return body
1374
- }
1375
-
1376
- function replaceStaticExportReads(node, rewrites) {
1377
- if (node == null || typeof node !== 'object' || !Array.isArray(node)) return node
1378
- if ((node[0] === '.' || node[0] === '?.') && typeof node[1] === 'string' && typeof node[2] === 'string') {
1379
- const value = rewrites.get(`${node[1]}.${node[2]}`)
1380
- if (value) return cloneAst(value)
1381
- }
1382
- if (node[0] === ':') return [node[0], node[1], replaceStaticExportReads(node[2], rewrites)]
1383
- return node.map((part, i) => i === 0 ? part : replaceStaticExportReads(part, rewrites))
1384
- }
1385
-
1386
- function canonicalizeObjectIdioms(node) {
1387
- if (node == null || typeof node !== 'object' || !Array.isArray(node)) return node
1388
-
1389
- const out = node.map((part, i) => i === 0 ? part : canonicalizeObjectIdioms(part))
1390
-
1391
- const hasOwnCall = objectHasOwnPropertyCall(out)
1392
- if (hasOwnCall) return ['()', ['.', hasOwnCall.obj, 'hasOwnProperty'], hasOwnCall.key]
1393
-
1394
- const mapString = arrayMapStringCallback(out)
1395
- if (mapString) return mapString
1396
-
1397
- if (out[0] === '&&') {
1398
- const leftCtor = constructorIsObject(out[1])
1399
- const rightKeys = objectKeysLengthZero(out[2])
1400
- if (leftCtor && rightKeys && astEqual(leftCtor.obj, rightKeys.obj)) return out[2]
1401
-
1402
- const leftKeys = objectKeysLengthZero(out[1])
1403
- const rightCtor = constructorIsObject(out[2])
1404
- if (leftKeys && rightCtor && astEqual(leftKeys.obj, rightCtor.obj)) return out[1]
1405
- }
1406
-
1407
- return out
1408
- }
1409
-
1410
- function arrayMapStringCallback(node) {
1411
- if (!Array.isArray(node) || node[0] !== '()') return null
1412
- const callee = node[1]
1413
- if (!Array.isArray(callee) || callee[0] !== '.' || callee[2] !== 'map') return null
1414
- const args = callArgs(node.slice(2))
1415
- if (args.length !== 1 || args[0] !== 'String') return null
1416
- return ['()', callee, ['=>', 'value', ['()', 'String', 'value']]]
1417
- }
1418
-
1419
- function objectHasOwnPropertyCall(node) {
1420
- if (!Array.isArray(node) || node[0] !== '()') return null
1421
- const callee = node[1]
1422
- if (!Array.isArray(callee) || callee[0] !== '.' || callee[2] !== 'call') return null
1423
- if (!isObjectHasOwnPropertyRef(callee[1])) return null
1424
- const args = callArgs(node.slice(2))
1425
- if (args.length < 2) return null
1426
- return { obj: args[0], key: args[1] }
1427
- }
1428
-
1429
- function isObjectHasOwnPropertyRef(node) {
1430
- if (!Array.isArray(node) || node[0] !== '.' || node[2] !== 'hasOwnProperty') return false
1431
- if (node[1] === 'Object') return true
1432
- return Array.isArray(node[1]) && node[1][0] === '.' && node[1][1] === 'Object' && node[1][2] === 'prototype'
1433
- }
1434
-
1435
- function constructorIsObject(node) {
1436
- if (!Array.isArray(node) || (node[0] !== '===' && node[0] !== '==')) return null
1437
- const left = constructorReceiver(node[1])
1438
- if (left && node[2] === 'Object') return { obj: left }
1439
- const right = constructorReceiver(node[2])
1440
- if (right && node[1] === 'Object') return { obj: right }
1441
- return null
1442
- }
1443
-
1444
- function constructorReceiver(node) {
1445
- return Array.isArray(node) && node[0] === '.' && node[2] === 'constructor' ? node[1] : null
1446
- }
1447
-
1448
- function objectKeysLengthZero(node) {
1449
- if (!Array.isArray(node) || (node[0] !== '===' && node[0] !== '==')) return null
1450
- const left = objectKeysLengthReceiver(node[1])
1451
- if (left && isZeroLiteral(node[2])) return { obj: left }
1452
- const right = objectKeysLengthReceiver(node[2])
1453
- if (right && isZeroLiteral(node[1])) return { obj: right }
1454
- return null
1455
- }
1456
-
1457
- function objectKeysLengthReceiver(node) {
1458
- if (!Array.isArray(node) || node[0] !== '.' || node[2] !== 'length') return null
1459
- const call = node[1]
1460
- if (!Array.isArray(call) || call[0] !== '()') return null
1461
- const callee = call[1]
1462
- if (!Array.isArray(callee) || callee[0] !== '.' || callee[1] !== 'Object' || callee[2] !== 'keys') return null
1463
- const args = callArgs(call.slice(2))
1464
- return args.length === 1 ? args[0] : null
1465
- }
1466
-
1467
- function isZeroLiteral(node) {
1468
- return Array.isArray(node) && node[0] == null && node[1] === 0
1469
- }
1470
-
1471
- function astEqual(a, b) {
1472
- return JSON.stringify(a) === JSON.stringify(b)
1473
- }
1474
-
1475
- function cloneAst(node) {
1476
- if (node == null || typeof node !== 'object') return node
1477
- if (!Array.isArray(node)) return node
1478
- return node.map(cloneAst)
1479
- }
1480
-
1481
- /** Flatten a switch clause body to a single node, dropping ASI position markers.
1482
- * Unlike a plain statement list this keeps any `break` intact — transformSwitch
1483
- * needs the breaks to gate fall-through; it rewrites them to a sticky flag. */
1484
- function normalizeCaseBody(body) {
1485
- if (!Array.isArray(body) || body[0] !== ';') return body
1486
- const stmts = body.slice(1).filter(s => s != null && typeof s !== 'number')
1487
- return stmts.length === 0 ? null : stmts.length === 1 ? stmts[0] : [';', ...stmts]
1488
- }
1489
-
1490
- const SWITCH_BREAK_BOUNDARIES = new Set(['for', 'for-in', 'for-of', 'while', 'do', 'switch', '=>', 'function', 'class'])
1491
-
1492
- function hasOwnSwitchBreak(node) {
1493
- if (!Array.isArray(node)) return false
1494
- if (node[0] === 'break') return true
1495
- if (SWITCH_BREAK_BOUNDARIES.has(node[0])) return false
1496
- for (let i = 1; i < node.length; i++) if (hasOwnSwitchBreak(node[i])) return true
1497
- return false
1498
- }
1499
-
1500
- function rewriteSwitchBreaks(node, flag) {
1501
- if (!Array.isArray(node)) return node
1502
- const op = node[0]
1503
- if (op === 'break') return ['=', flag, [null, true]]
1504
- if (SWITCH_BREAK_BOUNDARIES.has(op)) return node
1505
-
1506
- if (op === ';') {
1507
- const out = []
1508
- const stmts = node.slice(1)
1509
- for (let i = 0; i < stmts.length; i++) {
1510
- const stmt = stmts[i]
1511
- out.push(rewriteSwitchBreaks(stmt, flag))
1512
- if (hasOwnSwitchBreak(stmt) && i < stmts.length - 1) {
1513
- const tail = rewriteSwitchBreaks([';', ...stmts.slice(i + 1)], flag)
1514
- out.push(['if', ['!', flag], tail])
1515
- break
1516
- }
1517
- }
1518
- return out.length === 0 ? null : out.length === 1 ? out[0] : [';', ...out]
1519
- }
1520
-
1521
- return node.map((part, i) => i === 0 ? part : rewriteSwitchBreaks(part, flag))
1522
- }
1523
-
1524
- /** Transform a switch into structured control flow with faithful fall-through.
1525
- *
1526
- * A pure if/else-if chain (the former lowering) can't express fall-through,
1527
- * stacked labels, or a `default` clause that isn't last \u2014 it ran only the first
1528
- * matching body. The correct model is two-phase, evaluated once with no goto:
1529
- *
1530
- * 1. ENTRY \u2014 compare the discriminant against each `case` label in source
1531
- * order; the first `===` match fixes the entry index. No case matches \u2192
1532
- * entry = the `default` clause's source index (or past-end if none).
1533
- * 2. RUN \u2014 walk clauses in source order; `entry <= i` runs clause i, so every
1534
- * clause from the entry onward executes (fall-through). A `break` flips the
1535
- * sticky `brk` flag (via rewriteSwitchBreaks) and gates the rest.
1536
- *
1537
- * The discriminant is bound to a temp only when re-reading it isn't free/safe; a
1538
- * bare identifier is compared directly \u2014 a synthetic temp would shed its STRING
1539
- * val-type and mis-fold string `case`s to `false` under strict-=== folding. */
1540
- let swIdx = 0
1541
- function transformSwitch(discriminant, cases) {
1542
- const disc = transform(discriminant)
1543
- const simple = typeof disc === 'string' || (Array.isArray(disc) && disc[0] == null)
1544
- const tmp = simple ? disc : `\uE000sw${swIdx++}`
1545
- const start = `\uE000swst${swIdx++}`
1546
- const needsBreakFlag = cases.some(c => hasOwnSwitchBreak(c[0] === 'case' ? c[2] : c[1]))
1547
- const brk = needsBreakFlag ? `\uE000swbrk${swIdx++}` : null
1548
-
1549
- const n = cases.length
1550
- let defaultIdx = -1
1551
- const bodies = cases.map((c, i) => {
1552
- if (c[0] === 'default') { defaultIdx = i; return transform(c[1]) }
1553
- return transform(c[2])
1554
- })
1555
-
1556
- const stmts = []
1557
- if (!simple) stmts.push(['let', ['=', tmp, disc]])
1558
-
1559
- // Phase 1 \u2014 entry index. Init to default's position (or n = "no clause runs"),
1560
- // then let the first matching label override it via an if/else-if chain.
1561
- stmts.push(['let', ['=', start, [null, defaultIdx >= 0 ? defaultIdx : n]]])
1562
- let chain = null
1563
- for (let i = n - 1; i >= 0; i--) {
1564
- if (cases[i][0] !== 'case') continue
1565
- const hit = ['=', start, [null, i]]
1566
- const cond = ['===', tmp, transform(cases[i][1])]
1567
- chain = chain != null ? ['if', cond, hit, chain] : ['if', cond, hit]
1568
- }
1569
- if (chain) stmts.push(chain)
1570
- if (brk) stmts.push(['let', ['=', brk, [null, false]]])
1571
-
1572
- // Phase 2 \u2014 run clauses from the entry index, falling through until a break.
1573
- for (let i = 0; i < n; i++) {
1574
- if (bodies[i] == null) continue
1575
- const body = brk ? rewriteSwitchBreaks(bodies[i], brk) : bodies[i]
1576
- const reached = ['<=', start, [null, i]]
1577
- stmts.push(['if', brk ? ['&&', ['!', brk], reached] : reached, body])
1578
- }
1579
- return [';', ...stmts]
1580
- }