jz 0.2.1 → 0.3.1
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 +18 -26
- package/cli.js +53 -8
- package/index.js +14 -1
- package/module/array.js +66 -9
- package/module/collection.js +68 -35
- package/module/core.js +65 -19
- package/module/date.js +5 -0
- package/module/function.js +2 -1
- package/module/json.js +66 -8
- package/module/number.js +127 -5
- package/module/object.js +88 -1
- package/module/string.js +3 -2
- package/module/typedarray.js +7 -1
- package/package.json +3 -3
- package/src/analyze.js +16 -4
- package/src/assemble.js +544 -0
- package/src/ast.js +160 -0
- package/src/auto-config.js +120 -0
- package/src/autoload.js +4 -1
- package/src/compile.js +22 -620
- package/src/ctx.js +33 -5
- package/src/emit.js +73 -165
- package/src/host.js +12 -0
- package/src/ir.js +13 -0
- package/src/jzify.js +348 -9
- package/src/narrow.js +2 -1
- package/src/optimize.js +298 -24
- package/src/plan.js +220 -34
- package/src/prepare.js +22 -2
- package/src/vectorize.js +3 -12
package/src/jzify.js
CHANGED
|
@@ -26,11 +26,12 @@ export default function jzify(ast) {
|
|
|
26
26
|
swIdx = 0
|
|
27
27
|
argsIdx = 0
|
|
28
28
|
doIdx = 0
|
|
29
|
+
classIdx = 0
|
|
29
30
|
// Hoist module-level vars: any `var x` inside nested blocks bubbles up.
|
|
30
31
|
const names = new Set()
|
|
31
32
|
ast = hoistVars(ast, names)
|
|
32
33
|
if (names.size) ast = prependDecls(ast, names)
|
|
33
|
-
return transformScope(ast)
|
|
34
|
+
return foldStaticExportHelpers(transformScope(ast))
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
/**
|
|
@@ -70,12 +71,20 @@ function hoistVars(node, names) {
|
|
|
70
71
|
}
|
|
71
72
|
return [op, lhs, hoistVars(node[2], names)]
|
|
72
73
|
}
|
|
74
|
+
if (op === '=' && Array.isArray(node[1]) && node[1][0] === 'var' && typeof node[1][1] === 'string' && node[1].length === 2) {
|
|
75
|
+
names.add(node[1][1])
|
|
76
|
+
return ['=', node[1][1], hoistVars(node[2], names)]
|
|
77
|
+
}
|
|
73
78
|
// For-head `;` is positional (init; cond; update), not a statement sequence.
|
|
74
79
|
// Recurse into each slot but never filter nulls — empty slots are valid.
|
|
75
80
|
if (op === 'for') {
|
|
76
81
|
const head = node[1]
|
|
77
82
|
let h2
|
|
78
|
-
if (Array.isArray(head) && head[0] === '
|
|
83
|
+
if (Array.isArray(head) && head[0] === 'var' && Array.isArray(head[1]) &&
|
|
84
|
+
(head[1][0] === 'in' || head[1][0] === 'of') && typeof head[1][1] === 'string') {
|
|
85
|
+
names.add(head[1][1])
|
|
86
|
+
h2 = [head[1][0], head[1][1], hoistVars(head[1][2], names)]
|
|
87
|
+
} else if (Array.isArray(head) && head[0] === ';') {
|
|
79
88
|
h2 = [';']
|
|
80
89
|
for (let i = 1; i < head.length; i++) h2.push(hoistVars(head[i], names))
|
|
81
90
|
} else {
|
|
@@ -144,16 +153,43 @@ function transformScope(node) {
|
|
|
144
153
|
|
|
145
154
|
// Single named function-statement at scope position: hoist as const arrow
|
|
146
155
|
if (op === 'function' && args[0]) return hoistFnDecl(...args)
|
|
156
|
+
// Single statement-form class declaration: bind the factory (no hoisting — classes are TDZ)
|
|
157
|
+
if (op === 'class' && args[0]) return ['let', ['=', args[0], lowerClass(...args)]]
|
|
147
158
|
|
|
148
159
|
// Statement sequence: collect hoisted functions
|
|
149
160
|
if (op === ';') {
|
|
150
161
|
const hoisted = [], rest = []
|
|
151
|
-
for (
|
|
162
|
+
for (let i = 0; i < args.length; i++) {
|
|
163
|
+
const stmt = args[i]
|
|
164
|
+
// Workaround for subscript parser ASI bug: multiline named IIFE
|
|
165
|
+
// `(function name(){...})();` is parsed as two statements when there are
|
|
166
|
+
// newlines inside the function body. Reconstruct the single-statement IIFE
|
|
167
|
+
// so the () handler can desugar it correctly.
|
|
168
|
+
if (Array.isArray(stmt) && stmt[0] === '()' &&
|
|
169
|
+
Array.isArray(stmt[1]) && stmt[1][0] === 'function' && stmt[1][1] &&
|
|
170
|
+
i + 1 < args.length && Array.isArray(args[i + 1]) && args[i + 1][0] === '()') {
|
|
171
|
+
const merged = ['()', ['()', stmt[1]], args[i + 1][1] ?? null]
|
|
172
|
+
const t = transform(merged)
|
|
173
|
+
if (t != null) {
|
|
174
|
+
if (Array.isArray(t) && t[0] === ';') {
|
|
175
|
+
for (const s of t.slice(1)) { if (s != null) rest.push(s) }
|
|
176
|
+
} else {
|
|
177
|
+
rest.push(t)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
i++
|
|
181
|
+
continue
|
|
182
|
+
}
|
|
152
183
|
// Statement-form named function declaration: hoist directly (skip expression handler)
|
|
153
184
|
if (Array.isArray(stmt) && stmt[0] === 'function' && stmt[1]) {
|
|
154
185
|
hoisted.push(hoistFnDecl(stmt[1], stmt[2], stmt[3]))
|
|
155
186
|
continue
|
|
156
187
|
}
|
|
188
|
+
// Statement-form class declaration: bind the factory in place (not hoisted — TDZ)
|
|
189
|
+
if (Array.isArray(stmt) && stmt[0] === 'class' && stmt[1]) {
|
|
190
|
+
rest.push(['let', ['=', stmt[1], lowerClass(stmt[1], stmt[2], stmt[3])]])
|
|
191
|
+
continue
|
|
192
|
+
}
|
|
157
193
|
const t = transform(stmt)
|
|
158
194
|
if (t == null) continue
|
|
159
195
|
// Hoist function declarations to top of scope
|
|
@@ -174,13 +210,35 @@ function transformScope(node) {
|
|
|
174
210
|
// Hoist functions AFTER imports (imports must be processed first for scope resolution)
|
|
175
211
|
const imports = rest.filter(s => Array.isArray(s) && s[0] === 'import')
|
|
176
212
|
const nonImports = rest.filter(s => !(Array.isArray(s) && s[0] === 'import'))
|
|
177
|
-
const all = [...imports, ...hoisted, ...nonImports]
|
|
213
|
+
const all = dedupeRedecls([...imports, ...hoisted, ...nonImports])
|
|
178
214
|
return all.length === 0 ? null : all.length === 1 ? all[0] : [';', ...all]
|
|
179
215
|
}
|
|
180
216
|
|
|
181
217
|
return transform(node)
|
|
182
218
|
}
|
|
183
219
|
|
|
220
|
+
/**
|
|
221
|
+
* Drop redundant re-declarations of the same name within one scope's statement
|
|
222
|
+
* list. JS allows `function f(){} var f;`, `var x; var x;`, `var x = 1; var x;` —
|
|
223
|
+
* jzify lowers `function`→`const` and `var`→`let`, which would otherwise emit two
|
|
224
|
+
* bindings for one slot (and a typed-slot clash in codegen). The first declaration
|
|
225
|
+
* wins; a later redeclaration keeps only its initializer, as a plain assignment.
|
|
226
|
+
*/
|
|
227
|
+
function dedupeRedecls(stmts) {
|
|
228
|
+
const nameOf = s => Array.isArray(s) && (s[0] === 'let' || s[0] === 'const' || s[0] === 'var')
|
|
229
|
+
? (typeof s[1] === 'string' ? s[1]
|
|
230
|
+
: Array.isArray(s[1]) && s[1][0] === '=' && typeof s[1][1] === 'string' ? s[1][1] : null)
|
|
231
|
+
: null
|
|
232
|
+
const seen = new Set(), out = []
|
|
233
|
+
for (const s of stmts) {
|
|
234
|
+
const n = nameOf(s)
|
|
235
|
+
if (n == null) { out.push(s); continue }
|
|
236
|
+
if (seen.has(n)) { if (Array.isArray(s[1]) && s[1][0] === '=') out.push(['=', s[1][1], s[1][2]]); continue }
|
|
237
|
+
seen.add(n); out.push(s)
|
|
238
|
+
}
|
|
239
|
+
return out
|
|
240
|
+
}
|
|
241
|
+
|
|
184
242
|
/** Wrap function body for arrow conversion */
|
|
185
243
|
function wrapArrowBody(body) {
|
|
186
244
|
const t = transformScope(body)
|
|
@@ -211,6 +269,19 @@ function usesArguments(node) {
|
|
|
211
269
|
return false
|
|
212
270
|
}
|
|
213
271
|
|
|
272
|
+
// `arguments` is the implicit object only if the function body doesn't declare a
|
|
273
|
+
// local of that name. Scan the body's own statement list (not nested scopes) for
|
|
274
|
+
// `var/let/const arguments` — a regular `function` with `var arguments;` just has
|
|
275
|
+
// an ordinary local, no arguments object.
|
|
276
|
+
function bindsArguments(body) {
|
|
277
|
+
const isArgDecl = s => Array.isArray(s) && (s[0] === 'var' || s[0] === 'let' || s[0] === 'const') &&
|
|
278
|
+
s.slice(1).some(d => d === 'arguments' || (Array.isArray(d) && d[0] === '=' && d[1] === 'arguments'))
|
|
279
|
+
let n = body
|
|
280
|
+
if (Array.isArray(n) && n[0] === '{}') n = n[1]
|
|
281
|
+
if (Array.isArray(n) && n[0] === ';') return n.slice(1).some(isArgDecl)
|
|
282
|
+
return isArgDecl(n)
|
|
283
|
+
}
|
|
284
|
+
|
|
214
285
|
function renameArguments(node, to) {
|
|
215
286
|
if (node === 'arguments') return to
|
|
216
287
|
if (!Array.isArray(node)) return node
|
|
@@ -236,8 +307,19 @@ function paramList(params) {
|
|
|
236
307
|
return [params]
|
|
237
308
|
}
|
|
238
309
|
|
|
310
|
+
// Destructuring pattern as a parameter — `[a,b]` / `{a,b}` (optionally with a
|
|
311
|
+
// default). Plain `=` defaults and `...rest` are handled natively by emit, so
|
|
312
|
+
// they don't by themselves force lowering.
|
|
313
|
+
const isDestructurePat = p => Array.isArray(p) && (p[0] === '[]' || p[0] === '{}' || (p[0] === '=' && isDestructurePat(p[1])))
|
|
314
|
+
|
|
239
315
|
function lowerArguments(params, body) {
|
|
240
|
-
|
|
316
|
+
// A function body that declares its own `arguments` local: it's an ordinary
|
|
317
|
+
// variable, not the implicit object \u2014 rename it out of jz's reserved set,
|
|
318
|
+
// no rest param synthesized.
|
|
319
|
+
if (bindsArguments(body)) body = renameArguments(body, `\uE001arg${argsIdx++}`)
|
|
320
|
+
const paramsNeedLowering = paramList(params).some(isDestructurePat)
|
|
321
|
+
const usesArgsObj = usesArguments(params) || usesArguments(body)
|
|
322
|
+
if (!paramsNeedLowering && !usesArgsObj) return [params, body]
|
|
241
323
|
const name = `\uE001arg${argsIdx++}`
|
|
242
324
|
const decls = []
|
|
243
325
|
for (const [idx, param] of paramList(params).entries()) {
|
|
@@ -251,12 +333,120 @@ function lowerArguments(params, body) {
|
|
|
251
333
|
}
|
|
252
334
|
decls.push(['=', param, ['[]', name, [null, idx]]])
|
|
253
335
|
}
|
|
254
|
-
const renamed = renameArguments(body, name)
|
|
255
|
-
return [['()', ['...', name]], decls.length ? ['
|
|
336
|
+
const renamed = usesArgsObj ? renameArguments(body, name) : body
|
|
337
|
+
return [['()', ['...', name]], decls.length ? prependParamDecls(['let', ...decls], renamed) : renamed]
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function prependParamDecls(decl, body) {
|
|
341
|
+
if (Array.isArray(body) && body[0] === '{}') {
|
|
342
|
+
const inner = body[1]
|
|
343
|
+
if (Array.isArray(inner) && inner[0] === ';') return ['{}', [';', decl, ...inner.slice(1)]]
|
|
344
|
+
if (inner == null) return ['{}', decl]
|
|
345
|
+
return ['{}', [';', decl, inner]]
|
|
346
|
+
}
|
|
347
|
+
if (Array.isArray(body) && (body[0] === ';' || body[0] === 'return')) return [';', decl, body]
|
|
348
|
+
return ['{}', [';', decl, ['return', body]]]
|
|
256
349
|
}
|
|
257
350
|
|
|
258
351
|
const arrowParams = params => Array.isArray(params) && params[0] === '()' ? params : ['()', params]
|
|
259
352
|
|
|
353
|
+
// === class lowering ===
|
|
354
|
+
//
|
|
355
|
+
// A class is lowered to a factory arrow. Instance state is a plain object;
|
|
356
|
+
// methods are per-instance arrows capturing it (so `obj.m()` keeps working
|
|
357
|
+
// without a separate `this` argument); `this` is renamed to that object;
|
|
358
|
+
// `new C(a)` is already turned into `C(a)` by the `new` handler.
|
|
359
|
+
//
|
|
360
|
+
// class Point { x = 0; y; constructor(a,b){ this.x = a; this.y = b }
|
|
361
|
+
// dist(){ return Math.hypot(this.x, this.y) } }
|
|
362
|
+
// →
|
|
363
|
+
// let Point = (a, b) => {
|
|
364
|
+
// let selfN = { x: undefined, y: undefined,
|
|
365
|
+
// dist: () => Math.hypot(selfN.x, selfN.y) }
|
|
366
|
+
// selfN.x = 0 // field initializers, in declaration order
|
|
367
|
+
// selfN.x = a // then the constructor body
|
|
368
|
+
// selfN.y = b
|
|
369
|
+
// return selfN
|
|
370
|
+
// }
|
|
371
|
+
//
|
|
372
|
+
// Out of scope for now (rejected with a clear message): `extends`/`super`,
|
|
373
|
+
// `static` members, getters/setters, computed/private-via-`#` member names are
|
|
374
|
+
// kept as the literal key string `#name` (jz allows it).
|
|
375
|
+
let classIdx = 0
|
|
376
|
+
|
|
377
|
+
const classBodyItems = (body) =>
|
|
378
|
+
body == null ? [] : Array.isArray(body) && body[0] === ';' ? body.slice(1) : [body]
|
|
379
|
+
|
|
380
|
+
// Rename `this` → `to`, not crossing into a nested `function`/`class` (those
|
|
381
|
+
// rebind `this`); arrows inherit `this`, so they are crossed. Property *names*
|
|
382
|
+
// (`obj.this`, `{this: …}` value-side only) are left alone.
|
|
383
|
+
function renameThis(node, to) {
|
|
384
|
+
if (node === 'this') return to
|
|
385
|
+
if (!Array.isArray(node)) return node
|
|
386
|
+
if (node[0] === 'function' || node[0] === 'class') return node
|
|
387
|
+
if (node[0] === '.' || node[0] === '?.') return [node[0], renameThis(node[1], to), node[2]]
|
|
388
|
+
if (node[0] === ':') return [node[0], node[1], renameThis(node[2], to)]
|
|
389
|
+
return node.map(n => renameThis(n, to))
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function jzifyError(msg) { throw new Error(`jzify: ${msg}`) }
|
|
393
|
+
|
|
394
|
+
function lowerClass(name, heritage, body) {
|
|
395
|
+
if (heritage != null) jzifyError('`class … extends …` is not supported yet — flatten the hierarchy or compose explicitly')
|
|
396
|
+
let ctorParams = null, ctorBody = null
|
|
397
|
+
const methods = [], fields = []
|
|
398
|
+
for (const it of classBodyItems(body)) {
|
|
399
|
+
if (typeof it === 'string') { fields.push([it, null]); continue } // bare `x;`
|
|
400
|
+
if (!Array.isArray(it)) continue
|
|
401
|
+
if (it[0] === ':' && Array.isArray(it[2]) && it[2][0] === '=>') {
|
|
402
|
+
if (typeof it[1] !== 'string') jzifyError('computed class member names are not supported')
|
|
403
|
+
if (it[1] === 'constructor') { ctorParams = it[2][1]; ctorBody = it[2][2] }
|
|
404
|
+
else methods.push([it[1], it[2][1], it[2][2]])
|
|
405
|
+
continue
|
|
406
|
+
}
|
|
407
|
+
if (it[0] === '=') {
|
|
408
|
+
const lhs = it[1]
|
|
409
|
+
if (Array.isArray(lhs) && lhs[0] === 'static') jzifyError('`static` class members are not supported yet')
|
|
410
|
+
if (typeof lhs !== 'string') jzifyError('computed/destructured class fields are not supported')
|
|
411
|
+
fields.push([lhs, it[2]])
|
|
412
|
+
continue
|
|
413
|
+
}
|
|
414
|
+
if (it[0] === 'get' || it[0] === 'set') jzifyError('class getters/setters are not supported — jz objects have no accessors')
|
|
415
|
+
if (it[0] === 'static') jzifyError('`static` class members are not supported yet')
|
|
416
|
+
jzifyError(`unsupported class member ${JSON.stringify(it).slice(0, 60)}`)
|
|
417
|
+
}
|
|
418
|
+
const self = `self${classIdx++}`
|
|
419
|
+
const UNDEF = [] // jessie's node for `undefined`
|
|
420
|
+
// A class member body from jessie is a bare statement / `;`-sequence — wrap it
|
|
421
|
+
// in a `{}` block so the `=>` handler treats it as a function body, not an
|
|
422
|
+
// expression (an unwrapped `;`-seq arrow body produces malformed IR).
|
|
423
|
+
const block = b => Array.isArray(b) && b[0] === '{}' ? b : ['{}', b]
|
|
424
|
+
const usesThis = n => n === 'this' || (Array.isArray(n) && n[0] !== 'function' && n[0] !== 'class' && n.some(usesThis))
|
|
425
|
+
// Object literal: every declared field (its initializer inline when it doesn't
|
|
426
|
+
// touch `this`, else `undefined` and assigned below), every method as its
|
|
427
|
+
// self-capturing arrow. Declaring all fields up front fixes the object shape.
|
|
428
|
+
const litProps = [], deferred = []
|
|
429
|
+
for (const [fname, init] of fields) {
|
|
430
|
+
if (init != null && !usesThis(init)) litProps.push([':', fname, transform(init)])
|
|
431
|
+
else { litProps.push([':', fname, UNDEF]); if (init != null) deferred.push([fname, init]) }
|
|
432
|
+
}
|
|
433
|
+
for (const [mname, mparams, mbody] of methods)
|
|
434
|
+
litProps.push([':', mname, transform(['=>', mparams ?? ['()', null], block(renameThis(mbody, self))])])
|
|
435
|
+
const lit = ['{}', litProps.length === 0 ? null : litProps.length === 1 ? litProps[0] : [',', ...litProps]]
|
|
436
|
+
const stmts = [['let', ['=', self, lit]]]
|
|
437
|
+
// `this`-dependent field initializers run, in declaration order, before the ctor.
|
|
438
|
+
for (const [fname, init] of deferred)
|
|
439
|
+
stmts.push(['=', ['.', self, fname], transform(renameThis(init, self))])
|
|
440
|
+
if (ctorBody != null) {
|
|
441
|
+
let cb = transform(renameThis(ctorBody, self))
|
|
442
|
+
if (Array.isArray(cb) && cb[0] === '{}') cb = cb[1]
|
|
443
|
+
if (Array.isArray(cb) && cb[0] === ';') stmts.push(...cb.slice(1).filter(s => s != null))
|
|
444
|
+
else if (cb != null) stmts.push(cb)
|
|
445
|
+
}
|
|
446
|
+
stmts.push(['return', self])
|
|
447
|
+
return ['=>', arrowParams(ctorParams ?? ['()', null]), ['{}', [';', ...stmts]]]
|
|
448
|
+
}
|
|
449
|
+
|
|
260
450
|
const handlers = {
|
|
261
451
|
// Named IIFE: (function name(p){b})(a) → let name = arrow; name(a)
|
|
262
452
|
'()'(callee, ...rest) {
|
|
@@ -283,6 +473,16 @@ const handlers = {
|
|
|
283
473
|
return arrow
|
|
284
474
|
},
|
|
285
475
|
|
|
476
|
+
'=>'(params, body) {
|
|
477
|
+
const [p2, b2] = lowerArguments(params, body)
|
|
478
|
+
return ['=>', p2, transform(b2)]
|
|
479
|
+
},
|
|
480
|
+
|
|
481
|
+
// Class in expression position → its factory arrow. (A named class
|
|
482
|
+
// expression's own inner binding is dropped — rare; statement-form
|
|
483
|
+
// `class C {}` is handled by transformScope, which keeps the binding.)
|
|
484
|
+
'class'(name, heritage, body) { return lowerClass(name, heritage, body) },
|
|
485
|
+
|
|
286
486
|
// `var` is hoisted away before transform reaches here. If one slips through
|
|
287
487
|
// (e.g. raw subscript output without going via jzify entry/wrapArrowBody),
|
|
288
488
|
// fall back to treating it as `let`.
|
|
@@ -326,10 +526,15 @@ const handlers = {
|
|
|
326
526
|
|
|
327
527
|
// new → call (keep TypedArrays)
|
|
328
528
|
'new'(ctor, ...cargs) {
|
|
529
|
+
if (Array.isArray(ctor) && ctor[0] === '()' && Array.isArray(ctor[1]) && ctor[1][0] === '.') {
|
|
530
|
+
return ['()', ['.', transform(['new', ctor[1][1]]), ctor[1][2]], ...ctor.slice(2).map(transform)]
|
|
531
|
+
}
|
|
329
532
|
const name = typeof ctor === 'string' ? ctor : (Array.isArray(ctor) && ctor[0] === '()' ? ctor[1] : null)
|
|
330
|
-
if (typeof name === 'string' && TYPED_ARRAYS.has(name)) return ['new', transform(ctor), ...cargs.map(transform)]
|
|
533
|
+
if (typeof name === 'string' && (TYPED_ARRAYS.has(name) || name === 'Array')) return ['new', transform(ctor), ...cargs.map(transform)]
|
|
331
534
|
if (Array.isArray(ctor) && ctor[0] === '()') return transform(ctor)
|
|
332
|
-
|
|
535
|
+
// `new C(a)` → `C(a)`; `new C` (no parens) → `C()` — a 2-element `['()', X]`
|
|
536
|
+
// is grouping parens, so a no-arg call needs the explicit `null` arg slot.
|
|
537
|
+
return ['()', transform(ctor), ...(cargs.length ? cargs.map(transform) : [null])]
|
|
333
538
|
},
|
|
334
539
|
|
|
335
540
|
// instanceof → typeof / Array.isArray (jzify allows what strict mode prohibits)
|
|
@@ -363,10 +568,17 @@ const handlers = {
|
|
|
363
568
|
if (Array.isArray(inner) && inner[0] === 'function' && inner[1]) {
|
|
364
569
|
return ['export', hoistFnDecl(inner[1], inner[2], inner[3])]
|
|
365
570
|
}
|
|
571
|
+
// `export class C {}` → `export let C = factory`; named class keeps its binding.
|
|
572
|
+
if (Array.isArray(inner) && inner[0] === 'class' && inner[1]) {
|
|
573
|
+
return ['export', ['let', ['=', inner[1], lowerClass(inner[1], inner[2], inner[3])]]]
|
|
574
|
+
}
|
|
366
575
|
if (Array.isArray(inner) && inner[0] === 'default' && Array.isArray(inner[1]) && inner[1][0] === 'function' && inner[1][1]) {
|
|
367
576
|
const decl = hoistFnDecl(inner[1][1], inner[1][2], inner[1][3])
|
|
368
577
|
return [';', decl, ['export', ['default', inner[1][1]]]]
|
|
369
578
|
}
|
|
579
|
+
if (Array.isArray(inner) && inner[0] === 'default' && Array.isArray(inner[1]) && inner[1][0] === 'class' && inner[1][1]) {
|
|
580
|
+
return [';', ['let', ['=', inner[1][1], lowerClass(inner[1][1], inner[1][2], inner[1][3])]], ['export', ['default', inner[1][1]]]]
|
|
581
|
+
}
|
|
370
582
|
return ['export', transform(inner)]
|
|
371
583
|
},
|
|
372
584
|
}
|
|
@@ -380,6 +592,133 @@ function transform(node) {
|
|
|
380
592
|
return (h && h(...args)) ?? (h ? [op, ...args.map(transform)] : [op, ...args.map(transform)])
|
|
381
593
|
}
|
|
382
594
|
|
|
595
|
+
// Esbuild emits a small ESM helper:
|
|
596
|
+
//
|
|
597
|
+
// var __defProp = Object.defineProperty;
|
|
598
|
+
// var __export = (target, all) => {
|
|
599
|
+
// for (var name in all)
|
|
600
|
+
// __defProp(target, name, { get: all[name], enumerable: true });
|
|
601
|
+
// };
|
|
602
|
+
// __export(src_exports, { default: () => value });
|
|
603
|
+
// use(src_exports.default);
|
|
604
|
+
//
|
|
605
|
+
// Full descriptor/prototype semantics are outside JZ's fixed-shape object model.
|
|
606
|
+
// This pass instead recognizes the static helper pattern and rewrites reads of
|
|
607
|
+
// the synthetic export object to the real binding.
|
|
608
|
+
function foldStaticExportHelpers(ast) {
|
|
609
|
+
const body = astSeq(ast)
|
|
610
|
+
if (!body) return ast
|
|
611
|
+
|
|
612
|
+
const defPropAliases = new Set()
|
|
613
|
+
for (const stmt of body) {
|
|
614
|
+
if (Array.isArray(stmt) && stmt[0] === '=' && typeof stmt[1] === 'string' && isObjectDefineProperty(stmt[2]))
|
|
615
|
+
defPropAliases.add(stmt[1])
|
|
616
|
+
}
|
|
617
|
+
if (!defPropAliases.size) return ast
|
|
618
|
+
|
|
619
|
+
const helperNames = new Set()
|
|
620
|
+
for (const stmt of body) {
|
|
621
|
+
if (Array.isArray(stmt) && stmt[0] === '=' && typeof stmt[1] === 'string' &&
|
|
622
|
+
Array.isArray(stmt[2]) && stmt[2][0] === '=>' && containsDefinePropertyCall(stmt[2], defPropAliases))
|
|
623
|
+
helperNames.add(stmt[1])
|
|
624
|
+
}
|
|
625
|
+
if (!helperNames.size) return ast
|
|
626
|
+
|
|
627
|
+
const rewrites = new Map()
|
|
628
|
+
const removable = new Set()
|
|
629
|
+
for (const stmt of body) {
|
|
630
|
+
const ex = staticExportCall(stmt, helperNames)
|
|
631
|
+
if (!ex) continue
|
|
632
|
+
for (const [key, value] of ex.props) rewrites.set(`${ex.target}.${key}`, value)
|
|
633
|
+
removable.add(stmt)
|
|
634
|
+
}
|
|
635
|
+
if (!rewrites.size) return ast
|
|
636
|
+
|
|
637
|
+
const rewritten = body
|
|
638
|
+
.filter(stmt => !removable.has(stmt) && !isDefPropAliasAssign(stmt, defPropAliases) && !isExportHelperAssign(stmt, helperNames))
|
|
639
|
+
.map(stmt => replaceStaticExportReads(stmt, rewrites))
|
|
640
|
+
return rewritten.length === 0 ? null : rewritten.length === 1 ? rewritten[0] : [';', ...rewritten]
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function astSeq(ast) {
|
|
644
|
+
if (!Array.isArray(ast)) return null
|
|
645
|
+
return ast[0] === ';' ? ast.slice(1).filter(Boolean) : [ast]
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function isObjectDefineProperty(node) {
|
|
649
|
+
return Array.isArray(node) && node[0] === '.' && node[1] === 'Object' && node[2] === 'defineProperty'
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function isDefPropAliasAssign(stmt, aliases) {
|
|
653
|
+
return Array.isArray(stmt) && stmt[0] === '=' && aliases.has(stmt[1]) && isObjectDefineProperty(stmt[2])
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function isExportHelperAssign(stmt, helpers) {
|
|
657
|
+
return Array.isArray(stmt) && stmt[0] === '=' && helpers.has(stmt[1])
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function containsDefinePropertyCall(node, aliases) {
|
|
661
|
+
if (!Array.isArray(node)) return false
|
|
662
|
+
if (node[0] === '()' && (aliases.has(node[1]) || isObjectDefineProperty(node[1]))) return true
|
|
663
|
+
for (let i = 1; i < node.length; i++) if (containsDefinePropertyCall(node[i], aliases)) return true
|
|
664
|
+
return false
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function staticExportCall(stmt, helpers) {
|
|
668
|
+
if (!Array.isArray(stmt) || stmt[0] !== '()' || !helpers.has(stmt[1])) return null
|
|
669
|
+
const args = callArgs(stmt.slice(2))
|
|
670
|
+
if (args.length !== 2 || typeof args[0] !== 'string') return null
|
|
671
|
+
const props = objectProps(args[1])
|
|
672
|
+
if (!props) return null
|
|
673
|
+
const out = []
|
|
674
|
+
for (const prop of props) {
|
|
675
|
+
if (!Array.isArray(prop) || prop[0] !== ':' || typeof prop[1] !== 'string') return null
|
|
676
|
+
const value = getterReturnExpr(prop[2])
|
|
677
|
+
if (!value) return null
|
|
678
|
+
out.push([prop[1], value])
|
|
679
|
+
}
|
|
680
|
+
return { target: args[0], props: out }
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function callArgs(args) {
|
|
684
|
+
if (args.length === 1 && Array.isArray(args[0]) && args[0][0] === ',') return args[0].slice(1)
|
|
685
|
+
return args.filter(a => a != null)
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function objectProps(node) {
|
|
689
|
+
if (!Array.isArray(node) || node[0] !== '{}') return null
|
|
690
|
+
const body = node[1]
|
|
691
|
+
if (body == null) return []
|
|
692
|
+
if (Array.isArray(body) && body[0] === ',') return body.slice(1)
|
|
693
|
+
return [body]
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function getterReturnExpr(node) {
|
|
697
|
+
if (!Array.isArray(node) || node[0] !== '=>') return null
|
|
698
|
+
const params = paramList(node[1])
|
|
699
|
+
if (params.length !== 0) return null
|
|
700
|
+
const body = node[2]
|
|
701
|
+
if (Array.isArray(body) && body[0] === '{}' && Array.isArray(body[1]) && body[1][0] === 'return') return body[1][1]
|
|
702
|
+
if (Array.isArray(body) && body[0] === 'return') return body[1]
|
|
703
|
+
return body
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function replaceStaticExportReads(node, rewrites) {
|
|
707
|
+
if (node == null || typeof node !== 'object' || !Array.isArray(node)) return node
|
|
708
|
+
if ((node[0] === '.' || node[0] === '?.') && typeof node[1] === 'string' && typeof node[2] === 'string') {
|
|
709
|
+
const value = rewrites.get(`${node[1]}.${node[2]}`)
|
|
710
|
+
if (value) return cloneAst(value)
|
|
711
|
+
}
|
|
712
|
+
if (node[0] === ':') return [node[0], node[1], replaceStaticExportReads(node[2], rewrites)]
|
|
713
|
+
return node.map((part, i) => i === 0 ? part : replaceStaticExportReads(part, rewrites))
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function cloneAst(node) {
|
|
717
|
+
if (node == null || typeof node !== 'object') return node
|
|
718
|
+
if (!Array.isArray(node)) return node
|
|
719
|
+
return node.map(cloneAst)
|
|
720
|
+
}
|
|
721
|
+
|
|
383
722
|
/** Transform switch statement to if/else chain. */
|
|
384
723
|
let swIdx = 0
|
|
385
724
|
function transformSwitch(discriminant, cases) {
|
package/src/narrow.js
CHANGED
|
@@ -389,7 +389,8 @@ export default function narrowSignatures(programFacts, ast) {
|
|
|
389
389
|
// Safety:
|
|
390
390
|
// - exclude ARRAY (forwards on realloc — f64 NaN-box is a stable identity) and
|
|
391
391
|
// STRING (SSO vs heap dual encoding depends on ptr-type bits we'd drop).
|
|
392
|
-
// - exclude CLOSURE
|
|
392
|
+
// - exclude CLOSURE (aux carries funcIdx, needed for call_indirect) and TYPED
|
|
393
|
+
// (aux carries element-type, handled separately by applyTypedPointerParamAbi).
|
|
393
394
|
// - exclude params with defaults (nullish sentinel needs the f64 NaN space).
|
|
394
395
|
// - exclude rest position (array pack/unpack stays f64).
|
|
395
396
|
applyPointerParamAbi(paramReps, valueUsed)
|