jz 0.5.0 → 0.6.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 (80) hide show
  1. package/README.md +288 -314
  2. package/bench/README.md +319 -0
  3. package/bench/bench.svg +112 -0
  4. package/cli.js +32 -24
  5. package/index.js +177 -55
  6. package/interop.js +88 -159
  7. package/jz.svg +5 -0
  8. package/jzify/arguments.js +97 -0
  9. package/jzify/bundler.js +382 -0
  10. package/jzify/classes.js +328 -0
  11. package/jzify/hoist-vars.js +177 -0
  12. package/jzify/index.js +51 -0
  13. package/jzify/names.js +37 -0
  14. package/jzify/switch.js +106 -0
  15. package/jzify/transform.js +349 -0
  16. package/layout.js +179 -0
  17. package/module/array.js +322 -153
  18. package/module/collection.js +603 -145
  19. package/module/console.js +55 -43
  20. package/module/core.js +281 -142
  21. package/module/date.js +15 -3
  22. package/module/function.js +73 -6
  23. package/module/index.js +2 -1
  24. package/module/json.js +226 -61
  25. package/module/math.js +461 -185
  26. package/module/number.js +306 -60
  27. package/module/object.js +448 -184
  28. package/module/regex.js +255 -25
  29. package/module/schema.js +24 -6
  30. package/module/simd.js +85 -0
  31. package/module/string.js +591 -220
  32. package/module/symbol.js +1 -1
  33. package/module/timer.js +9 -14
  34. package/module/typedarray.js +45 -48
  35. package/package.json +41 -12
  36. package/src/abi/index.js +39 -23
  37. package/src/abi/string.js +38 -41
  38. package/src/ast.js +460 -0
  39. package/src/autoload.js +26 -24
  40. package/src/bridge.js +111 -0
  41. package/src/compile/analyze-scans.js +661 -0
  42. package/src/compile/analyze.js +1565 -0
  43. package/src/compile/emit-assign.js +408 -0
  44. package/src/compile/emit.js +3201 -0
  45. package/src/compile/flow-types.js +103 -0
  46. package/src/{compile.js → compile/index.js} +497 -125
  47. package/src/{infer.js → compile/infer.js} +27 -98
  48. package/src/{narrow.js → compile/narrow.js} +302 -96
  49. package/src/compile/plan/advise.js +316 -0
  50. package/src/compile/plan/common.js +150 -0
  51. package/src/compile/plan/index.js +118 -0
  52. package/src/compile/plan/inline.js +679 -0
  53. package/src/compile/plan/literals.js +984 -0
  54. package/src/compile/plan/loops.js +472 -0
  55. package/src/compile/plan/scope.js +573 -0
  56. package/src/compile/program-facts.js +404 -0
  57. package/src/ctx.js +176 -58
  58. package/src/ir.js +540 -171
  59. package/src/kind-traits.js +105 -0
  60. package/src/kind.js +462 -0
  61. package/src/op-policy.js +57 -0
  62. package/src/{optimize.js → optimize/index.js} +1106 -446
  63. package/src/optimize/vectorize.js +1874 -0
  64. package/src/param-reps.js +65 -0
  65. package/src/parse.js +44 -0
  66. package/src/{prepare.js → prepare/index.js} +600 -205
  67. package/src/reps.js +115 -0
  68. package/src/resolve.js +12 -3
  69. package/src/static.js +199 -0
  70. package/src/type.js +647 -0
  71. package/src/{assemble.js → wat/assemble.js} +86 -48
  72. package/src/wat/optimize.js +3760 -0
  73. package/transform.js +21 -0
  74. package/wasi.js +47 -5
  75. package/src/analyze.js +0 -3818
  76. package/src/emit.js +0 -2997
  77. package/src/jzify.js +0 -1553
  78. package/src/plan.js +0 -2132
  79. package/src/vectorize.js +0 -1088
  80. /package/src/{codegen.js → wat/codegen.js} +0 -0
package/src/jzify.js DELETED
@@ -1,1553 +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
- const TYPED_ARRAYS = new Set(['Float64Array','Float32Array','Int32Array','Uint32Array',
372
- 'Int16Array','Uint16Array','Int8Array','Uint8Array',
373
- 'ArrayBuffer','BigInt64Array','BigUint64Array','DataView'])
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
-
415
- // `arguments` lowering: regular `function` has implicit `arguments`; arrow doesn't.
416
- // jzify converts function → arrow, so any `arguments` use must be rewritten to a rest param.
417
- // Arrow functions inherit `arguments` from enclosing function — don't stop at '=>'.
418
- // Nested `function` introduces its own `arguments` — stop recursion there.
419
- let argsIdx = 0
420
- let doIdx = 0
421
-
422
- function usesArguments(node) {
423
- if (node === 'arguments') return true
424
- if (!Array.isArray(node)) return false
425
- if (node[0] === 'function') return false
426
- if (node[0] === '.' || node[0] === '?.') return usesArguments(node[1])
427
- if (node[0] === ':') return usesArguments(node[2])
428
- for (let i = 1; i < node.length; i++) if (usesArguments(node[i])) return true
429
- return false
430
- }
431
-
432
- // `arguments` is the implicit object only if the function body doesn't declare a
433
- // local of that name. Scan the body's own statement list (not nested scopes) for
434
- // `var/let/const arguments` — a regular `function` with `var arguments;` just has
435
- // an ordinary local, no arguments object.
436
- function bindsArguments(body) {
437
- const isArgDecl = s => Array.isArray(s) && (s[0] === 'var' || s[0] === 'let' || s[0] === 'const') &&
438
- s.slice(1).some(d => d === 'arguments' || (Array.isArray(d) && d[0] === '=' && d[1] === 'arguments'))
439
- let n = body
440
- if (Array.isArray(n) && n[0] === '{}') n = n[1]
441
- if (Array.isArray(n) && n[0] === ';') return n.slice(1).some(isArgDecl)
442
- return isArgDecl(n)
443
- }
444
-
445
- function renameArguments(node, to) {
446
- if (node === 'arguments') return to
447
- if (!Array.isArray(node)) return node
448
- if (node[0] === 'function') return node
449
- if (node[0] === '.' || node[0] === '?.')
450
- return [node[0], renameArguments(node[1], to), node[2]]
451
- if (node[0] === ':')
452
- return [node[0], node[1], renameArguments(node[2], to)]
453
- return node.map(n => renameArguments(n, to))
454
- }
455
-
456
- function paramList(params) {
457
- if (params == null) return []
458
- if (Array.isArray(params)) {
459
- if (params[0] === '()') {
460
- const inner = params[1]
461
- if (inner == null) return []
462
- if (Array.isArray(inner) && inner[0] === ',') return inner.slice(1)
463
- return [inner]
464
- }
465
- if (params[0] === ',') return params.slice(1)
466
- }
467
- return [params]
468
- }
469
-
470
- // Destructuring pattern as a parameter — `[a,b]` / `{a,b}` (optionally with a
471
- // default). Plain `=` defaults and `...rest` are handled natively by emit, so
472
- // they don't by themselves force lowering.
473
- const isDestructurePat = p => Array.isArray(p) && (p[0] === '[]' || p[0] === '{}' || (p[0] === '=' && isDestructurePat(p[1])))
474
-
475
- function lowerArguments(params, body) {
476
- // A function body that declares its own `arguments` local: it's an ordinary
477
- // variable, not the implicit object \u2014 rename it out of jz's reserved set,
478
- // no rest param synthesized.
479
- if (bindsArguments(body)) body = renameArguments(body, `\uE001arg${argsIdx++}`)
480
- const paramsNeedLowering = paramList(params).some(isDestructurePat)
481
- const usesArgsObj = usesArguments(params) || usesArguments(body)
482
- if (!paramsNeedLowering && !usesArgsObj) return [params, body]
483
- const name = `\uE001arg${argsIdx++}`
484
- const decls = []
485
- for (const [idx, param] of paramList(params).entries()) {
486
- if (Array.isArray(param) && param[0] === '...') {
487
- decls.push(['=', param[1], ['()', ['.', name, 'slice'], [null, idx]]])
488
- continue
489
- }
490
- if (Array.isArray(param) && param[0] === '=') {
491
- decls.push(['=', param[1], ['??', ['[]', name, [null, idx]], renameArguments(param[2], name)]])
492
- continue
493
- }
494
- decls.push(['=', param, ['[]', name, [null, idx]]])
495
- }
496
- const renamed = usesArgsObj ? renameArguments(body, name) : body
497
- return [['()', ['...', name]], decls.length ? prependParamDecls(['let', ...decls], renamed) : renamed]
498
- }
499
-
500
- function prependParamDecls(decl, body) {
501
- if (Array.isArray(body) && body[0] === '{}') {
502
- const inner = body[1]
503
- if (Array.isArray(inner) && inner[0] === ';') return ['{}', [';', decl, ...inner.slice(1)]]
504
- if (inner == null) return ['{}', decl]
505
- return ['{}', [';', decl, inner]]
506
- }
507
- if (Array.isArray(body) && (body[0] === ';' || body[0] === 'return')) return [';', decl, body]
508
- return ['{}', [';', decl, ['return', body]]]
509
- }
510
-
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]
516
-
517
- // === class lowering ===
518
- //
519
- // A class is lowered to a factory arrow. Instance state is a plain object;
520
- // methods are per-instance arrows capturing it (so `obj.m()` keeps working
521
- // without a separate `this` argument); `this` is renamed to that object;
522
- // `new C(a)` is already turned into `C(a)` by the `new` handler.
523
- //
524
- // class Point { x = 0; y; constructor(a,b){ this.x = a; this.y = b }
525
- // dist(){ return Math.hypot(this.x, this.y) } }
526
- // →
527
- // let Point = (a, b) => {
528
- // let selfN = { x: undefined, y: undefined,
529
- // dist: () => Math.hypot(selfN.x, selfN.y) }
530
- // selfN.x = 0 // field initializers, in declaration order
531
- // selfN.x = a // then the constructor body
532
- // selfN.y = b
533
- // return selfN
534
- // }
535
- //
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).
544
- let classIdx = 0
545
- let objThisIdx = 0
546
- let staticClassIdx = 0
547
- let classBaseIdx = 0
548
- const DEFAULT_DERIVED_CTOR_ARITY = 8
549
-
550
- const classBodyItems = (body) =>
551
- body == null ? [] : Array.isArray(body) && body[0] === ';' ? body.slice(1) : [body]
552
-
553
- // Rename `this` → `to`, not crossing into a nested `function`/`class` (those
554
- // rebind `this`); arrows inherit `this`, so they are crossed. Property *names*
555
- // (`obj.this`, `{this: …}` value-side only) are left alone.
556
- function renameThis(node, to) {
557
- if (node === 'this') return to
558
- if (!Array.isArray(node)) return node
559
- if (node[0] === 'function' || node[0] === 'class') return node
560
- if (node[0] === '.' || node[0] === '?.') return [node[0], renameThis(node[1], to), node[2]]
561
- if (node[0] === ':') return [node[0], node[1], renameThis(node[2], to)]
562
- return node.map(n => renameThis(n, to))
563
- }
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
-
691
- function jzifyError(msg) { throw new Error(`jzify: ${msg}`) }
692
-
693
- function lowerClass(name, heritage, body) {
694
- let ctorParams = null, ctorBody = null
695
- const methods = [], fields = [], statics = []
696
- for (const it of classBodyItems(body)) {
697
- if (typeof it === 'string') { fields.push([it, null]); continue } // bare `x;`
698
- if (!Array.isArray(it)) continue
699
- const bareFieldName = constStringKey(it)
700
- if (bareFieldName != null) { fields.push([bareFieldName, null]); continue }
701
- if (it[0] === ':' && Array.isArray(it[2]) && it[2][0] === '=>') {
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]])
706
- continue
707
- }
708
- if (it[0] === '=') {
709
- const lhs = it[1]
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])
736
- continue
737
- }
738
- if (it[0] === 'get' || it[0] === 'set') jzifyError('class getters/setters are not supported — jz objects have no accessors')
739
- if (it[0] === 'static') jzifyError('`static` class members are not supported yet')
740
- jzifyError(`unsupported class member ${JSON.stringify(it).slice(0, 60)}`)
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
- }
757
- const self = `self${classIdx++}`
758
- const UNDEF = [] // jessie's node for `undefined`
759
- // Object literal: every declared field (its initializer inline when it doesn't
760
- // touch `this`, else `undefined` and assigned below), every method as its
761
- // self-capturing arrow. Declaring all fields up front fixes the object shape.
762
- const litProps = [], deferred = []
763
- for (const [fname, init] of fields) {
764
- if (init != null && !usesThis(init)) litProps.push([':', fname, transform(init)])
765
- else { litProps.push([':', fname, UNDEF]); if (init != null) deferred.push([fname, init]) }
766
- }
767
- for (const [mname, mparams, mbody] of methods)
768
- litProps.push([':', mname, transform(['=>', mparams ?? ['()', null], block(renameThis(mbody, self))])])
769
- const lit = ['{}', litProps.length === 0 ? null : litProps.length === 1 ? litProps[0] : [',', ...litProps]]
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
- }
798
- // `this`-dependent field initializers run, in declaration order, before the ctor.
799
- if (heritage == null) {
800
- for (const [fname, init] of deferred)
801
- stmts.push(['=', ['.', self, fname], transform(renameThis(init, self))])
802
- }
803
- if (ctorBody != null) {
804
- let cb = transform(renameThis(ctorBody, self))
805
- if (Array.isArray(cb) && cb[0] === '{}') cb = cb[1]
806
- if (Array.isArray(cb) && cb[0] === ';') stmts.push(...cb.slice(1).filter(s => s != null))
807
- else if (cb != null) stmts.push(cb)
808
- }
809
- stmts.push(['return', self])
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
835
- }
836
-
837
- const handlers = {
838
- // Named IIFE: (function name(p){b})(a) → let name = arrow; name(a)
839
- '()'(callee, ...rest) {
840
- if (callee === 'Array') {
841
- const lit = lowerArrayConstructor(rest[0])
842
- if (lit) return lit
843
- }
844
- if (Array.isArray(callee) && callee[0] === '()' && Array.isArray(callee[1]) && callee[1][0] === 'function' && callee[1][1]) {
845
- const [, name, params, body] = callee[1]
846
- const [p2, b2] = lowerArguments(params, functionBodyBlock(body))
847
- return [';', ['let', ['=', name, ['=>', arrowParams(p2), wrapArrowBody(b2)]]], ['()', name, ...rest.map(transform)]]
848
- }
849
- },
850
-
851
- // function → arrow. Named function expression desugars to IIFE so the name is
852
- // bound inside body per ES spec: `function f(){...f...}` → `(()=>{let f;f=arrow;return f})()`.
853
- // Statement-form named functions are hoisted by transformScope before reaching here.
854
- 'function'(name, params, body) {
855
- const [p2, b2] = lowerArguments(params, functionBodyBlock(body))
856
- const arrow = ['=>', p2, wrapArrowBody(b2)]
857
- if (name) {
858
- return ['()', ['()', ['=>', null, ['{}', [';',
859
- ['let', name],
860
- ['=', name, arrow],
861
- ['return', name]
862
- ]]]], null]
863
- }
864
- return arrow
865
- },
866
-
867
- '=>'(params, body) {
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)
883
- return ['=>', p2, transform(b2)]
884
- },
885
-
886
- // Class in expression position → its factory arrow. (A named class
887
- // expression's own inner binding is dropped — rare; statement-form
888
- // `class C {}` is handled by transformScope, which keeps the binding.)
889
- 'class'(name, heritage, body) { return lowerClass(name, heritage, body) },
890
-
891
- // `var` is hoisted away before transform reaches here. If one slips through
892
- // (e.g. raw subscript output without going via jzify entry/wrapArrowBody),
893
- // fall back to treating it as `let`.
894
- 'var'(...args) {
895
- return ['let', ...args.map(transform)]
896
- },
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
-
903
- '='(lhs, rhs) {
904
- if (isDestructurePat(lhs)) return ['=', transformPattern(lhs), transform(rhs)]
905
- },
906
-
907
- 'switch'(disc, ...cases) {
908
- const clean = cases.map(c => {
909
- if (c[0] === 'case' && Array.isArray(c[2]) && c[2][0] === ';') {
910
- const body = c[2].slice(1).filter(s => typeof s !== 'number')
911
- const stripped = stripTerminalSwitchBreak(body.length === 1 ? body[0] : [';', ...body])
912
- return ['case', c[1], stripped]
913
- }
914
- if (c[0] === 'default' && Array.isArray(c[1]) && c[1][0] === ';') {
915
- const body = c[1].slice(1).filter(s => s != null && typeof s !== 'number')
916
- const stripped = stripTerminalSwitchBreak(body.length === 1 ? body[0] : [';', ...body])
917
- return ['default', stripped]
918
- }
919
- if (c[0] === 'case') return ['case', c[1], stripTerminalSwitchBreak(c[2])]
920
- if (c[0] === 'default') return ['default', stripTerminalSwitchBreak(c[1])]
921
- return c
922
- })
923
- return transformSwitch(disc, clean)
924
- },
925
-
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)] },
932
- '==='(a, b) { if (isProto(a) || isProto(b)) return 1 },
933
- '!=='(a, b) { if (isProto(a) || isProto(b)) return 0 },
934
-
935
- // new → call (keep TypedArrays)
936
- 'new'(ctor, ...cargs) {
937
- if (Array.isArray(ctor) && ctor[0] === '()' && ctor[1] === 'Array') {
938
- const lit = lowerArrayConstructor(ctor[2])
939
- if (lit) return lit
940
- }
941
- const name = typeof ctor === 'string' ? ctor : (Array.isArray(ctor) && ctor[0] === '()' ? ctor[1] : null)
942
- if (typeof name === 'string' && (TYPED_ARRAYS.has(name) || name === 'Array' || name === 'RegExp')) return ['new', transform(ctor), ...cargs.map(transform)]
943
- if (Array.isArray(ctor) && ctor[0] === '()') return transform(ctor)
944
- // `new C(a)` → `C(a)`; `new C` (no parens) → `C()` — a 2-element `['()', X]`
945
- // is grouping parens, so a no-arg call needs the explicit `null` arg slot.
946
- return ['()', transform(ctor), ...(cargs.length ? cargs.map(transform) : [null])]
947
- },
948
-
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.
957
- 'instanceof'(val, ctor) {
958
- const t = transform(val)
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]
963
- if (name === 'Array') return ['()', ['.', 'Array', 'isArray'], t]
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.
969
- return ['===', ['typeof', t], [null, 'object']]
970
- },
971
-
972
- // do { body } while (cond) → let _once = true; while (_once || cond) { _once = false; body }
973
- // Avoids body duplication and preserves continue: `continue` jumps back to the
974
- // while condition after the one-shot flag has been cleared.
975
- 'do'(body, cond) {
976
- const flag = `do${doIdx++}`
977
- return [';',
978
- ['let', ['=', flag, [null, true]]],
979
- ['while', ['||', flag, transform(cond)], ['{}', [';', ['=', flag, [null, false]], transform(body)]]]]
980
- },
981
-
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
- },
1003
-
1004
- // Export: recurse into exported declaration. Statement-form `export function name`
1005
- // and `export default function name` must be hoisted as const-arrows — otherwise
1006
- // the generic `function` handler wraps them in a named-IIFE (correct for *expressions*,
1007
- // wrong for declarations), producing `export ['()', IIFE]` which has no exportable binding.
1008
- 'export'(inner) {
1009
- if (Array.isArray(inner) && inner[0] === 'function' && inner[1]) {
1010
- return ['export', hoistFnDecl(inner[1], inner[2], inner[3])]
1011
- }
1012
- // `export class C {}` → `export let C = factory`; named class keeps its binding.
1013
- if (Array.isArray(inner) && inner[0] === 'class' && inner[1]) {
1014
- return ['export', ['let', ['=', inner[1], lowerClass(inner[1], inner[2], inner[3])]]]
1015
- }
1016
- if (Array.isArray(inner) && inner[0] === 'default' && Array.isArray(inner[1]) && inner[1][0] === 'function' && inner[1][1]) {
1017
- const decl = hoistFnDecl(inner[1][1], inner[1][2], inner[1][3])
1018
- return [';', decl, ['export', ['default', inner[1][1]]]]
1019
- }
1020
- if (Array.isArray(inner) && inner[0] === 'default' && Array.isArray(inner[1]) && inner[1][0] === 'class' && inner[1][1]) {
1021
- return [';', ['let', ['=', inner[1][1], lowerClass(inner[1][1], inner[1][2], inner[1][3])]], ['export', ['default', inner[1][1]]]]
1022
- }
1023
- return ['export', transform(inner)]
1024
- },
1025
- }
1026
-
1027
- /** Transform a single AST node recursively. */
1028
- function transform(node) {
1029
- if (node == null || typeof node !== 'object' || !Array.isArray(node)) return node
1030
- const [op, ...args] = node
1031
- if (op == null) return node
1032
- const h = handlers[op]
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)]
1037
- }
1038
-
1039
- // Esbuild emits a small ESM helper:
1040
- //
1041
- // var __defProp = Object.defineProperty;
1042
- // var __export = (target, all) => {
1043
- // for (var name in all)
1044
- // __defProp(target, name, { get: all[name], enumerable: true });
1045
- // };
1046
- // __export(src_exports, { default: () => value });
1047
- // use(src_exports.default);
1048
- //
1049
- // Full descriptor/prototype semantics are outside JZ's fixed-shape object model.
1050
- // This pass instead recognizes the static helper pattern and rewrites reads of
1051
- // the synthetic export object to the real binding.
1052
- function foldStaticExportHelpers(ast) {
1053
- const body = astSeq(ast)
1054
- if (!body) return ast
1055
-
1056
- const defPropAliases = new Set()
1057
- for (const stmt of body) {
1058
- const b = bindingOf(stmt)
1059
- if (b && isObjectDefineProperty(b[1])) defPropAliases.add(b[0])
1060
- }
1061
- if (!defPropAliases.size) return ast
1062
-
1063
- const helperNames = new Set()
1064
- for (const stmt of body) {
1065
- const b = bindingOf(stmt)
1066
- if (b && Array.isArray(b[1]) && b[1][0] === '=>' && containsDefinePropertyCall(b[1], defPropAliases))
1067
- helperNames.add(b[0])
1068
- }
1069
- if (!helperNames.size) return ast
1070
-
1071
- const rewrites = new Map()
1072
- const removable = new Set()
1073
- for (const stmt of body) {
1074
- const ex = staticExportCall(stmt, helperNames)
1075
- if (!ex) continue
1076
- for (const [key, value] of ex.props) rewrites.set(`${ex.target}.${key}`, value)
1077
- removable.add(stmt)
1078
- }
1079
- if (!rewrites.size) return ast
1080
-
1081
- const rewritten = body
1082
- .filter(stmt => !removable.has(stmt) && !isDefPropAliasAssign(stmt, defPropAliases) && !isExportHelperAssign(stmt, helperNames))
1083
- .map(stmt => replaceStaticExportReads(stmt, rewrites))
1084
- return rewritten.length === 0 ? null : rewritten.length === 1 ? rewritten[0] : [';', ...rewritten]
1085
- }
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
-
1291
- function astSeq(ast) {
1292
- if (!Array.isArray(ast)) return null
1293
- return ast[0] === ';' ? ast.slice(1).filter(Boolean) : [ast]
1294
- }
1295
-
1296
- function isObjectDefineProperty(node) {
1297
- return Array.isArray(node) && node[0] === '.' && node[1] === 'Object' && node[2] === 'defineProperty'
1298
- }
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
-
1314
- function isDefPropAliasAssign(stmt, aliases) {
1315
- const b = bindingOf(stmt)
1316
- return b != null && aliases.has(b[0]) && isObjectDefineProperty(b[1])
1317
- }
1318
-
1319
- function isExportHelperAssign(stmt, helpers) {
1320
- const b = bindingOf(stmt)
1321
- return b != null && helpers.has(b[0])
1322
- }
1323
-
1324
- function containsDefinePropertyCall(node, aliases) {
1325
- if (!Array.isArray(node)) return false
1326
- if (node[0] === '()' && (aliases.has(node[1]) || isObjectDefineProperty(node[1]))) return true
1327
- for (let i = 1; i < node.length; i++) if (containsDefinePropertyCall(node[i], aliases)) return true
1328
- return false
1329
- }
1330
-
1331
- function staticExportCall(stmt, helpers) {
1332
- if (!Array.isArray(stmt) || stmt[0] !== '()' || !helpers.has(stmt[1])) return null
1333
- const args = callArgs(stmt.slice(2))
1334
- if (args.length !== 2 || typeof args[0] !== 'string') return null
1335
- const props = objectProps(args[1])
1336
- if (!props) return null
1337
- const out = []
1338
- for (const prop of props) {
1339
- if (!Array.isArray(prop) || prop[0] !== ':' || typeof prop[1] !== 'string') return null
1340
- const value = getterReturnExpr(prop[2])
1341
- if (!value) return null
1342
- out.push([prop[1], value])
1343
- }
1344
- return { target: args[0], props: out }
1345
- }
1346
-
1347
- function callArgs(args) {
1348
- if (args.length === 1 && Array.isArray(args[0]) && args[0][0] === ',') return args[0].slice(1)
1349
- return args.filter(a => a != null)
1350
- }
1351
-
1352
- function objectProps(node) {
1353
- if (!Array.isArray(node) || node[0] !== '{}') return null
1354
- const body = node[1]
1355
- if (body == null) return []
1356
- if (Array.isArray(body) && body[0] === ',') return body.slice(1)
1357
- return [body]
1358
- }
1359
-
1360
- function getterReturnExpr(node) {
1361
- if (!Array.isArray(node) || node[0] !== '=>') return null
1362
- const params = paramList(node[1])
1363
- if (params.length !== 0) return null
1364
- const body = node[2]
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]
1368
- if (Array.isArray(body) && body[0] === 'return') return body[1]
1369
- return body
1370
- }
1371
-
1372
- function replaceStaticExportReads(node, rewrites) {
1373
- if (node == null || typeof node !== 'object' || !Array.isArray(node)) return node
1374
- if ((node[0] === '.' || node[0] === '?.') && typeof node[1] === 'string' && typeof node[2] === 'string') {
1375
- const value = rewrites.get(`${node[1]}.${node[2]}`)
1376
- if (value) return cloneAst(value)
1377
- }
1378
- if (node[0] === ':') return [node[0], node[1], replaceStaticExportReads(node[2], rewrites)]
1379
- return node.map((part, i) => i === 0 ? part : replaceStaticExportReads(part, rewrites))
1380
- }
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
-
1471
- function cloneAst(node) {
1472
- if (node == null || typeof node !== 'object') return node
1473
- if (!Array.isArray(node)) return node
1474
- return node.map(cloneAst)
1475
- }
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
-
1526
- /** Transform switch statement to if/else chain. */
1527
- let swIdx = 0
1528
- function transformSwitch(discriminant, cases) {
1529
- const disc = transform(discriminant)
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
1533
-
1534
- // Collect case/default
1535
- const stmts = [['let', ['=', tmp, disc]]]
1536
- if (brk) stmts.push(['let', ['=', brk, [null, false]]])
1537
- let chain = null
1538
-
1539
- for (let i = cases.length - 1; i >= 0; i--) {
1540
- const c = cases[i]
1541
- if (c[0] === 'default') {
1542
- const body = transform(c[1])
1543
- chain = brk ? rewriteSwitchBreaks(body, brk) : body
1544
- } else if (c[0] === 'case') {
1545
- const cond = ['===', tmp, transform(c[1])]
1546
- const body = transform(c[2])
1547
- const lowered = brk ? rewriteSwitchBreaks(body, brk) : body
1548
- chain = chain != null ? ['if', cond, lowered, chain] : ['if', cond, lowered]
1549
- }
1550
- }
1551
- if (chain) stmts.push(chain)
1552
- return [';', ...stmts]
1553
- }