jz 0.5.1 → 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.
- package/README.md +288 -314
- package/bench/README.md +319 -0
- package/bench/bench.svg +112 -0
- package/cli.js +32 -24
- package/index.js +177 -55
- package/interop.js +88 -159
- package/jz.svg +5 -0
- package/jzify/arguments.js +97 -0
- package/jzify/bundler.js +382 -0
- package/jzify/classes.js +328 -0
- package/jzify/hoist-vars.js +177 -0
- package/jzify/index.js +51 -0
- package/jzify/names.js +37 -0
- package/jzify/switch.js +106 -0
- package/jzify/transform.js +349 -0
- package/layout.js +179 -0
- package/module/array.js +322 -153
- package/module/collection.js +603 -145
- package/module/console.js +55 -43
- package/module/core.js +266 -153
- package/module/date.js +15 -3
- package/module/function.js +73 -6
- package/module/index.js +2 -1
- package/module/json.js +226 -61
- package/module/math.js +414 -186
- package/module/number.js +306 -60
- package/module/object.js +448 -184
- package/module/regex.js +255 -25
- package/module/schema.js +24 -6
- package/module/simd.js +85 -0
- package/module/string.js +586 -220
- package/module/symbol.js +1 -1
- package/module/timer.js +9 -14
- package/module/typedarray.js +45 -48
- package/package.json +41 -12
- package/src/abi/index.js +39 -23
- package/src/abi/string.js +38 -41
- package/src/ast.js +460 -0
- package/src/autoload.js +26 -24
- package/src/bridge.js +111 -0
- package/src/compile/analyze-scans.js +661 -0
- package/src/compile/analyze.js +1565 -0
- package/src/compile/emit-assign.js +408 -0
- package/src/compile/emit.js +3201 -0
- package/src/compile/flow-types.js +103 -0
- package/src/{compile.js → compile/index.js} +497 -125
- package/src/{infer.js → compile/infer.js} +27 -98
- package/src/{narrow.js → compile/narrow.js} +302 -96
- package/src/compile/plan/advise.js +316 -0
- package/src/compile/plan/common.js +150 -0
- package/src/compile/plan/index.js +118 -0
- package/src/compile/plan/inline.js +679 -0
- package/src/compile/plan/literals.js +984 -0
- package/src/compile/plan/loops.js +472 -0
- package/src/compile/plan/scope.js +573 -0
- package/src/compile/program-facts.js +404 -0
- package/src/ctx.js +176 -58
- package/src/ir.js +540 -171
- package/src/kind-traits.js +105 -0
- package/src/kind.js +462 -0
- package/src/op-policy.js +57 -0
- package/src/{optimize.js → optimize/index.js} +1106 -446
- package/src/optimize/vectorize.js +1874 -0
- package/src/param-reps.js +65 -0
- package/src/parse.js +44 -0
- package/src/{prepare.js → prepare/index.js} +589 -202
- package/src/reps.js +115 -0
- package/src/resolve.js +12 -3
- package/src/static.js +199 -0
- package/src/type.js +647 -0
- package/src/{assemble.js → wat/assemble.js} +86 -48
- package/src/wat/optimize.js +3760 -0
- package/transform.js +21 -0
- package/wasi.js +47 -5
- package/src/analyze.js +0 -3818
- package/src/emit.js +0 -3040
- package/src/jzify.js +0 -1580
- package/src/plan.js +0 -2132
- package/src/vectorize.js +0 -1088
- /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
|
-
}
|