jz 0.8.0 → 0.9.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 +56 -9
- package/bench/README.md +121 -50
- package/bench/bench.svg +27 -27
- package/cli.js +3 -1
- package/dist/interop.js +1 -1
- package/dist/jz.js +7541 -6178
- package/index.js +165 -74
- package/interop.js +189 -17
- package/jzify/arguments.js +32 -1
- package/jzify/async.js +409 -0
- package/jzify/classes.js +136 -18
- package/jzify/generators.js +639 -0
- package/jzify/hoist-vars.js +73 -1
- package/jzify/index.js +123 -4
- package/jzify/names.js +1 -0
- package/jzify/transform.js +239 -1
- package/layout.js +48 -3
- package/module/array.js +318 -43
- package/module/atomics.js +144 -0
- package/module/collection.js +1429 -155
- package/module/core.js +431 -40
- package/module/date.js +142 -120
- package/module/fs.js +144 -0
- package/module/function.js +6 -3
- package/module/index.js +4 -1
- package/module/json.js +270 -49
- package/module/math.js +1032 -145
- package/module/number.js +532 -163
- package/module/object.js +353 -95
- package/module/regex.js +157 -7
- package/module/schema.js +100 -2
- package/module/string.js +428 -93
- package/module/typedarray.js +264 -72
- package/module/web.js +36 -0
- package/package.json +5 -5
- package/src/abi/string.js +82 -11
- package/src/ast.js +11 -5
- package/src/autoload.js +28 -1
- package/src/bridge.js +7 -2
- package/src/compile/analyze-scans.js +22 -7
- package/src/compile/analyze.js +136 -30
- package/src/compile/dyn-closure-tables.js +277 -0
- package/src/compile/emit-assign.js +281 -15
- package/src/compile/emit.js +1055 -70
- package/src/compile/index.js +277 -29
- package/src/compile/infer.js +42 -4
- package/src/compile/inplace-store.js +329 -0
- package/src/compile/loop-recurrence.js +167 -0
- package/src/compile/narrow.js +555 -18
- package/src/compile/peel-stencil.js +5 -0
- package/src/compile/plan/index.js +45 -3
- package/src/compile/plan/inline.js +8 -1
- package/src/compile/plan/literals.js +39 -0
- package/src/compile/plan/scope.js +430 -23
- package/src/compile/program-facts.js +732 -38
- package/src/ctx.js +64 -9
- package/src/helper-counters.js +7 -1
- package/src/ir.js +113 -5
- package/src/kind-traits.js +66 -3
- package/src/kind.js +84 -12
- package/src/op-policy.js +7 -4
- package/src/optimize/index.js +1179 -704
- package/src/optimize/recurse.js +2 -2
- package/src/optimize/vectorize.js +982 -67
- package/src/prepare/index.js +809 -67
- package/src/prepare/math-kernel.js +331 -0
- package/src/prepare/pre-eval.js +714 -0
- package/src/snapshot.js +194 -0
- package/src/type.js +1170 -56
- package/src/wat/assemble.js +403 -65
- package/src/wat/codegen.js +74 -14
- package/transform.js +113 -4
- package/wasi.js +3 -0
package/jzify/hoist-vars.js
CHANGED
|
@@ -5,8 +5,21 @@
|
|
|
5
5
|
|
|
6
6
|
import { JZ_BLOCK_OPS } from '../src/ast.js'
|
|
7
7
|
|
|
8
|
+
// A `'[]'`-tagged node is ambiguous pre-prepare(): an array literal/destructure
|
|
9
|
+
// pattern (`[a, b]` → `['[]', commaSeqOrSingleElem]`, length ≤ 2 — empty `[]`
|
|
10
|
+
// is length 1, one element is length 2, 2+ elements comma-wrap into that one
|
|
11
|
+
// slot) and an element-access expression (`arr[i]` → `['[]', receiver, index]`,
|
|
12
|
+
// ALWAYS exactly length 3 — two separate args, never comma-wrapped) share the
|
|
13
|
+
// tag until prepare() splits them into `'['` (literal) vs `'[]'` (access).
|
|
14
|
+
// Length disambiguates: without it, `arr[i] = v` misclassifies as a
|
|
15
|
+
// destructuring assignment and gets walked as a pattern (`arr`/`i` treated as
|
|
16
|
+
// binding targets) instead of falling through to the plain-assignment path.
|
|
17
|
+
// Native jzify happens to reconstruct byte-identical IR either way for this
|
|
18
|
+
// shape (both the pattern-walk and the generic-transform fallback are no-ops
|
|
19
|
+
// on a receiver-name + literal-index pair) — masking it there; the self-hosted
|
|
20
|
+
// kernel exercises the (wrong) pattern-walk's own compiled path and diverges.
|
|
8
21
|
export const isDestructurePat = p =>
|
|
9
|
-
Array.isArray(p) && (p[0] === '[]' || p[0] === '{}' || (p[0] === '=' && isDestructurePat(p[1])))
|
|
22
|
+
Array.isArray(p) && ((p[0] === '[]' && p.length !== 3) || p[0] === '{}' || (p[0] === '=' && isDestructurePat(p[1])))
|
|
10
23
|
|
|
11
24
|
export function hoistVars(node, names) {
|
|
12
25
|
if (node == null || !Array.isArray(node)) return node
|
|
@@ -28,6 +41,10 @@ export function hoistVars(node, names) {
|
|
|
28
41
|
if (Array.isArray(lhs) && lhs[0] === 'var' && typeof lhs[1] === 'string' && lhs.length === 2) {
|
|
29
42
|
names.add(lhs[1])
|
|
30
43
|
lhs = lhs[1]
|
|
44
|
+
} else if (Array.isArray(lhs) && lhs[0] === 'var' && lhs.length === 2 && isDestructurePat(lhs[1])) {
|
|
45
|
+
// `for (var [a, b] of …)` — hoist the pattern's bindings, keep an
|
|
46
|
+
// assignment-form head (the generic var branch DROPPED the declarator).
|
|
47
|
+
lhs = hoistVarPattern(lhs[1], names)
|
|
31
48
|
} else {
|
|
32
49
|
lhs = hoistVars(lhs, names)
|
|
33
50
|
}
|
|
@@ -53,6 +70,12 @@ export function hoistVars(node, names) {
|
|
|
53
70
|
(head[1][0] === 'in' || head[1][0] === 'of') && typeof head[1][1] === 'string') {
|
|
54
71
|
names.add(head[1][1])
|
|
55
72
|
h2 = [head[1][0], head[1][1], hoistVars(head[1][2], names)]
|
|
73
|
+
} else if (Array.isArray(head) && head[0] === 'var' && Array.isArray(head[1]) &&
|
|
74
|
+
(head[1][0] === 'in' || head[1][0] === 'of') && isDestructurePat(head[1][1])) {
|
|
75
|
+
// `for (var [a, b] of …)` / `for (var {x} in …)` — hoist the pattern's
|
|
76
|
+
// bindings, keep an assignment-form head (a silently DROPPED head here
|
|
77
|
+
// was the "'y' is not in scope" class).
|
|
78
|
+
h2 = [head[1][0], hoistVarPattern(head[1][1], names), hoistVars(head[1][2], names)]
|
|
56
79
|
} else if (Array.isArray(head) && head[0] === ';') {
|
|
57
80
|
h2 = [';']
|
|
58
81
|
for (let i = 1; i < head.length; i++) h2.push(hoistVars(head[i], names))
|
|
@@ -61,6 +84,33 @@ export function hoistVars(node, names) {
|
|
|
61
84
|
}
|
|
62
85
|
return ['for', h2, hoistVars(node[2], names)]
|
|
63
86
|
}
|
|
87
|
+
// `export var x = 1, y` — the var splits into a hoisted `let` + in-place
|
|
88
|
+
// assignment, so the `export` keyword can't stay on the declarator
|
|
89
|
+
// (`export x = 1` is not JS). Assignments stay in place; the export moves
|
|
90
|
+
// to a clause: `x = 1; export { x, y }` — live bindings, same semantics.
|
|
91
|
+
// A function-valued declarator instead keeps decl+init together in place
|
|
92
|
+
// (`export let f = fn`) so the compiler sees a function binding, not a
|
|
93
|
+
// closure-valued global — mirrors the `;`-handler's arrow-in-place rule.
|
|
94
|
+
if (op === 'export' && Array.isArray(node[1]) && node[1][0] === 'var') {
|
|
95
|
+
const isFnInit = v => Array.isArray(v) && (v[0] === '=>' || v[0] === 'function')
|
|
96
|
+
const stmts = [], exported = []
|
|
97
|
+
for (let i = 1; i < node[1].length; i++) {
|
|
98
|
+
const d = node[1][i]
|
|
99
|
+
if (typeof d === 'string') { names.add(d); exported.push(d) }
|
|
100
|
+
else if (Array.isArray(d) && d[0] === '=' && typeof d[1] === 'string') {
|
|
101
|
+
if (isFnInit(d[2])) { stmts.push(['export', ['let', ['=', d[1], hoistVars(d[2], names)]]]); continue }
|
|
102
|
+
names.add(d[1]); exported.push(d[1])
|
|
103
|
+
stmts.push(['=', d[1], hoistVars(d[2], names)])
|
|
104
|
+
} else if (Array.isArray(d) && d[0] === '=' && isDestructurePat(d[1])) {
|
|
105
|
+
const local = new Set()
|
|
106
|
+
stmts.push(['=', hoistVarPattern(d[1], local), hoistVars(d[2], names)])
|
|
107
|
+
for (const n of local) { names.add(n); exported.push(n) }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (exported.length) stmts.push(['export', ['{}', exported.length === 1 ? exported[0] : [',', ...exported]]])
|
|
111
|
+
if (!stmts.length) return null
|
|
112
|
+
return stmts.length === 1 ? stmts[0] : [';', ...stmts]
|
|
113
|
+
}
|
|
64
114
|
if (op === 'var') {
|
|
65
115
|
const decls = []
|
|
66
116
|
for (let i = 1; i < node.length; i++) {
|
|
@@ -69,6 +119,13 @@ export function hoistVars(node, names) {
|
|
|
69
119
|
if (Array.isArray(d) && d[0] === '=' && typeof d[1] === 'string') {
|
|
70
120
|
names.add(d[1])
|
|
71
121
|
decls.push(['=', d[1], hoistVars(d[2], names)])
|
|
122
|
+
continue
|
|
123
|
+
}
|
|
124
|
+
// `var [a, b] = src` / `var {x} = src` — hoist every pattern binding,
|
|
125
|
+
// keep the declarator as an assignment-form destructure (a silently
|
|
126
|
+
// DROPPED declarator here was the "'b' is not in scope" class).
|
|
127
|
+
if (Array.isArray(d) && d[0] === '=' && isDestructurePat(d[1])) {
|
|
128
|
+
decls.push(['=', hoistVarPattern(d[1], names), hoistVars(d[2], names)])
|
|
72
129
|
}
|
|
73
130
|
}
|
|
74
131
|
if (decls.length === 0) return null
|
|
@@ -128,6 +185,21 @@ export function hoistPattern(node, names) {
|
|
|
128
185
|
return hoistVars(node, names)
|
|
129
186
|
}
|
|
130
187
|
|
|
188
|
+
// `var`-declared pattern: every bare leaf in binding position is a hoisted
|
|
189
|
+
// name (the declarator itself becomes a plain assignment). let/const patterns
|
|
190
|
+
// keep hoistPattern above — their names are declared by the surviving `let`,
|
|
191
|
+
// so collecting them here would predeclare duplicates.
|
|
192
|
+
function hoistVarPattern(node, names) {
|
|
193
|
+
if (typeof node === 'string') { names.add(node); return node }
|
|
194
|
+
if (node == null || !Array.isArray(node)) return node
|
|
195
|
+
const op = node[0]
|
|
196
|
+
if (op === '=') return ['=', hoistVarPattern(node[1], names), hoistVars(node[2], names)]
|
|
197
|
+
if (op === ':') return [':', hoistVars(node[1], names), hoistVarPattern(node[2], names)]
|
|
198
|
+
if (op === '...') return ['...', hoistVarPattern(node[1], names)]
|
|
199
|
+
if (op === '[]' || op === '{}' || op === ',') return [op, ...node.slice(1).map(n => hoistVarPattern(n, names))]
|
|
200
|
+
return hoistVars(node, names)
|
|
201
|
+
}
|
|
202
|
+
|
|
131
203
|
export function prependDecls(body, names) {
|
|
132
204
|
const decl = ['let', ...names]
|
|
133
205
|
if (Array.isArray(body) && body[0] === ';') return [';', decl, ...body.slice(1)]
|
package/jzify/index.js
CHANGED
|
@@ -8,13 +8,16 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { JZIFY_CLASS_ERRORS as JC } from '../src/op-policy.js'
|
|
11
|
+
import { parse } from '../src/parse.js'
|
|
12
|
+
import { createAsyncLowering, ASYNC_RUNTIME, ASYNC_GEN_RUNTIME } from './async.js'
|
|
11
13
|
import { createNames } from './names.js'
|
|
12
14
|
import { foldStaticExportHelpers, foldStaticBundlerHelpers, canonicalizeObjectIdioms } from './bundler.js'
|
|
13
15
|
import { createSwitchLowering, normalizeCaseBody } from './switch.js'
|
|
14
|
-
import { createClassLowering } from './classes.js'
|
|
16
|
+
import { createClassLowering, foldPseudoClassical } from './classes.js'
|
|
15
17
|
import { hoistVars, prependDecls } from './hoist-vars.js'
|
|
16
18
|
import { createArgumentsLowering } from './arguments.js'
|
|
17
|
-
import { createTransform } from './transform.js'
|
|
19
|
+
import { createTransform, bindGenerators } from './transform.js'
|
|
20
|
+
import { createGeneratorLowering, ITER_HELPERS_RUNTIME, ITER_ARR_RUNTIME } from './generators.js'
|
|
18
21
|
|
|
19
22
|
const names = createNames()
|
|
20
23
|
const { lowerArguments, transformPattern, bindTransform } = createArgumentsLowering(names)
|
|
@@ -34,9 +37,72 @@ let transform, transformScope
|
|
|
34
37
|
}))
|
|
35
38
|
bindTransform(transform)
|
|
36
39
|
|
|
37
|
-
|
|
40
|
+
const constStrings = new Map()
|
|
41
|
+
;({ lowerClass, lowerObjectLiteralThis, lowerArrayConstructor } = createClassLowering({ transform, names, JC, constStrings }))
|
|
42
|
+
const generatorNames = new Set()
|
|
43
|
+
// Program mints iterator objects (generators anywhere, hand-rolled `next()`
|
|
44
|
+
// members, `[Symbol.iterator]` methods) — gates the for-of protocol fork so
|
|
45
|
+
// programs without iterator producers compile byte-identically.
|
|
46
|
+
const iterProto = { on: false }
|
|
47
|
+
const genErr = (msg) => { throw new Error('jzify: ' + msg) }
|
|
48
|
+
const { lowerGenerator, desugarForOfGenerator, desugarForOfProtocol, unwindChain, fuseTerminal, fusedLoop, isTerminal } = createGeneratorLowering({ transform, err: genErr, generatorNames, genTemp: (t) => names.genTemp(t), iterProto })
|
|
49
|
+
const { lowerAsync, lowerAsyncGen, noteAsync, asyncUsed, agenUsed, resetAsync } = createAsyncLowering({ genTemp: (t) => names.genTemp(t), err: genErr })
|
|
50
|
+
bindGenerators({ lowerGenerator, desugarForOfGenerator, desugarForOfProtocol, lowerAsync, lowerAsyncGen, noteAsync, generatorNames, iterProto, unwindChain, fuseTerminal, fusedLoop, isTerminal })
|
|
38
51
|
transformSwitch = createSwitchLowering(transform, names)
|
|
39
52
|
|
|
53
|
+
// Spread normalization for iterator values — injected only when a spread site
|
|
54
|
+
// wrapped in __drain (iterProto.drain). Arrays/strings/Sets/Maps pass through
|
|
55
|
+
// untouched (the existing spread machinery owns them); machines and
|
|
56
|
+
// @@iterator providers materialize.
|
|
57
|
+
const ITER_RUNTIME = `
|
|
58
|
+
let __it_drain = (v) => {
|
|
59
|
+
if (v == null) return v
|
|
60
|
+
if (typeof v === 'object' && v['@@iterator'] != null) v = v['@@iterator']()
|
|
61
|
+
if (typeof v !== 'object' || v.next == null) return v
|
|
62
|
+
let r = v.next(), a = []
|
|
63
|
+
while (!r.done) { a.push(r.value); r = v.next() }
|
|
64
|
+
return a
|
|
65
|
+
}
|
|
66
|
+
`
|
|
67
|
+
|
|
68
|
+
const isSymbolWellKnown = (n, which) => Array.isArray(n) && n[0] === '.' && n[1] === 'Symbol' && n[2] === which
|
|
69
|
+
const WELL_KNOWN = { iterator: '@@iterator', dispose: '@@dispose', asyncIterator: '@@asyncIterator' }
|
|
70
|
+
// Iterator-helper method names (ES2025) — a CALL of one of these on any
|
|
71
|
+
// receiver, in a program that mints iterators, gates decorated generator
|
|
72
|
+
// objects (__it_mk). Fusable chains still fuse; this covers value positions.
|
|
73
|
+
const ITER_HELPER_NAMES = new Set(['map', 'filter', 'take', 'drop', 'flatMap',
|
|
74
|
+
'toArray', 'reduce', 'forEach', 'some', 'every', 'find'])
|
|
75
|
+
// Entry walk, two jobs in one pass:
|
|
76
|
+
// 1. Canonicalize well-known-symbol shapes to reserved literal props — a
|
|
77
|
+
// fixed-shape object has no symbol slots, so `[Symbol.iterator]` becomes
|
|
78
|
+
// the '@@iterator' prop in BOTH key position (computed member/method) and
|
|
79
|
+
// access position (`x[Symbol.iterator]`).
|
|
80
|
+
// 2. Detect iterator producers for the protocol-fork gate, and helper-method
|
|
81
|
+
// use / `instanceof Iterator` for the decorated-iterator gate.
|
|
82
|
+
function canonSymbols(node) {
|
|
83
|
+
if (!Array.isArray(node)) return node
|
|
84
|
+
const [op] = node
|
|
85
|
+
if (op === 'function*') iterProto.on = true
|
|
86
|
+
if (op === ':' && (node[1] === 'next' || node[1] === '@@iterator') &&
|
|
87
|
+
Array.isArray(node[2]) && (node[2][0] === '=>' || node[2][0] === 'function' || node[2][0] === 'function*'))
|
|
88
|
+
iterProto.on = true
|
|
89
|
+
if (op === '()' && Array.isArray(node[1]) && node[1][0] === '.' && ITER_HELPER_NAMES.has(node[1][2]))
|
|
90
|
+
iterProto.helpers = true
|
|
91
|
+
if (op === 'instanceof' && node[2] === 'Iterator') iterProto.helpers = true
|
|
92
|
+
// computed key: [':', ['[]', Symbol.X], value]
|
|
93
|
+
if (op === ':' && Array.isArray(node[1]) && node[1][0] === '[]' && node[1].length === 2) {
|
|
94
|
+
for (const [k, prop] of Object.entries(WELL_KNOWN))
|
|
95
|
+
if (isSymbolWellKnown(node[1][1], k)) { node[1] = prop; if (prop === '@@iterator') iterProto.on = true }
|
|
96
|
+
}
|
|
97
|
+
// access: ['[]', obj, Symbol.X] → ['.', obj, '@@X']
|
|
98
|
+
if (op === '[]' && node.length === 3) {
|
|
99
|
+
for (const [k, prop] of Object.entries(WELL_KNOWN))
|
|
100
|
+
if (isSymbolWellKnown(node[2], k)) { node[0] = '.'; node[2] = prop }
|
|
101
|
+
}
|
|
102
|
+
for (let i = 1; i < node.length; i++) canonSymbols(node[i])
|
|
103
|
+
return node
|
|
104
|
+
}
|
|
105
|
+
|
|
40
106
|
/**
|
|
41
107
|
* Transform AST in-place. Returns transformed AST.
|
|
42
108
|
* @param {Array} ast - subscript/jessie parsed AST
|
|
@@ -44,8 +110,61 @@ transformSwitch = createSwitchLowering(transform, names)
|
|
|
44
110
|
*/
|
|
45
111
|
export default function jzify(ast) {
|
|
46
112
|
names.reset()
|
|
113
|
+
// Module-scope `const K = 'str'` bindings — lets class lowering fold computed
|
|
114
|
+
// member names `[K]() {}` (const guarantees the binding never changes).
|
|
115
|
+
constStrings.clear()
|
|
116
|
+
generatorNames.clear()
|
|
117
|
+
iterProto.on = false
|
|
118
|
+
iterProto.helpers = false
|
|
119
|
+
iterProto.helpersUsed = false
|
|
120
|
+
iterProto.arr = false
|
|
121
|
+
iterProto.fromUsed = false
|
|
122
|
+
ast = canonSymbols(ast)
|
|
123
|
+
if (Array.isArray(ast)) {
|
|
124
|
+
const stmts = ast[0] === ';' ? ast.slice(1) : [ast]
|
|
125
|
+
for (const st of stmts) {
|
|
126
|
+
if (Array.isArray(st) && st[0] === 'function*' && typeof st[1] === 'string' && st[1]) generatorNames.add(st[1])
|
|
127
|
+
if (Array.isArray(st) && st[0] === 'export' && Array.isArray(st[1]) && st[1][0] === 'function*' && st[1][1]) generatorNames.add(st[1][1])
|
|
128
|
+
if (!Array.isArray(st) || st[0] !== 'const') continue
|
|
129
|
+
for (let i = 1; i < st.length; i++) {
|
|
130
|
+
const d = st[i]
|
|
131
|
+
if (Array.isArray(d) && d[0] === '=' && typeof d[1] === 'string' &&
|
|
132
|
+
Array.isArray(d[2]) && d[2][0] == null && typeof d[2][1] === 'string')
|
|
133
|
+
constStrings.set(d[1], d[2][1])
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
47
137
|
const hoisted = new Set()
|
|
48
138
|
ast = hoistVars(ast, hoisted)
|
|
49
139
|
if (hoisted.size) ast = prependDecls(ast, hoisted)
|
|
50
|
-
|
|
140
|
+
if (Array.isArray(ast) && ast[0] === ';') ast = [';', ...foldPseudoClassical(ast.slice(1))]
|
|
141
|
+
resetAsync()
|
|
142
|
+
iterProto.drain = false
|
|
143
|
+
let out = transformScope(ast)
|
|
144
|
+
const prepend = (src) => {
|
|
145
|
+
// runtimes ride the same well-known-symbol canonicalization as user code
|
|
146
|
+
// ([Symbol.iterator] → '@@iterator' in key/access/assign positions) — the
|
|
147
|
+
// dot-canonical form keeps writes and prehashed dot-reads on ONE path.
|
|
148
|
+
const rt = transformScope(canonSymbols(parse(src)))
|
|
149
|
+
const rtStmts = Array.isArray(rt) && rt[0] === ';' ? rt.slice(1) : [rt]
|
|
150
|
+
const outStmts = Array.isArray(out) && out[0] === ';' ? out.slice(1) : [out]
|
|
151
|
+
out = [';', ...rtStmts, ...outStmts]
|
|
152
|
+
}
|
|
153
|
+
// spread-of-iterator sites wrapped in __drain → splice the helper
|
|
154
|
+
// (pass-through for arrays/strings; materializes machines/providers).
|
|
155
|
+
if (iterProto.drain) prepend(ITER_RUNTIME)
|
|
156
|
+
// Array.from-over-iterators sites → splice __it_arr (materialize/copy).
|
|
157
|
+
if (iterProto.arr) prepend(ITER_ARR_RUNTIME)
|
|
158
|
+
// helper-bearing generator mints (__it_mk) or literal-receiver
|
|
159
|
+
// [Symbol.iterator]() mints (__it_from) → splice the decorated-iterator
|
|
160
|
+
// factory. Programs without value-position helpers never take this shape.
|
|
161
|
+
if (iterProto.helpersUsed || iterProto.fromUsed) prepend(ITER_HELPERS_RUNTIME)
|
|
162
|
+
// async generators → splice the tagged-yield driver (before the promise
|
|
163
|
+
// runtime prepend so it lands AFTER it in module order — it calls __p_*).
|
|
164
|
+
if (agenUsed()) prepend(ASYNC_GEN_RUNTIME)
|
|
165
|
+
// async somewhere in the program → splice the plain-jz promise runtime
|
|
166
|
+
// (microtask queue, __async_run driver, promise shape + boundary readers)
|
|
167
|
+
// ahead of user code. Sync programs never reach this — byte-identical.
|
|
168
|
+
if (asyncUsed()) prepend(ASYNC_RUNTIME)
|
|
169
|
+
return foldStaticBundlerHelpers(foldStaticExportHelpers(canonicalizeObjectIdioms(out)))
|
|
51
170
|
}
|
package/jzify/names.js
CHANGED
package/jzify/transform.js
CHANGED
|
@@ -73,6 +73,9 @@ const arrowParams = params => Array.isArray(params) && params[0] === '()' ? para
|
|
|
73
73
|
* @param {() => Function} opts.lowerObjectLiteralThis
|
|
74
74
|
* @param {() => Function} opts.lowerArrayConstructor
|
|
75
75
|
*/
|
|
76
|
+
let _gen = null
|
|
77
|
+
export const bindGenerators = (g) => { _gen = g }
|
|
78
|
+
|
|
76
79
|
export function createTransform(opts) {
|
|
77
80
|
const { names, lowerArguments, transformPattern, normalizeCaseBody, transformSwitch } = opts
|
|
78
81
|
const lowerClass = (...a) => opts.lowerClass()(...a)
|
|
@@ -103,13 +106,42 @@ export function createTransform(opts) {
|
|
|
103
106
|
return decl
|
|
104
107
|
}
|
|
105
108
|
|
|
109
|
+
// `using x = res` (ERM): bind, resolve [Symbol.dispose] up front (TypeError
|
|
110
|
+
// if absent on a non-null resource — spec checks at binding), then wrap the
|
|
111
|
+
// REST of the scope in try/finally calling it. Multiple resources nest —
|
|
112
|
+
// LIFO disposal falls out of the nesting. Divergence (documented): if both
|
|
113
|
+
// the body and a dispose throw, the dispose error propagates (no
|
|
114
|
+
// SuppressedError aggregation).
|
|
115
|
+
function lowerUsing(declarators, remaining) {
|
|
116
|
+
const NULL = [null, null]
|
|
117
|
+
const dispose = (name) => ['if', ['!=', name, NULL], ['()', ['.', name, '@@dispose'], null]]
|
|
118
|
+
let inner = remaining.length ? transformScope([';', ...remaining]) : null
|
|
119
|
+
for (let k = declarators.length - 1; k >= 0; k--) {
|
|
120
|
+
const [, name, init] = declarators[k]
|
|
121
|
+
inner = [';',
|
|
122
|
+
['let', ['=', name, transform(init)]],
|
|
123
|
+
['if', ['&&', ['!=', name, NULL], ['==', ['.', name, '@@dispose'], NULL]],
|
|
124
|
+
['throw', [null, 'using: value has no [Symbol.dispose]() method']]],
|
|
125
|
+
['try', inner ?? ['{}', null], ['finally', dispose(name)]],
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
return inner
|
|
129
|
+
}
|
|
130
|
+
|
|
106
131
|
function transformScope(node) {
|
|
107
132
|
if (!Array.isArray(node)) return transform(node)
|
|
108
133
|
|
|
109
134
|
const [op, ...args] = node
|
|
110
135
|
|
|
111
136
|
if (op === 'function' && args[0]) return hoistFnDecl(...args)
|
|
137
|
+
if (op === 'function*' && args[0] && _gen)
|
|
138
|
+
return ['const', ['=', args[0], _gen.lowerGenerator(args[1], args[2])]]
|
|
139
|
+
if (op === 'async' && Array.isArray(args[0]) && args[0][0] === 'function' && args[0][1] && _gen?.lowerAsync)
|
|
140
|
+
return ['const', ['=', args[0][1], transform(_gen.lowerAsync(args[0][2], args[0][3]))]]
|
|
141
|
+
if (op === 'async' && Array.isArray(args[0]) && args[0][0] === 'function*' && args[0][1] && _gen?.lowerAsyncGen)
|
|
142
|
+
return ['const', ['=', args[0][1], transform(_gen.lowerAsyncGen(args[0][2], args[0][3]))]]
|
|
112
143
|
if (op === 'class' && args[0]) return ['let', ['=', args[0], lowerClass(...args)]]
|
|
144
|
+
if (op === 'using') return lowerUsing(args, [])
|
|
113
145
|
|
|
114
146
|
if (op === ';') {
|
|
115
147
|
const hoisted = [], rest = []
|
|
@@ -119,10 +151,41 @@ export function createTransform(opts) {
|
|
|
119
151
|
hoisted.push(hoistFnDecl(stmt[1], stmt[2], stmt[3]))
|
|
120
152
|
continue
|
|
121
153
|
}
|
|
154
|
+
if (Array.isArray(stmt) && stmt[0] === 'function*' && stmt[1] && _gen) {
|
|
155
|
+
hoisted.push(['const', ['=', stmt[1], _gen.lowerGenerator(stmt[2], stmt[3])]])
|
|
156
|
+
continue
|
|
157
|
+
}
|
|
158
|
+
// async function DECLARATION — hoists like any function declaration.
|
|
159
|
+
if (Array.isArray(stmt) && stmt[0] === 'async' && Array.isArray(stmt[1]) &&
|
|
160
|
+
stmt[1][0] === 'function' && stmt[1][1] && _gen?.lowerAsync) {
|
|
161
|
+
hoisted.push(['const', ['=', stmt[1][1], transform(_gen.lowerAsync(stmt[1][2], stmt[1][3]))]])
|
|
162
|
+
continue
|
|
163
|
+
}
|
|
164
|
+
// async GENERATOR declaration — same hoisting, tagged-yield machine.
|
|
165
|
+
if (Array.isArray(stmt) && stmt[0] === 'async' && Array.isArray(stmt[1]) &&
|
|
166
|
+
stmt[1][0] === 'function*' && stmt[1][1] && _gen?.lowerAsyncGen) {
|
|
167
|
+
hoisted.push(['const', ['=', stmt[1][1], transform(_gen.lowerAsyncGen(stmt[1][2], stmt[1][3]))]])
|
|
168
|
+
continue
|
|
169
|
+
}
|
|
122
170
|
if (Array.isArray(stmt) && stmt[0] === 'class' && stmt[1]) {
|
|
123
171
|
rest.push(['let', ['=', stmt[1], lowerClass(stmt[1], stmt[2], stmt[3])]])
|
|
124
172
|
continue
|
|
125
173
|
}
|
|
174
|
+
// `using` consumes the REST of the scope into its try body (disposal
|
|
175
|
+
// runs at scope exit however the scope exits).
|
|
176
|
+
if (Array.isArray(stmt) && stmt[0] === 'using') {
|
|
177
|
+
rest.push(lowerUsing(stmt.slice(1), args.slice(i + 1)))
|
|
178
|
+
break
|
|
179
|
+
}
|
|
180
|
+
// Labeled BLOCK in statement position (`lbl: { … }`): unambiguous here —
|
|
181
|
+
// a ':' STATEMENT can't be an object prop. '{}' stays out of
|
|
182
|
+
// LABEL_BODY_OPS (the expression-context disambiguator), so literal
|
|
183
|
+
// props `k: {…}` never label.
|
|
184
|
+
if (Array.isArray(stmt) && stmt[0] === ':' && typeof stmt[1] === 'string' &&
|
|
185
|
+
Array.isArray(stmt[2]) && stmt[2][0] === '{}') {
|
|
186
|
+
rest.push(['label', stmt[1], transform(stmt[2])])
|
|
187
|
+
continue
|
|
188
|
+
}
|
|
126
189
|
const t = transform(stmt)
|
|
127
190
|
if (t == null) continue
|
|
128
191
|
if (Array.isArray(t) && t[0] === 'const' && t._hoisted) {
|
|
@@ -154,12 +217,98 @@ export function createTransform(opts) {
|
|
|
154
217
|
return transform(node)
|
|
155
218
|
}
|
|
156
219
|
|
|
220
|
+
// Promise statics → the injected plain-jz runtime helpers.
|
|
221
|
+
const P_STATIC = {
|
|
222
|
+
resolve: '__p_resolve', reject: '__p_reject', all: '__p_all', race: '__p_race',
|
|
223
|
+
allSettled: '__p_allSettled', any: '__p_any', try: '__p_try', withResolvers: '__p_withResolvers',
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Spread of a possibly-iterator value (iterator-minting programs only):
|
|
227
|
+
// `...E` → `...__drain(E)` — pass-through for arrays/strings, materializes
|
|
228
|
+
// machines/@@iterator providers. Array literals skip (statically safe).
|
|
229
|
+
const wrapSpreadDrain = (e) => {
|
|
230
|
+
if (!_gen?.iterProto?.on) return null
|
|
231
|
+
const v = e[1]
|
|
232
|
+
if (Array.isArray(v) && (v[0] === '[]' || v[0] == null)) return null
|
|
233
|
+
_gen.iterProto.drain = true
|
|
234
|
+
return ['...', ['()', '__it_drain', transform(v)]]
|
|
235
|
+
}
|
|
236
|
+
const wrapArg = (a) => (Array.isArray(a) && a[0] === '...' && wrapSpreadDrain(a)) || transform(a)
|
|
237
|
+
|
|
157
238
|
const handlers = {
|
|
239
|
+
// async function/arrow → (...aa) => __async_run((function* …)(...aa))
|
|
240
|
+
'async'(inner) {
|
|
241
|
+
if (!_gen?.lowerAsync || !Array.isArray(inner)) return
|
|
242
|
+
if (inner[0] === 'function*') return transform(_gen.lowerAsyncGen(inner[2], inner[3]))
|
|
243
|
+
if (inner[0] === 'function') return transform(_gen.lowerAsync(inner[2], inner[3]))
|
|
244
|
+
if (inner[0] === '=>') {
|
|
245
|
+
const params = Array.isArray(inner[1]) && inner[1][0] === '()' ? inner[1][1] : inner[1]
|
|
246
|
+
return transform(_gen.lowerAsync(params, inner[2]))
|
|
247
|
+
}
|
|
248
|
+
// `async function () {}()` — the parser binds the CALL inside the async
|
|
249
|
+
// wrapper; lower the callee, keep the call.
|
|
250
|
+
if (inner[0] === '()' && Array.isArray(inner[1]) && (inner[1][0] === 'function' || inner[1][0] === '=>'))
|
|
251
|
+
return transform(['()', ['async', inner[1]], ...inner.slice(2)])
|
|
252
|
+
},
|
|
253
|
+
|
|
158
254
|
'()'(callee, ...rest) {
|
|
255
|
+
// Promise API rides the async runtime: new Promise(fn) arrives here as a
|
|
256
|
+
// plain call (the `new` handler unwraps unknown ctors), statics by name.
|
|
257
|
+
if (_gen?.noteAsync) {
|
|
258
|
+
if (callee === 'Promise' && rest.length) {
|
|
259
|
+
_gen.noteAsync()
|
|
260
|
+
return ['()', '__p_exec', ...rest.map(a => a == null ? a : transform(a))]
|
|
261
|
+
}
|
|
262
|
+
if (Array.isArray(callee) && callee[0] === '.' && callee[1] === 'Promise' && P_STATIC[callee[2]]) {
|
|
263
|
+
_gen.noteAsync()
|
|
264
|
+
return ['()', P_STATIC[callee[2]], ...rest.map(a => a == null ? a : transform(a))]
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// Terminal iterator helper (toArray/reduce/forEach/some/every/find) on a
|
|
268
|
+
// chain rooted at a known generator call → fused IIFE loop.
|
|
269
|
+
if (_gen && Array.isArray(callee) && callee[0] === '.') {
|
|
270
|
+
const chain = _gen.unwindChain(['()', callee, ...rest])
|
|
271
|
+
if (chain && chain.stages.length && _gen.isTerminal(chain.stages[chain.stages.length - 1].h)) {
|
|
272
|
+
const fused = _gen.fuseTerminal(chain, names.genTemp)
|
|
273
|
+
if (fused) return transform(fused)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
159
276
|
if (callee === 'Array') {
|
|
160
277
|
const lit = lowerArrayConstructor(rest[0])
|
|
161
278
|
if (lit) return lit
|
|
162
279
|
}
|
|
280
|
+
// `[…literal][Symbol.iterator]()` / `'s'[Symbol.iterator]()` — mint a
|
|
281
|
+
// decorated indexed iterator (array/string values as iterators). Literal
|
|
282
|
+
// receivers only: name/expression receivers keep the plain dot-call
|
|
283
|
+
// (machines answer it themselves; the protocol desugar's own probe-call
|
|
284
|
+
// must not re-enter here).
|
|
285
|
+
if (_gen && Array.isArray(callee) && callee[0] === '.' && callee[2] === '@@iterator' &&
|
|
286
|
+
rest.every(a => a == null) && Array.isArray(callee[1]) &&
|
|
287
|
+
((callee[1][0] === '[]' && callee[1].length === 2) || (callee[1][0] == null && typeof callee[1][1] === 'string'))) {
|
|
288
|
+
_gen.iterProto.fromUsed = true
|
|
289
|
+
return ['()', '__it_from', transform(callee[1])]
|
|
290
|
+
}
|
|
291
|
+
// Array.from over iterator values (iterator-minting programs only):
|
|
292
|
+
// protocol values materialize via __it_arr; arrays copy; array-likes
|
|
293
|
+
// build by length. `Array.from(x, fn)` maps the materialized array.
|
|
294
|
+
if (_gen?.iterProto?.on && Array.isArray(callee) && callee[0] === '.' &&
|
|
295
|
+
callee[1] === 'Array' && callee[2] === 'from' && rest.length === 1) {
|
|
296
|
+
_gen.iterProto.arr = true
|
|
297
|
+
const args = Array.isArray(rest[0]) && rest[0][0] === ',' ? rest[0].slice(1) : [rest[0]]
|
|
298
|
+
const drained = ['()', '__it_arr', transform(args[0])]
|
|
299
|
+
if (args.length >= 2) return ['()', ['.', drained, 'map'], transform(args[1])]
|
|
300
|
+
return drained
|
|
301
|
+
}
|
|
302
|
+
// spread ARG of a possibly-iterator value → __drain (iterator programs only)
|
|
303
|
+
if (_gen?.iterProto?.on && rest.length === 1 && Array.isArray(rest[0])) {
|
|
304
|
+
const args = rest[0]
|
|
305
|
+
if (args[0] === ',' && args.slice(1).some(x => Array.isArray(x) && x[0] === '...'))
|
|
306
|
+
return ['()', transform(callee), [',', ...args.slice(1).map(wrapArg)]]
|
|
307
|
+
if (args[0] === '...') {
|
|
308
|
+
const w = wrapSpreadDrain(args)
|
|
309
|
+
if (w) return ['()', transform(callee), w]
|
|
310
|
+
}
|
|
311
|
+
}
|
|
163
312
|
if (Array.isArray(callee) && callee[0] === '()' && Array.isArray(callee[1]) && callee[1][0] === 'function' && callee[1][1]) {
|
|
164
313
|
const [, name, params, body] = callee[1]
|
|
165
314
|
const [p2, b2] = lowerArguments(params, functionBodyBlock(body))
|
|
@@ -217,6 +366,43 @@ export function createTransform(opts) {
|
|
|
217
366
|
return ['let', ...args.map(transform)]
|
|
218
367
|
},
|
|
219
368
|
|
|
369
|
+
'function*'(name, params, body) {
|
|
370
|
+
// Expression form (`let g = function* () {…}`). Named statement forms are
|
|
371
|
+
// hoisted in transformScope like plain function declarations.
|
|
372
|
+
if (!_gen) return
|
|
373
|
+
return _gen.lowerGenerator(params, body)
|
|
374
|
+
},
|
|
375
|
+
|
|
376
|
+
'[]'(payload, idx) {
|
|
377
|
+
// 2-arg form is INDEXING (obj[key]) — not ours. The 1-arg form is the
|
|
378
|
+
// array LITERAL: rewrite a spread of a generator / helper chain into a
|
|
379
|
+
// spread of the FUSED toArray (a plain array) — the existing
|
|
380
|
+
// array-spread machinery takes it from there. In an iterator-minting
|
|
381
|
+
// program, any OTHER spread of a non-literal wraps in __drain (returns
|
|
382
|
+
// the value untouched unless it's an iterator — then materializes it).
|
|
383
|
+
if (!_gen || idx !== undefined) return
|
|
384
|
+
const rewrite = (e) => {
|
|
385
|
+
if (!Array.isArray(e) || e[0] !== '...') return null
|
|
386
|
+
if (Array.isArray(e[1])) {
|
|
387
|
+
const chain = _gen.unwindChain(e[1])
|
|
388
|
+
if (chain && (!chain.stages.length || !_gen.isTerminal(chain.stages[chain.stages.length - 1].h))) {
|
|
389
|
+
const fused = _gen.fuseTerminal({ root: chain.root, stages: [...chain.stages, { h: 'toArray', args: [] }] }, names.genTemp)
|
|
390
|
+
if (fused) return ['...', transform(fused)]
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return wrapSpreadDrain(e)
|
|
394
|
+
}
|
|
395
|
+
if (payload === undefined) return
|
|
396
|
+
if (Array.isArray(payload) && payload[0] === ',') {
|
|
397
|
+
let hit = false
|
|
398
|
+
const mapped = payload.slice(1).map((e) => { const r = rewrite(e); if (r) hit = true; return r ?? transform(e) })
|
|
399
|
+
if (hit) return ['[]', [',', ...mapped]]
|
|
400
|
+
return
|
|
401
|
+
}
|
|
402
|
+
const one = rewrite(payload)
|
|
403
|
+
if (one) return ['[]', one]
|
|
404
|
+
},
|
|
405
|
+
|
|
220
406
|
':'(label, body) {
|
|
221
407
|
if (typeof label === 'string' && Array.isArray(body) && LABEL_BODY_OPS.has(body[0]))
|
|
222
408
|
return ['label', label, transform(body)]
|
|
@@ -246,6 +432,17 @@ export function createTransform(opts) {
|
|
|
246
432
|
if (lit) return lit
|
|
247
433
|
}
|
|
248
434
|
const name = typeof ctor === 'string' ? ctor : (Array.isArray(ctor) && ctor[0] === '()' ? ctor[1] : null)
|
|
435
|
+
// SharedArrayBuffer canonicalizes to ArrayBuffer: within one module the
|
|
436
|
+
// buffer IS jz's linear memory — sharedness is a jz.pool linking concern
|
|
437
|
+
// (sharedMemory: true), not a per-buffer object property. Atomics accept
|
|
438
|
+
// proven Int32Array views either way. (`new X(args)` parses with the
|
|
439
|
+
// args INSIDE the ctor call node — rename the callee, keep the rest.)
|
|
440
|
+
if (name === 'SharedArrayBuffer') {
|
|
441
|
+
const ctor2 = Array.isArray(ctor) && ctor[0] === '()'
|
|
442
|
+
? ['()', 'ArrayBuffer', ...ctor.slice(2).map(transform)]
|
|
443
|
+
: 'ArrayBuffer'
|
|
444
|
+
return ['new', ctor2, ...cargs.map(transform)]
|
|
445
|
+
}
|
|
249
446
|
// Preserve `new` for native constructors the compiler resolves under its own
|
|
250
447
|
// `new` handler (prepare/index.js): typed arrays/Array/RegExp need the `new`
|
|
251
448
|
// form, and `new URL(rel, import.meta.url)` lowers to a static href string there.
|
|
@@ -256,8 +453,21 @@ export function createTransform(opts) {
|
|
|
256
453
|
},
|
|
257
454
|
|
|
258
455
|
'instanceof'(val, ctor) {
|
|
456
|
+
// promise-shape probe — promises are fixed-shape objects, no ctor chain
|
|
457
|
+
if (ctor === 'Promise' && _gen?.noteAsync) {
|
|
458
|
+
_gen.noteAsync()
|
|
459
|
+
const t0 = transform(val)
|
|
460
|
+
return ['&&', ['!=', t0, [null, null]], ['==', ['.', t0, '__p'], [null, 1]]]
|
|
461
|
+
}
|
|
462
|
+
// iterator-shape probe — anything driving the protocol (a callable next)
|
|
463
|
+
// is an Iterator to jz; arrays/strings probe false (no `next` prop).
|
|
464
|
+
if (ctor === 'Iterator' && _gen) {
|
|
465
|
+
const t0 = transform(val)
|
|
466
|
+
return ['&&', ['===', ['typeof', t0], [null, 'object']], ['!=', ['.', t0, 'next'], [null, null]]]
|
|
467
|
+
}
|
|
259
468
|
const t = transform(val)
|
|
260
|
-
|
|
469
|
+
let name = typeof ctor === 'string' ? ctor : (Array.isArray(ctor) && ctor[0] === '()' ? ctor[1] : null)
|
|
470
|
+
if (name === 'SharedArrayBuffer') name = 'ArrayBuffer' // same canonicalization as `new`
|
|
261
471
|
const fold = staticInstanceofFold(val, name)
|
|
262
472
|
if (fold != null) return [null, fold]
|
|
263
473
|
if (typeof name === 'string' && ERROR_INSTANCEOF.has(name)) {
|
|
@@ -288,6 +498,34 @@ export function createTransform(opts) {
|
|
|
288
498
|
// (`for (; i < n; i++)` ran zero/garbage iterations). for-of/for-in heads aren't
|
|
289
499
|
// `;`-lists, so they pass through transform unchanged.
|
|
290
500
|
'for'(head, body) {
|
|
501
|
+
// for-of over a KNOWN generator call → while-next desugar (the generator
|
|
502
|
+
// object is plain closures + a fixed-shape result record).
|
|
503
|
+
if (_gen && Array.isArray(head) && head[0] === 'of' &&
|
|
504
|
+
Array.isArray(head[2]) && head[2][0] === '()' &&
|
|
505
|
+
typeof head[2][1] === 'string' && _gen.generatorNames.has(head[2][1]))
|
|
506
|
+
return transform(_gen.desugarForOfGenerator(head[1], transform(head[2]), body, names.genTemp))
|
|
507
|
+
// for-of over an iterator-HELPER CHAIN rooted at a known generator call
|
|
508
|
+
// → one fused while-next loop (map/filter/take/drop compose in place).
|
|
509
|
+
if (_gen && Array.isArray(head) && head[0] === 'of' && Array.isArray(head[2])) {
|
|
510
|
+
const chain = _gen.unwindChain(head[2])
|
|
511
|
+
if (chain && chain.stages.length && chain.stages.every(st => !_gen.isTerminal(st.h))) {
|
|
512
|
+
const name = Array.isArray(head[1]) ? head[1][1] : head[1]
|
|
513
|
+
const bodyStmts = Array.isArray(body) && body[0] === ';' ? body.slice(1) : [body]
|
|
514
|
+
const x = names.genTemp('gx')
|
|
515
|
+
const fused = _gen.fusedLoop(chain.root, chain.stages, names.genTemp, x,
|
|
516
|
+
() => [['let', ['=', name, x]], ...bodyStmts])
|
|
517
|
+
return transform(fused)
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
// 'of-idx' — the protocol fork's array arm: plain indexed for-of, no re-fork.
|
|
521
|
+
if (Array.isArray(head) && head[0] === 'of-idx') {
|
|
522
|
+
const t = transform(['of', head[1], head[2]])
|
|
523
|
+
return ['for', t, transform(body)]
|
|
524
|
+
}
|
|
525
|
+
// for-of over an UNKNOWN source in an iterator-minting program → runtime
|
|
526
|
+
// protocol fork (probe once, drive next() lazily, else indexed path).
|
|
527
|
+
if (_gen && _gen.iterProto?.on && Array.isArray(head) && head[0] === 'of')
|
|
528
|
+
return transform(_gen.desugarForOfProtocol(head[1], head[2], body, names.genTemp))
|
|
291
529
|
if (Array.isArray(head) && head[0] === ';')
|
|
292
530
|
return ['for', [';', ...head.slice(1).map(s => s == null ? s : transform(s))], transform(body)]
|
|
293
531
|
return ['for', transform(head), transform(body)]
|