jz 0.5.1 → 0.7.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 +315 -318
- package/bench/README.md +369 -0
- package/bench/bench.svg +102 -0
- package/cli.js +104 -30
- package/dist/interop.js +1 -0
- package/dist/jz.js +6024 -0
- package/index.d.ts +126 -0
- package/index.js +362 -84
- package/interop.js +159 -186
- 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 +184 -0
- package/module/array.js +377 -176
- package/module/collection.js +639 -145
- package/module/console.js +55 -43
- package/module/core.js +264 -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 +551 -187
- package/module/number.js +327 -60
- package/module/object.js +474 -184
- package/module/regex.js +255 -25
- package/module/schema.js +24 -6
- package/module/simd.js +117 -0
- package/module/string.js +621 -226
- package/module/symbol.js +1 -1
- package/module/timer.js +9 -14
- package/module/typedarray.js +459 -73
- package/package.json +58 -14
- package/src/abi/index.js +39 -23
- package/src/abi/string.js +38 -41
- package/src/ast.js +460 -0
- package/src/autoload.js +29 -24
- package/src/bridge.js +111 -0
- package/src/compile/analyze-scans.js +824 -0
- package/src/compile/analyze.js +1600 -0
- package/src/compile/emit-assign.js +411 -0
- package/src/compile/emit.js +3512 -0
- package/src/compile/flow-types.js +103 -0
- package/src/{compile.js → compile/index.js} +778 -128
- package/src/{infer.js → compile/infer.js} +62 -98
- package/src/compile/loop-divmod.js +155 -0
- package/src/{narrow.js → compile/narrow.js} +409 -106
- package/src/compile/peel-stencil.js +261 -0
- package/src/compile/plan/advise.js +370 -0
- package/src/compile/plan/common.js +150 -0
- package/src/compile/plan/index.js +122 -0
- package/src/compile/plan/inline.js +682 -0
- package/src/compile/plan/literals.js +1199 -0
- package/src/compile/plan/loops.js +472 -0
- package/src/compile/plan/scope.js +649 -0
- package/src/compile/program-facts.js +423 -0
- package/src/ctx.js +220 -64
- package/src/ir.js +589 -172
- package/src/kind-traits.js +132 -0
- package/src/kind.js +524 -0
- package/src/op-policy.js +57 -0
- package/src/{optimize.js → optimize/index.js} +1432 -473
- package/src/optimize/vectorize.js +4660 -0
- package/src/param-reps.js +65 -0
- package/src/parse.js +44 -0
- package/src/{prepare.js → prepare/index.js} +649 -205
- package/src/prepare/lift-iife.js +149 -0
- package/src/reps.js +116 -0
- package/src/resolve.js +12 -3
- package/src/static.js +208 -0
- package/src/type.js +651 -0
- package/src/{assemble.js → wat/assemble.js} +275 -55
- package/src/wat/optimize.js +3938 -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/jzify/classes.js
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Class and object-method `this` lowering.
|
|
3
|
+
* @module jzify/classes
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { extractParams as paramList, objectLiteralEntries } from '../src/ast.js'
|
|
7
|
+
import { err } from '../src/ctx.js'
|
|
8
|
+
|
|
9
|
+
export function createClassLowering({ transform, names, JC }) {
|
|
10
|
+
// === class lowering ===
|
|
11
|
+
//
|
|
12
|
+
// A class is lowered to a factory arrow. Instance state is a plain object;
|
|
13
|
+
// methods are per-instance arrows capturing it (so `obj.m()` keeps working
|
|
14
|
+
// without a separate `this` argument); `this` is renamed to that object;
|
|
15
|
+
// `new C(a)` is already turned into `C(a)` by the `new` handler.
|
|
16
|
+
//
|
|
17
|
+
// class Point { x = 0; y; constructor(a,b){ this.x = a; this.y = b }
|
|
18
|
+
// dist(){ return Math.hypot(this.x, this.y) } }
|
|
19
|
+
// →
|
|
20
|
+
// let Point = (a, b) => {
|
|
21
|
+
// let selfN = { x: undefined, y: undefined,
|
|
22
|
+
// dist: () => Math.hypot(selfN.x, selfN.y) }
|
|
23
|
+
// selfN.x = 0 // field initializers, in declaration order
|
|
24
|
+
// selfN.x = a // then the constructor body
|
|
25
|
+
// selfN.y = b
|
|
26
|
+
// return selfN
|
|
27
|
+
// }
|
|
28
|
+
//
|
|
29
|
+
// Simple inheritance is lowered too: `class D extends B` builds the instance
|
|
30
|
+
// from `B`'s factory — forwarding `super(...)` args, or the derived ctor params
|
|
31
|
+
// when the derived constructor is implicit — then applies D's own fields and
|
|
32
|
+
// methods over it.
|
|
33
|
+
//
|
|
34
|
+
// Out of scope (rejected with a clear message): full `super.foo` property
|
|
35
|
+
// semantics, getters/setters, non-constant computed member names. Private
|
|
36
|
+
// `#name` members are kept as the literal key string `#name` (jz allows it).
|
|
37
|
+
const DEFAULT_DERIVED_CTOR_ARITY = 8
|
|
38
|
+
|
|
39
|
+
const arrowParams = params => Array.isArray(params) && params[0] === '()' ? params : ['()', params]
|
|
40
|
+
const block = b => Array.isArray(b) && b[0] === '{}' ? b : ['{}', b]
|
|
41
|
+
|
|
42
|
+
const classBodyItems = (body) =>
|
|
43
|
+
body == null ? [] : Array.isArray(body) && body[0] === ';' ? body.slice(1) : [body]
|
|
44
|
+
|
|
45
|
+
// Rename `this` → `to`, not crossing into a nested `function`/`class` (those
|
|
46
|
+
// rebind `this`); arrows inherit `this`, so they are crossed. Property *names*
|
|
47
|
+
// (`obj.this`, `{this: …}` value-side only) are left alone.
|
|
48
|
+
function renameThis(node, to) {
|
|
49
|
+
if (node === 'this') return to
|
|
50
|
+
if (!Array.isArray(node)) return node
|
|
51
|
+
if (node[0] === 'function' || node[0] === 'class') return node
|
|
52
|
+
if (node[0] === '.' || node[0] === '?.') return [node[0], renameThis(node[1], to), node[2]]
|
|
53
|
+
if (node[0] === ':') return [node[0], node[1], renameThis(node[2], to)]
|
|
54
|
+
return node.map(n => renameThis(n, to))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function usesThis(node) {
|
|
58
|
+
if (node === 'this') return true
|
|
59
|
+
if (!Array.isArray(node)) return false
|
|
60
|
+
if (node[0] === 'function' || node[0] === 'class') return false
|
|
61
|
+
if (node[0] === '.' || node[0] === '?.') return usesThis(node[1])
|
|
62
|
+
if (node[0] === ':') return usesThis(node[2])
|
|
63
|
+
return node.some(usesThis)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function hasSuperProp(node) {
|
|
67
|
+
if (!Array.isArray(node)) return false
|
|
68
|
+
if ((node[0] === '.' || node[0] === '?.') && node[1] === 'super') return true
|
|
69
|
+
if (node[0] === '[]' && node[1] === 'super') return true
|
|
70
|
+
return node.some(hasSuperProp)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function isSuperCall(node) {
|
|
74
|
+
return Array.isArray(node) && node[0] === '()' && node[1] === 'super'
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function literalStringKey(node) {
|
|
78
|
+
return Array.isArray(node) && node[0] == null && typeof node[1] === 'string' ? node[1] : null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function constStringKey(node) {
|
|
82
|
+
if (typeof node === 'string') return node
|
|
83
|
+
const lit = literalStringKey(node)
|
|
84
|
+
if (lit != null) return lit
|
|
85
|
+
if (Array.isArray(node) && node[0] === '[]') return literalStringKey(node[1])
|
|
86
|
+
return null
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function superMethodName(callee) {
|
|
90
|
+
if (!Array.isArray(callee)) return null
|
|
91
|
+
if ((callee[0] === '.' || callee[0] === '?.') && callee[1] === 'super') return callee[2]
|
|
92
|
+
if (callee[0] === '[]' && callee[1] === 'super') return literalStringKey(callee[2])
|
|
93
|
+
return null
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function collectSuperMethodCalls(node, out = new Set()) {
|
|
97
|
+
if (!Array.isArray(node)) return out
|
|
98
|
+
if (node[0] === 'function' || node[0] === 'class') return out
|
|
99
|
+
if (node[0] === '()') {
|
|
100
|
+
const name = superMethodName(node[1])
|
|
101
|
+
if (name) out.add(name)
|
|
102
|
+
}
|
|
103
|
+
for (const n of node) collectSuperMethodCalls(n, out)
|
|
104
|
+
return out
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function rewriteSuperMethodCalls(node, baseMethodVars) {
|
|
108
|
+
if (!Array.isArray(node)) return node
|
|
109
|
+
if (node[0] === 'function' || node[0] === 'class') return node
|
|
110
|
+
if (node[0] === '()') {
|
|
111
|
+
const name = superMethodName(node[1])
|
|
112
|
+
if (name) {
|
|
113
|
+
const fn = baseMethodVars.get(name)
|
|
114
|
+
if (!fn) jzifyError(`super.${name} is not available on the base class`)
|
|
115
|
+
return ['()', fn, ...node.slice(2).map(n => rewriteSuperMethodCalls(n, baseMethodVars))]
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return node.map(n => rewriteSuperMethodCalls(n, baseMethodVars))
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function splitCtorSuper(body) {
|
|
122
|
+
if (body == null) return { args: null, body }
|
|
123
|
+
if (isSuperCall(body)) return { args: body.slice(2), body: null }
|
|
124
|
+
if (Array.isArray(body) && body[0] === '{}') {
|
|
125
|
+
const inner = splitCtorSuper(body[1])
|
|
126
|
+
return { args: inner.args, body: ['{}', inner.body] }
|
|
127
|
+
}
|
|
128
|
+
if (Array.isArray(body) && body[0] === ';') {
|
|
129
|
+
const out = [';']
|
|
130
|
+
let args = null
|
|
131
|
+
for (const stmt of body.slice(1)) {
|
|
132
|
+
if (args == null && isSuperCall(stmt)) { args = stmt.slice(2); continue }
|
|
133
|
+
out.push(stmt)
|
|
134
|
+
}
|
|
135
|
+
return { args, body: out.length === 1 ? null : out.length === 2 ? out[1] : out }
|
|
136
|
+
}
|
|
137
|
+
return { args: null, body }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Object shorthand methods and arrow-valued properties both parse as `=>`.
|
|
141
|
+
// Stay conservative: only statement-shaped bodies are receiver methods here;
|
|
142
|
+
// expression-bodied arrows keep their lexical `this` and remain unsupported.
|
|
143
|
+
const OBJ_METHOD_BODY_OPS = new Set([';', 'return', 'if', 'for', 'for-in', 'for-of',
|
|
144
|
+
'while', 'do', 'switch', 'throw', 'try', 'break', 'continue'])
|
|
145
|
+
|
|
146
|
+
function isStatementBody(body) {
|
|
147
|
+
return Array.isArray(body) && OBJ_METHOD_BODY_OPS.has(body[0])
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function objectMethodUsesThis(prop) {
|
|
151
|
+
if (!Array.isArray(prop) || prop[0] !== ':' || typeof prop[1] !== 'string') return false
|
|
152
|
+
const value = prop[2]
|
|
153
|
+
if (!Array.isArray(value)) return false
|
|
154
|
+
if (value[0] === '=>' && isStatementBody(value[2])) return usesThis(value[2])
|
|
155
|
+
return false
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function lowerObjectLiteralThis(args) {
|
|
159
|
+
const props = objectLiteralEntries(args)
|
|
160
|
+
if (props.length === 0 || !props.some(objectMethodUsesThis)) return null
|
|
161
|
+
if (!props.every(p => Array.isArray(p) && p[0] === ':' && typeof p[1] === 'string')) return null
|
|
162
|
+
|
|
163
|
+
const self = names.objThis()
|
|
164
|
+
const litProps = props.map(p => {
|
|
165
|
+
const value = p[2]
|
|
166
|
+
if (objectMethodUsesThis(p)) {
|
|
167
|
+
return [':', p[1], transform(['=>', value[1], block(renameThis(value[2], self))])]
|
|
168
|
+
}
|
|
169
|
+
return [':', p[1], transform(value)]
|
|
170
|
+
})
|
|
171
|
+
const lit = ['{}', litProps.length === 1 ? litProps[0] : [',', ...litProps]]
|
|
172
|
+
return ['()', ['()', ['=>', null, ['{}', [';',
|
|
173
|
+
['let', ['=', self, lit]],
|
|
174
|
+
['return', self]
|
|
175
|
+
]]]], null]
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Route through the shared compiler error channel (uniform Error shape + stack
|
|
179
|
+
// cleanup for dependents). jzify runs pre-emit, so err()'s location/function
|
|
180
|
+
// enrichment guards simply no-op; the `jzify:` prefix marks the phase.
|
|
181
|
+
function jzifyError(msg) { err(`jzify: ${msg}`) }
|
|
182
|
+
|
|
183
|
+
function lowerClass(name, heritage, body) {
|
|
184
|
+
let ctorParams = null, ctorBody = null
|
|
185
|
+
const methods = [], fields = [], statics = []
|
|
186
|
+
for (const it of classBodyItems(body)) {
|
|
187
|
+
if (typeof it === 'string') { fields.push([it, null]); continue } // bare `x;`
|
|
188
|
+
if (!Array.isArray(it)) continue
|
|
189
|
+
const bareFieldName = constStringKey(it)
|
|
190
|
+
if (bareFieldName != null) { fields.push([bareFieldName, null]); continue }
|
|
191
|
+
if (it[0] === ':' && Array.isArray(it[2]) && it[2][0] === '=>') {
|
|
192
|
+
const key = constStringKey(it[1])
|
|
193
|
+
if (key == null) jzifyError(JC.computedMember)
|
|
194
|
+
if (key === 'constructor') { ctorParams = it[2][1]; ctorBody = it[2][2] }
|
|
195
|
+
else methods.push([key, it[2][1], it[2][2]])
|
|
196
|
+
continue
|
|
197
|
+
}
|
|
198
|
+
if (it[0] === '=') {
|
|
199
|
+
const lhs = it[1]
|
|
200
|
+
if (Array.isArray(lhs) && lhs[0] === 'static') {
|
|
201
|
+
const key = constStringKey(lhs[1])
|
|
202
|
+
if (key == null) jzifyError(JC.computedStaticField)
|
|
203
|
+
statics.push([key, it[2]])
|
|
204
|
+
continue
|
|
205
|
+
}
|
|
206
|
+
const key = constStringKey(lhs)
|
|
207
|
+
if (key == null) jzifyError(JC.computedField)
|
|
208
|
+
fields.push([key, it[2]])
|
|
209
|
+
continue
|
|
210
|
+
}
|
|
211
|
+
if (it[0] === 'static') {
|
|
212
|
+
const key = constStringKey(it[1])
|
|
213
|
+
if (key != null) {
|
|
214
|
+
statics.push([key, null])
|
|
215
|
+
continue
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (it[0] === 'static' && typeof it[1] === 'string') {
|
|
219
|
+
statics.push([it[1], null])
|
|
220
|
+
continue
|
|
221
|
+
}
|
|
222
|
+
if (it[0] === 'static' && Array.isArray(it[1]) && it[1][0] === ':' && Array.isArray(it[1][2]) && it[1][2][0] === '=>') {
|
|
223
|
+
const key = constStringKey(it[1][1])
|
|
224
|
+
if (key == null) jzifyError(JC.computedStaticMember)
|
|
225
|
+
statics.push([key, it[1][2], true])
|
|
226
|
+
continue
|
|
227
|
+
}
|
|
228
|
+
if (it[0] === 'get' || it[0] === 'set') jzifyError(JC.accessor)
|
|
229
|
+
if (it[0] === 'static') jzifyError(JC.staticMember)
|
|
230
|
+
jzifyError(`unsupported class member ${JSON.stringify(it).slice(0, 60)}`)
|
|
231
|
+
}
|
|
232
|
+
const superMethods = heritage == null ? new Set() : new Set([
|
|
233
|
+
...collectSuperMethodCalls(ctorBody),
|
|
234
|
+
...fields.flatMap(([, init]) => init == null ? [] : [...collectSuperMethodCalls(init)]),
|
|
235
|
+
...methods.flatMap(([, , mbody]) => [...collectSuperMethodCalls(mbody)])
|
|
236
|
+
])
|
|
237
|
+
if (heritage != null) {
|
|
238
|
+
const dummySuperVars = new Map([...superMethods].map((k, i) => [k, names.classSuper(i)]))
|
|
239
|
+
const unsupportedSuperProp = node => node != null && hasSuperProp(rewriteSuperMethodCalls(node, dummySuperVars))
|
|
240
|
+
if (
|
|
241
|
+
unsupportedSuperProp(ctorBody) ||
|
|
242
|
+
fields.some(([, init]) => unsupportedSuperProp(init)) ||
|
|
243
|
+
methods.some(([, , mbody]) => unsupportedSuperProp(mbody))
|
|
244
|
+
)
|
|
245
|
+
jzifyError(JC.superProp)
|
|
246
|
+
}
|
|
247
|
+
const self = names.classSelf()
|
|
248
|
+
const UNDEF = [] // jessie's node for `undefined`
|
|
249
|
+
// Object literal: every declared field (its initializer inline when it doesn't
|
|
250
|
+
// touch `this`, else `undefined` and assigned below), every method as its
|
|
251
|
+
// self-capturing arrow. Declaring all fields up front fixes the object shape.
|
|
252
|
+
const litProps = [], deferred = []
|
|
253
|
+
for (const [fname, init] of fields) {
|
|
254
|
+
if (init != null && !usesThis(init)) litProps.push([':', fname, transform(init)])
|
|
255
|
+
else { litProps.push([':', fname, UNDEF]); if (init != null) deferred.push([fname, init]) }
|
|
256
|
+
}
|
|
257
|
+
for (const [mname, mparams, mbody] of methods)
|
|
258
|
+
litProps.push([':', mname, transform(['=>', mparams ?? ['()', null], block(renameThis(mbody, self))])])
|
|
259
|
+
const lit = ['{}', litProps.length === 0 ? null : litProps.length === 1 ? litProps[0] : [',', ...litProps]]
|
|
260
|
+
let params = ctorParams ?? ['()', null]
|
|
261
|
+
const dynamicBase = heritage != null && typeof heritage !== 'string'
|
|
262
|
+
const baseRef = heritage == null ? null : dynamicBase ? names.classBase() : heritage
|
|
263
|
+
const stmts = []
|
|
264
|
+
if (heritage != null) {
|
|
265
|
+
const split = splitCtorSuper(ctorBody)
|
|
266
|
+
ctorBody = split.body
|
|
267
|
+
const defaultArgs = ctorParams == null
|
|
268
|
+
? Array.from({ length: DEFAULT_DERIVED_CTOR_ARITY }, (_, i) => names.classSuperArg(i))
|
|
269
|
+
: null
|
|
270
|
+
const baseArgs = split.args ?? (defaultArgs ? [defaultArgs.length === 1 ? defaultArgs[0] : [',', ...defaultArgs]] : paramList(ctorParams))
|
|
271
|
+
stmts.push(['let', ['=', self, ['()', baseRef, ...baseArgs.map(transform)]]])
|
|
272
|
+
const superMethodVars = new Map()
|
|
273
|
+
let superIdx = 0
|
|
274
|
+
for (const mname of superMethods) {
|
|
275
|
+
const v = names.classSuper(superIdx++)
|
|
276
|
+
superMethodVars.set(mname, v)
|
|
277
|
+
stmts.push(['let', ['=', v, ['.', self, mname]]])
|
|
278
|
+
}
|
|
279
|
+
for (const [fname, init] of fields)
|
|
280
|
+
stmts.push(['=', ['.', self, fname], init != null ? transform(renameThis(rewriteSuperMethodCalls(init, superMethodVars), self)) : UNDEF])
|
|
281
|
+
for (const [mname, mparams, mbody] of methods)
|
|
282
|
+
stmts.push(['=', ['.', self, mname], transform(['=>', mparams ?? ['()', null], block(renameThis(rewriteSuperMethodCalls(mbody, superMethodVars), self))])])
|
|
283
|
+
ctorBody = rewriteSuperMethodCalls(ctorBody, superMethodVars)
|
|
284
|
+
if (defaultArgs) params = ['()', defaultArgs.length === 1 ? defaultArgs[0] : [',', ...defaultArgs]]
|
|
285
|
+
} else {
|
|
286
|
+
stmts.push(['let', ['=', self, lit]])
|
|
287
|
+
}
|
|
288
|
+
// `this`-dependent field initializers run, in declaration order, before the ctor.
|
|
289
|
+
if (heritage == null) {
|
|
290
|
+
for (const [fname, init] of deferred)
|
|
291
|
+
stmts.push(['=', ['.', self, fname], transform(renameThis(init, self))])
|
|
292
|
+
}
|
|
293
|
+
if (ctorBody != null) {
|
|
294
|
+
let cb = transform(renameThis(ctorBody, self))
|
|
295
|
+
if (Array.isArray(cb) && cb[0] === '{}') cb = cb[1]
|
|
296
|
+
if (Array.isArray(cb) && cb[0] === ';') stmts.push(...cb.slice(1).filter(s => s != null))
|
|
297
|
+
else if (cb != null) stmts.push(cb)
|
|
298
|
+
}
|
|
299
|
+
stmts.push(['return', self])
|
|
300
|
+
const factory = ['=>', arrowParams(params), ['{}', [';', ...stmts]]]
|
|
301
|
+
if (!dynamicBase && statics.length === 0) return factory
|
|
302
|
+
|
|
303
|
+
const cls = name || names.classStatic()
|
|
304
|
+
const staticStmts = []
|
|
305
|
+
if (dynamicBase) staticStmts.push(['let', ['=', baseRef, transform(heritage)]])
|
|
306
|
+
staticStmts.push(['let', ['=', cls, factory]])
|
|
307
|
+
for (const [sname, value, isMethod] of statics) {
|
|
308
|
+
const rhs = isMethod
|
|
309
|
+
? transform(['=>', value[1], block(renameThis(value[2], cls))])
|
|
310
|
+
: value == null ? UNDEF : transform(renameThis(value, cls))
|
|
311
|
+
staticStmts.push(['=', ['.', cls, sname], rhs])
|
|
312
|
+
}
|
|
313
|
+
staticStmts.push(['return', cls])
|
|
314
|
+
return ['()', ['()', ['=>', null, ['{}', [';', ...staticStmts]]]], null]
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Array(a, b, …) / new Array(a, b, …) → array literal [a, b, …]; Array() → [].
|
|
318
|
+
// The single-argument Array(n) is a length constructor (n holes), not a
|
|
319
|
+
// literal — return null there so the caller keeps it as a constructor call.
|
|
320
|
+
function lowerArrayConstructor(arg) {
|
|
321
|
+
if (arg == null) return ['[]', null]
|
|
322
|
+
if (Array.isArray(arg) && arg[0] === ',' && arg.length > 2)
|
|
323
|
+
return ['[]', [',', ...arg.slice(1).map(transform)]]
|
|
324
|
+
return null
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return { lowerClass, lowerObjectLiteralThis, lowerArrayConstructor }
|
|
328
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Var hoisting — bubble `var` to scope top, normalize for-heads, destructure patterns.
|
|
3
|
+
* @module jzify/hoist-vars
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { JZ_BLOCK_OPS } from '../src/ast.js'
|
|
7
|
+
|
|
8
|
+
export const isDestructurePat = p =>
|
|
9
|
+
Array.isArray(p) && (p[0] === '[]' || p[0] === '{}' || (p[0] === '=' && isDestructurePat(p[1])))
|
|
10
|
+
|
|
11
|
+
export function hoistVars(node, names) {
|
|
12
|
+
if (node == null || !Array.isArray(node)) return node
|
|
13
|
+
const op = node[0]
|
|
14
|
+
if (op === 'function') {
|
|
15
|
+
const inner = new Set()
|
|
16
|
+
let body = hoistVars(node[3], inner)
|
|
17
|
+
if (inner.size) body = prependDecls(body, inner)
|
|
18
|
+
return ['function', node[1], node[2], body]
|
|
19
|
+
}
|
|
20
|
+
if (op === '=>') {
|
|
21
|
+
const inner = new Set()
|
|
22
|
+
let body = hoistVars(node[2], inner)
|
|
23
|
+
if (inner.size) body = prependDecls(body, inner)
|
|
24
|
+
return ['=>', node[1], body]
|
|
25
|
+
}
|
|
26
|
+
if (op === 'in' || op === 'of') {
|
|
27
|
+
let lhs = node[1]
|
|
28
|
+
if (Array.isArray(lhs) && lhs[0] === 'var' && typeof lhs[1] === 'string' && lhs.length === 2) {
|
|
29
|
+
names.add(lhs[1])
|
|
30
|
+
lhs = lhs[1]
|
|
31
|
+
} else {
|
|
32
|
+
lhs = hoistVars(lhs, names)
|
|
33
|
+
}
|
|
34
|
+
return [op, lhs, hoistVars(node[2], names)]
|
|
35
|
+
}
|
|
36
|
+
if (op === ':' && typeof node[1] === 'string') {
|
|
37
|
+
return [':', node[1], hoistVars(node[2], names)]
|
|
38
|
+
}
|
|
39
|
+
if (op === '=' && Array.isArray(node[1]) && node[1][0] === 'var' && typeof node[1][1] === 'string' && node[1].length === 2) {
|
|
40
|
+
names.add(node[1][1])
|
|
41
|
+
return ['=', node[1][1], hoistVars(node[2], names)]
|
|
42
|
+
}
|
|
43
|
+
if (op === '=' && isDestructurePat(node[1])) {
|
|
44
|
+
return ['=', hoistPattern(node[1], names), hoistVars(node[2], names)]
|
|
45
|
+
}
|
|
46
|
+
if (op === 'for') {
|
|
47
|
+
const head = node[1]
|
|
48
|
+
let h2
|
|
49
|
+
const normalizedHead = normalizeForDeclHead(head, names) || normalizeForCommaHead(head, names)
|
|
50
|
+
if (normalizedHead) {
|
|
51
|
+
h2 = normalizedHead
|
|
52
|
+
} else if (Array.isArray(head) && head[0] === 'var' && Array.isArray(head[1]) &&
|
|
53
|
+
(head[1][0] === 'in' || head[1][0] === 'of') && typeof head[1][1] === 'string') {
|
|
54
|
+
names.add(head[1][1])
|
|
55
|
+
h2 = [head[1][0], head[1][1], hoistVars(head[1][2], names)]
|
|
56
|
+
} else if (Array.isArray(head) && head[0] === ';') {
|
|
57
|
+
h2 = [';']
|
|
58
|
+
for (let i = 1; i < head.length; i++) h2.push(hoistVars(head[i], names))
|
|
59
|
+
} else {
|
|
60
|
+
h2 = hoistVars(head, names)
|
|
61
|
+
}
|
|
62
|
+
return ['for', h2, hoistVars(node[2], names)]
|
|
63
|
+
}
|
|
64
|
+
if (op === 'var') {
|
|
65
|
+
const decls = []
|
|
66
|
+
for (let i = 1; i < node.length; i++) {
|
|
67
|
+
const d = node[i]
|
|
68
|
+
if (typeof d === 'string') { names.add(d); continue }
|
|
69
|
+
if (Array.isArray(d) && d[0] === '=' && typeof d[1] === 'string') {
|
|
70
|
+
names.add(d[1])
|
|
71
|
+
decls.push(['=', d[1], hoistVars(d[2], names)])
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (decls.length === 0) return null
|
|
75
|
+
if (decls.length === 1) return decls[0]
|
|
76
|
+
return [',', ...decls]
|
|
77
|
+
}
|
|
78
|
+
if (op === 'let' || op === 'const') {
|
|
79
|
+
const decls = [op]
|
|
80
|
+
for (let i = 1; i < node.length; i++) {
|
|
81
|
+
const d = node[i]
|
|
82
|
+
if (Array.isArray(d) && d[0] === '=' && isDestructurePat(d[1])) {
|
|
83
|
+
decls.push(['=', hoistPattern(d[1], names), hoistVars(d[2], names)])
|
|
84
|
+
} else {
|
|
85
|
+
decls.push(hoistVars(d, names))
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return decls
|
|
89
|
+
}
|
|
90
|
+
if (op === ';') {
|
|
91
|
+
const out = [op]
|
|
92
|
+
for (let i = 1; i < node.length; i++) {
|
|
93
|
+
const child = node[i]
|
|
94
|
+
if (Array.isArray(child) && child[0] === 'var' && child.length === 2 &&
|
|
95
|
+
Array.isArray(child[1]) && child[1][0] === '=' && typeof child[1][1] === 'string' &&
|
|
96
|
+
Array.isArray(child[1][2]) && child[1][2][0] === '=>') {
|
|
97
|
+
out.push(['let', ['=', child[1][1], hoistVars(child[1][2], names)]])
|
|
98
|
+
continue
|
|
99
|
+
}
|
|
100
|
+
const c = hoistVars(child, names)
|
|
101
|
+
if (c != null) out.push(c)
|
|
102
|
+
}
|
|
103
|
+
if (out.length === 1) return null
|
|
104
|
+
if (out.length === 2) return out[1]
|
|
105
|
+
return out
|
|
106
|
+
}
|
|
107
|
+
if (op === '{}' && node.length === 2) {
|
|
108
|
+
const inner = node[1]
|
|
109
|
+
const wasBlock = inner != null && Array.isArray(inner) && JZ_BLOCK_OPS.has(inner[0])
|
|
110
|
+
const t = hoistVars(inner, names)
|
|
111
|
+
if (!wasBlock || t == null) return ['{}', t]
|
|
112
|
+
const stayed = Array.isArray(t) && JZ_BLOCK_OPS.has(t[0])
|
|
113
|
+
return ['{}', stayed ? t : [';', t]]
|
|
114
|
+
}
|
|
115
|
+
const out = new Array(node.length)
|
|
116
|
+
out[0] = op
|
|
117
|
+
for (let i = 1; i < node.length; i++) out[i] = hoistVars(node[i], names)
|
|
118
|
+
return out
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function hoistPattern(node, names) {
|
|
122
|
+
if (node == null || !Array.isArray(node)) return node
|
|
123
|
+
const op = node[0]
|
|
124
|
+
if (op === '=') return ['=', hoistPattern(node[1], names), hoistVars(node[2], names)]
|
|
125
|
+
if (op === ':') return [':', hoistVars(node[1], names), hoistPattern(node[2], names)]
|
|
126
|
+
if (op === '...') return ['...', hoistPattern(node[1], names)]
|
|
127
|
+
if (op === '[]' || op === '{}' || op === ',') return [op, ...node.slice(1).map(n => hoistPattern(n, names))]
|
|
128
|
+
return hoistVars(node, names)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function prependDecls(body, names) {
|
|
132
|
+
const decl = ['let', ...names]
|
|
133
|
+
if (Array.isArray(body) && body[0] === ';') return [';', decl, ...body.slice(1)]
|
|
134
|
+
if (Array.isArray(body) && body[0] === '{}') {
|
|
135
|
+
const inner = body[1]
|
|
136
|
+
if (Array.isArray(inner) && inner[0] === ';') return ['{}', [';', decl, ...inner.slice(1)]]
|
|
137
|
+
if (inner == null) return ['{}', decl]
|
|
138
|
+
return ['{}', [';', decl, inner]]
|
|
139
|
+
}
|
|
140
|
+
return body == null ? decl : [';', decl, body]
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function normalizeForDeclHead(head, names) {
|
|
144
|
+
if (!Array.isArray(head) || (head[0] !== 'var' && head[0] !== 'let' && head[0] !== 'const')) return null
|
|
145
|
+
const kind = head[0]
|
|
146
|
+
if (head.length === 2) {
|
|
147
|
+
const expr = head[1]
|
|
148
|
+
if (!Array.isArray(expr)) return null
|
|
149
|
+
if (expr.length >= 3 && Array.isArray(expr[1]) &&
|
|
150
|
+
(expr[1][0] === 'in' || expr[1][0] === 'of') && typeof expr[1][1] === 'string') {
|
|
151
|
+
const iter = expr[1]
|
|
152
|
+
return [iter[0], normalizeForDecl(kind, iter[1], names), hoistVars([expr[0], iter[2], ...expr.slice(2)], names)]
|
|
153
|
+
}
|
|
154
|
+
return null
|
|
155
|
+
}
|
|
156
|
+
if (head.length > 2 && Array.isArray(head[1]) &&
|
|
157
|
+
(head[1][0] === 'in' || head[1][0] === 'of') && typeof head[1][1] === 'string') {
|
|
158
|
+
const iter = head[1]
|
|
159
|
+
return [iter[0], normalizeForDecl(kind, iter[1], names), hoistVars([',', iter[2], ...head.slice(2)], names)]
|
|
160
|
+
}
|
|
161
|
+
return null
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function normalizeForCommaHead(head, names) {
|
|
165
|
+
if (!Array.isArray(head) || head[0] !== ',' || head.length < 3) return null
|
|
166
|
+
const iter = head[1]
|
|
167
|
+
if (!Array.isArray(iter) || (iter[0] !== 'in' && iter[0] !== 'of') || typeof iter[1] !== 'string') return null
|
|
168
|
+
return [iter[0], iter[1], hoistVars([',', iter[2], ...head.slice(2)], names)]
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function normalizeForDecl(kind, name, names) {
|
|
172
|
+
if (kind === 'var') {
|
|
173
|
+
names.add(name)
|
|
174
|
+
return name
|
|
175
|
+
}
|
|
176
|
+
return [kind, name]
|
|
177
|
+
}
|
package/jzify/index.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
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
|
+
* @module jzify
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { JZIFY_CLASS_ERRORS as JC } from '../src/op-policy.js'
|
|
11
|
+
import { createNames } from './names.js'
|
|
12
|
+
import { foldStaticExportHelpers, foldStaticBundlerHelpers, canonicalizeObjectIdioms } from './bundler.js'
|
|
13
|
+
import { createSwitchLowering, normalizeCaseBody } from './switch.js'
|
|
14
|
+
import { createClassLowering } from './classes.js'
|
|
15
|
+
import { hoistVars, prependDecls } from './hoist-vars.js'
|
|
16
|
+
import { createArgumentsLowering } from './arguments.js'
|
|
17
|
+
import { createTransform } from './transform.js'
|
|
18
|
+
|
|
19
|
+
const names = createNames()
|
|
20
|
+
const { lowerArguments, transformPattern, bindTransform } = createArgumentsLowering(names)
|
|
21
|
+
|
|
22
|
+
let lowerClass, lowerObjectLiteralThis, lowerArrayConstructor, transformSwitch
|
|
23
|
+
let transform, transformScope
|
|
24
|
+
|
|
25
|
+
;({ transform, transformScope } = createTransform({
|
|
26
|
+
names,
|
|
27
|
+
lowerArguments,
|
|
28
|
+
transformPattern,
|
|
29
|
+
normalizeCaseBody,
|
|
30
|
+
transformSwitch: (...a) => transformSwitch(...a),
|
|
31
|
+
lowerClass: () => lowerClass,
|
|
32
|
+
lowerObjectLiteralThis: () => lowerObjectLiteralThis,
|
|
33
|
+
lowerArrayConstructor: () => lowerArrayConstructor,
|
|
34
|
+
}))
|
|
35
|
+
bindTransform(transform)
|
|
36
|
+
|
|
37
|
+
;({ lowerClass, lowerObjectLiteralThis, lowerArrayConstructor } = createClassLowering({ transform, names, JC }))
|
|
38
|
+
transformSwitch = createSwitchLowering(transform, names)
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Transform AST in-place. Returns transformed AST.
|
|
42
|
+
* @param {Array} ast - subscript/jessie parsed AST
|
|
43
|
+
* @returns {Array} Transformed AST
|
|
44
|
+
*/
|
|
45
|
+
export default function jzify(ast) {
|
|
46
|
+
names.reset()
|
|
47
|
+
const hoisted = new Set()
|
|
48
|
+
ast = hoistVars(ast, hoisted)
|
|
49
|
+
if (hoisted.size) ast = prependDecls(ast, hoisted)
|
|
50
|
+
return foldStaticBundlerHelpers(foldStaticExportHelpers(canonicalizeObjectIdioms(transformScope(ast))))
|
|
51
|
+
}
|
package/jzify/names.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synthetic temp-name factory for jzify lowering passes.
|
|
3
|
+
*
|
|
4
|
+
* Namespaces use private-use Unicode prefixes so they never collide with user code.
|
|
5
|
+
*
|
|
6
|
+
* @module jzify/names
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { T } from '../src/ast.js'
|
|
10
|
+
|
|
11
|
+
/** @returns fresh name counters, all reset together at jzify entry. */
|
|
12
|
+
export function createNames() {
|
|
13
|
+
let swIdx = 0
|
|
14
|
+
let argsIdx = 0
|
|
15
|
+
let doIdx = 0
|
|
16
|
+
let classIdx = 0
|
|
17
|
+
let objThisIdx = 0
|
|
18
|
+
let staticClassIdx = 0
|
|
19
|
+
let classBaseIdx = 0
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
reset() {
|
|
23
|
+
swIdx = argsIdx = doIdx = classIdx = objThisIdx = staticClassIdx = classBaseIdx = 0
|
|
24
|
+
},
|
|
25
|
+
switchDisc: () => `${T}sw${swIdx++}`,
|
|
26
|
+
switchStart: () => `${T}swst${swIdx++}`,
|
|
27
|
+
switchBreak: () => `${T}swbrk${swIdx++}`,
|
|
28
|
+
arg: () => `\uE001arg${argsIdx++}`,
|
|
29
|
+
doFlag: () => `\uE002do${doIdx++}`,
|
|
30
|
+
objThis: () => `\uE003obj${objThisIdx++}`,
|
|
31
|
+
classSelf: () => `\uE003self${classIdx++}`,
|
|
32
|
+
classBase: () => `\uE003base${classBaseIdx++}`,
|
|
33
|
+
classStatic: () => `\uE003class${staticClassIdx++}`,
|
|
34
|
+
classSuperArg: i => `\uE003superArg${classIdx}_${i}`,
|
|
35
|
+
classSuper: i => `\uE003super${classIdx}_${i}`,
|
|
36
|
+
}
|
|
37
|
+
}
|