jz 0.4.0 → 0.5.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/src/fuse.js DELETED
@@ -1,159 +0,0 @@
1
- /**
2
- * AST-level fusion passes.
3
- *
4
- * Unlike src/optimize.js (which is a pure WAT IR→IR rewrite, post-emission),
5
- * these rewrites need the *raw, pre-resolution* AST shape — bindings still named,
6
- * arrow bodies still inline — so they run inside prepare(), before scope
7
- * resolution and emit. They mutate the AST in place and always fire (cheap; the
8
- * shape guards are strict enough that misfires are impossible).
9
- *
10
- * @module fuse
11
- */
12
-
13
- /** Sparse-read .map fusion: rewrite `const b = a.map(arrow); for(...; j<b.length; ...) USE(b[j])`
14
- * into a fused for-loop that inlines `arrow(a[j])` at the read site, eliminating the materialized
15
- * intermediate array. Only fires on shapes where every use of `b` is a numeric `b[idx]` read or a
16
- * `b.length` read, the arrow is pure with a single named param, and `b` is not referenced after the
17
- * consumer for-loop. Preserves observable behavior because the arrow's pure-expression body has no
18
- * order-dependent effects. */
19
- export function fuseSparseMapReads(root) {
20
- walkSparse(root)
21
- }
22
- function walkSparse(node) {
23
- if (!Array.isArray(node)) return
24
- for (let i = 1; i < node.length; i++) walkSparse(node[i])
25
- if (node[0] === ';') tryFuseInBlock(node)
26
- }
27
- function tryFuseInBlock(seq) {
28
- for (let i = 1; i < seq.length - 1; i++) {
29
- const fused = tryFusePair(seq[i], seq[i + 1], seq, i)
30
- if (fused) {
31
- seq.splice(i, 2, ...fused)
32
- i-- // re-examine same position (chained fusions)
33
- }
34
- }
35
- }
36
- function tryFusePair(decl, forNode, seq, declIdx) {
37
- if (!Array.isArray(decl) || (decl[0] !== 'const' && decl[0] !== 'let')) return null
38
- if (decl.length !== 2) return null // single binding only
39
- const bind = decl[1]
40
- if (!Array.isArray(bind) || bind[0] !== '=' || typeof bind[1] !== 'string') return null
41
- const NAME = bind[1], rhs = bind[2]
42
- if (!Array.isArray(rhs) || rhs[0] !== '()') return null
43
- const callee = rhs[1]
44
- if (!Array.isArray(callee) || callee[0] !== '.' || callee[2] !== 'map') return null
45
- const RECV = callee[1]
46
- if (typeof RECV !== 'string' || RECV === NAME) return null
47
- const arrow = rhs[2]
48
- if (!Array.isArray(arrow) || arrow[0] !== '=>') return null
49
- // Single-name param only: `x => …` or `(x) => …`
50
- const ap = arrow[1]
51
- const PARAM = typeof ap === 'string' ? ap :
52
- (Array.isArray(ap) && ap[0] === '()' && typeof ap[1] === 'string' ? ap[1] : null)
53
- if (!PARAM || PARAM === NAME || PARAM === RECV) return null
54
- // Body: single-expression arrow only (block bodies skipped — could extend later).
55
- const aBody = arrow[2]
56
- if (Array.isArray(aBody) && aBody[0] === '{}') return null
57
- if (!isPureSparseArrowBody(aBody, PARAM)) return null
58
- // For-loop: ['for', [';', initStmt, cond, inc], body]
59
- if (!Array.isArray(forNode) || forNode[0] !== 'for' || forNode.length !== 3) return null
60
- const head = forNode[1]
61
- if (!Array.isArray(head) || head[0] !== ';' || head.length !== 4) return null
62
- const cond = head[2], forBody = forNode[2]
63
- // Verify `NAME` is used only as `NAME[idx]` or `NAME.length` inside cond+forBody.
64
- if (!hasOnlySparseUses(cond, NAME)) return null
65
- if (!hasOnlySparseUses(forBody, NAME)) return null
66
- if (!hasAnyIndexedRead(forBody, NAME) && !hasAnyIndexedRead(cond, NAME)) return null
67
- // `NAME` must not be read after the for-loop in the same block.
68
- for (let k = declIdx + 2; k < seq.length; k++) {
69
- if (refsName(seq[k], NAME)) return null
70
- }
71
- // RECV must not be reassigned inside the for-loop (would invalidate substitution).
72
- if (assignsName(forNode, RECV) || assignsName(forNode, NAME)) return null
73
- // PARAM must not collide with any binding inside forBody (otherwise substitution shadows wrongly).
74
- if (bindsName(forNode, PARAM)) return null
75
- // Apply substitution: NAME.length → RECV.length; NAME[idx] → arrowBody[PARAM ← RECV[idx]].
76
- const newCond = substSparse(cond, NAME, RECV, PARAM, aBody)
77
- const newBody = substSparse(forBody, NAME, RECV, PARAM, aBody)
78
- const newHead = [';', head[1], newCond, head[3]]
79
- return [['for', newHead, newBody]]
80
- }
81
- function isPureSparseArrowBody(n, PARAM) {
82
- if (typeof n === 'string') return true
83
- if (!Array.isArray(n)) return true
84
- const op = n[0]
85
- // Calls / new / assignments / increments are unsafe for repeated-substitution semantics.
86
- if (op === '()' || op === '?.()' || op === 'new' || op === '++' || op === '--') return false
87
- if (op === '=>') return false // nested closure is opaque
88
- if (typeof op === 'string' && op !== '=>' && op !== '===' && op !== '!==' && op !== '==' && op !== '!=' && op !== '<=' && op !== '>=' && op.endsWith('=') && op !== '=') return false
89
- if (op === '=') return false
90
- for (let i = 1; i < n.length; i++) if (!isPureSparseArrowBody(n[i], PARAM)) return false
91
- return true
92
- }
93
- function hasOnlySparseUses(n, NAME) {
94
- if (typeof n === 'string') return n !== NAME
95
- if (!Array.isArray(n)) return true
96
- const op = n[0]
97
- if (op === '[]' && n.length === 3 && n[1] === NAME) return hasOnlySparseUses(n[2], NAME) // NAME[idx] — idx must not reference NAME
98
- if (op === '.' && n[1] === NAME) {
99
- if (n[2] === 'length') return true
100
- return false // any other property access on NAME is opaque
101
- }
102
- for (let i = 1; i < n.length; i++) if (!hasOnlySparseUses(n[i], NAME)) return false
103
- return true
104
- }
105
- function hasAnyIndexedRead(n, NAME) {
106
- if (!Array.isArray(n)) return false
107
- if (n[0] === '[]' && n.length === 3 && n[1] === NAME) return true
108
- for (let i = 1; i < n.length; i++) if (hasAnyIndexedRead(n[i], NAME)) return true
109
- return false
110
- }
111
- function refsName(n, NAME) {
112
- if (typeof n === 'string') return n === NAME
113
- if (!Array.isArray(n)) return false
114
- for (let i = 1; i < n.length; i++) if (refsName(n[i], NAME)) return true
115
- return false
116
- }
117
- function assignsName(n, NAME) {
118
- if (!Array.isArray(n)) return false
119
- const op = n[0]
120
- if ((op === '=' || op === '++' || op === '--' ||
121
- (typeof op === 'string' && op.endsWith('=') && op !== '==' && op !== '===' && op !== '!=' && op !== '!==' && op !== '<=' && op !== '>='))
122
- && n[1] === NAME) return true
123
- for (let i = 1; i < n.length; i++) if (assignsName(n[i], NAME)) return true
124
- return false
125
- }
126
- function bindsName(n, NAME) {
127
- if (!Array.isArray(n)) return false
128
- const op = n[0]
129
- if ((op === 'let' || op === 'const')) {
130
- for (let i = 1; i < n.length; i++) {
131
- const bind = n[i]
132
- if (Array.isArray(bind) && bind[0] === '=' && bind[1] === NAME) return true
133
- }
134
- }
135
- if (op === '=>') {
136
- const p = n[1]
137
- if (p === NAME) return true
138
- if (Array.isArray(p)) {
139
- if (p[0] === '()' && p[1] === NAME) return true
140
- // skip deeper destructuring forms — conservative
141
- }
142
- }
143
- for (let i = 1; i < n.length; i++) if (bindsName(n[i], NAME)) return true
144
- return false
145
- }
146
- function substSparse(n, NAME, RECV, PARAM, arrowBody) {
147
- if (typeof n !== 'object' || n === null || !Array.isArray(n)) return n
148
- if (n[0] === '.' && n[1] === NAME && n[2] === 'length') return ['.', RECV, 'length']
149
- if (n[0] === '[]' && n.length === 3 && n[1] === NAME) {
150
- const idx = substSparse(n[2], NAME, RECV, PARAM, arrowBody)
151
- return cloneAndBind(arrowBody, PARAM, ['[]', RECV, idx])
152
- }
153
- return n.map((c, i) => i === 0 ? c : substSparse(c, NAME, RECV, PARAM, arrowBody))
154
- }
155
- function cloneAndBind(node, PARAM, replacement) {
156
- if (node === PARAM) return replacement
157
- if (!Array.isArray(node)) return node
158
- return node.map((c, i) => i === 0 ? c : cloneAndBind(c, PARAM, replacement))
159
- }