jz 0.0.0 → 0.1.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/LICENSE +21 -0
- package/README.md +373 -0
- package/cli.js +163 -0
- package/index.js +247 -0
- package/module/array.js +1322 -0
- package/module/collection.js +793 -0
- package/module/console.js +192 -0
- package/module/core.js +644 -0
- package/module/function.js +181 -0
- package/module/index.js +15 -0
- package/module/json.js +506 -0
- package/module/math.js +390 -0
- package/module/number.js +601 -0
- package/module/object.js +359 -0
- package/module/regex.js +914 -0
- package/module/schema.js +106 -0
- package/module/string.js +985 -0
- package/module/symbol.js +55 -0
- package/module/timer.js +253 -0
- package/module/typedarray.js +713 -0
- package/package.json +56 -5
- package/src/analyze.js +1927 -0
- package/src/autoload.js +175 -0
- package/src/compile.js +1211 -0
- package/src/ctx.js +244 -0
- package/src/emit.js +2105 -0
- package/src/host.js +536 -0
- package/src/ir.js +649 -0
- package/src/jzify.js +418 -0
- package/src/key.js +73 -0
- package/src/narrow.js +928 -0
- package/src/optimize.js +1352 -0
- package/src/plan.js +105 -0
- package/src/prepare.js +1534 -0
- package/src/source.js +76 -0
- package/wasi.js +80 -0
package/src/jzify.js
ADDED
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* jzify — Transform JS AST into jz-compatible form.
|
|
3
|
+
*
|
|
4
|
+
* Crockford-aligned: eliminates bad parts, enforces good practices.
|
|
5
|
+
* Runs before prepare() as an AST→AST pass.
|
|
6
|
+
*
|
|
7
|
+
* Transforms:
|
|
8
|
+
* function name(args) { body } → const name = (args) => { body }
|
|
9
|
+
* var → let
|
|
10
|
+
* switch → if/else chain
|
|
11
|
+
* new X(args) → X(args) (for known safe constructors)
|
|
12
|
+
* == → ===, != → !==
|
|
13
|
+
*
|
|
14
|
+
* Hoisting: function declarations are collected and moved to the top
|
|
15
|
+
* of their scope (module or block), preserving semantics.
|
|
16
|
+
*
|
|
17
|
+
* @module jzify
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Transform AST in-place. Returns transformed AST.
|
|
22
|
+
* @param {Array} ast - subscript/jessie parsed AST
|
|
23
|
+
* @returns {Array} Transformed AST
|
|
24
|
+
*/
|
|
25
|
+
export default function jzify(ast) {
|
|
26
|
+
swIdx = 0
|
|
27
|
+
argsIdx = 0
|
|
28
|
+
doIdx = 0
|
|
29
|
+
return transformScope(ast)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Transform a scope (module top-level or block body). Collects hoisted functions. */
|
|
33
|
+
function transformScope(node) {
|
|
34
|
+
if (!Array.isArray(node)) return transform(node)
|
|
35
|
+
|
|
36
|
+
const [op, ...args] = node
|
|
37
|
+
|
|
38
|
+
// Statement sequence: collect hoisted functions
|
|
39
|
+
if (op === ';') {
|
|
40
|
+
const hoisted = [], rest = []
|
|
41
|
+
for (const stmt of args) {
|
|
42
|
+
const t = transform(stmt)
|
|
43
|
+
if (t == null) continue
|
|
44
|
+
// Hoist function declarations to top of scope
|
|
45
|
+
if (Array.isArray(t) && t[0] === 'const' && t._hoisted) {
|
|
46
|
+
hoisted.push(t)
|
|
47
|
+
} else if (Array.isArray(t) && t[0] === ';') {
|
|
48
|
+
// Flatten nested ; from multi-statement transforms
|
|
49
|
+
for (const s of t.slice(1)) {
|
|
50
|
+
if (s != null) {
|
|
51
|
+
if (Array.isArray(s) && s[0] === 'const' && s._hoisted) hoisted.push(s)
|
|
52
|
+
else rest.push(s)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
rest.push(t)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Hoist functions AFTER imports (imports must be processed first for scope resolution)
|
|
60
|
+
const imports = rest.filter(s => Array.isArray(s) && s[0] === 'import')
|
|
61
|
+
const nonImports = rest.filter(s => !(Array.isArray(s) && s[0] === 'import'))
|
|
62
|
+
const all = [...imports, ...hoisted, ...nonImports]
|
|
63
|
+
return all.length === 0 ? null : all.length === 1 ? all[0] : [';', ...all]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return transform(node)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Wrap function body for arrow conversion */
|
|
70
|
+
function wrapArrowBody(body) {
|
|
71
|
+
const t = transformScope(body)
|
|
72
|
+
return Array.isArray(t) && (t[0] === '{}' || t[0] === ';') ? (t[0] === '{}' ? t : ['{}', t]) : ['{}', t]
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Prototype identity check: X.prototype.Y */
|
|
76
|
+
const isProto = n => Array.isArray(n) && n[0] === '.' && Array.isArray(n[1]) && n[1][0] === '.' && n[1][2] === 'prototype'
|
|
77
|
+
|
|
78
|
+
const TYPED_ARRAYS = new Set(['Float64Array','Float32Array','Int32Array','Uint32Array',
|
|
79
|
+
'Int16Array','Uint16Array','Int8Array','Uint8Array',
|
|
80
|
+
'ArrayBuffer','BigInt64Array','BigUint64Array','DataView'])
|
|
81
|
+
|
|
82
|
+
// `arguments` lowering: regular `function` has implicit `arguments`; arrow doesn't.
|
|
83
|
+
// jzify converts function → arrow, so any `arguments` use must be rewritten to a rest param.
|
|
84
|
+
// Arrow functions inherit `arguments` from enclosing function — don't stop at '=>'.
|
|
85
|
+
// Nested `function` introduces its own `arguments` — stop recursion there.
|
|
86
|
+
let argsIdx = 0
|
|
87
|
+
let doIdx = 0
|
|
88
|
+
|
|
89
|
+
function usesArguments(node) {
|
|
90
|
+
if (node === 'arguments') return true
|
|
91
|
+
if (!Array.isArray(node)) return false
|
|
92
|
+
if (node[0] === 'function') return false
|
|
93
|
+
if (node[0] === '.' || node[0] === '?.') return usesArguments(node[1])
|
|
94
|
+
if (node[0] === ':') return usesArguments(node[2])
|
|
95
|
+
for (let i = 1; i < node.length; i++) if (usesArguments(node[i])) return true
|
|
96
|
+
return false
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function renameArguments(node, to) {
|
|
100
|
+
if (node === 'arguments') return to
|
|
101
|
+
if (!Array.isArray(node)) return node
|
|
102
|
+
if (node[0] === 'function') return node
|
|
103
|
+
if (node[0] === '.' || node[0] === '?.')
|
|
104
|
+
return [node[0], renameArguments(node[1], to), node[2]]
|
|
105
|
+
if (node[0] === ':')
|
|
106
|
+
return [node[0], node[1], renameArguments(node[2], to)]
|
|
107
|
+
return node.map(n => renameArguments(n, to))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function paramList(params) {
|
|
111
|
+
if (params == null) return []
|
|
112
|
+
if (Array.isArray(params)) {
|
|
113
|
+
if (params[0] === '()') {
|
|
114
|
+
const inner = params[1]
|
|
115
|
+
if (inner == null) return []
|
|
116
|
+
if (Array.isArray(inner) && inner[0] === ',') return inner.slice(1)
|
|
117
|
+
return [inner]
|
|
118
|
+
}
|
|
119
|
+
if (params[0] === ',') return params.slice(1)
|
|
120
|
+
}
|
|
121
|
+
return [params]
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function lowerArguments(params, body) {
|
|
125
|
+
if (!usesArguments(params) && !usesArguments(body)) return [params, body]
|
|
126
|
+
const name = `\uE001arg${argsIdx++}`
|
|
127
|
+
const decls = []
|
|
128
|
+
for (const [idx, param] of paramList(params).entries()) {
|
|
129
|
+
if (Array.isArray(param) && param[0] === '...') {
|
|
130
|
+
decls.push(['=', param[1], ['()', ['.', name, 'slice'], [null, idx]]])
|
|
131
|
+
continue
|
|
132
|
+
}
|
|
133
|
+
if (Array.isArray(param) && param[0] === '=') {
|
|
134
|
+
decls.push(['=', param[1], ['??', ['[]', name, [null, idx]], renameArguments(param[2], name)]])
|
|
135
|
+
continue
|
|
136
|
+
}
|
|
137
|
+
decls.push(['=', param, ['[]', name, [null, idx]]])
|
|
138
|
+
}
|
|
139
|
+
const renamed = renameArguments(body, name)
|
|
140
|
+
return [['()', ['...', name]], decls.length ? [';', ['let', ...decls], renamed] : renamed]
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const arrowParams = params => Array.isArray(params) && params[0] === '()' ? params : ['()', params]
|
|
144
|
+
|
|
145
|
+
const handlers = {
|
|
146
|
+
// Named IIFE: (function name(p){b})(a) → let name = arrow; name(a)
|
|
147
|
+
'()'(callee, ...rest) {
|
|
148
|
+
if (Array.isArray(callee) && callee[0] === '()' && Array.isArray(callee[1]) && callee[1][0] === 'function' && callee[1][1]) {
|
|
149
|
+
const [, name, params, body] = callee[1]
|
|
150
|
+
const [p2, b2] = lowerArguments(params, body)
|
|
151
|
+
return [';', ['let', ['=', name, ['=>', arrowParams(p2), wrapArrowBody(b2)]]], ['()', name, ...rest.map(transform)]]
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
// function → arrow (named → hoisted const)
|
|
156
|
+
'function'(name, params, body) {
|
|
157
|
+
const [p2, b2] = lowerArguments(params, body)
|
|
158
|
+
const arrow = ['=>', p2, wrapArrowBody(b2)]
|
|
159
|
+
if (name) { const decl = ['const', ['=', name, arrow]]; decl._hoisted = true; return decl }
|
|
160
|
+
return arrow
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
'var'(...args) {
|
|
164
|
+
// for-in/for-of: ['var', ['in', 'k', obj]] → ['in', ['let', 'k'], obj]
|
|
165
|
+
if (args.length === 1 && Array.isArray(args[0]) && (args[0][0] === 'in' || args[0][0] === 'of')) {
|
|
166
|
+
const [, name, src] = args[0]
|
|
167
|
+
return [args[0][0], ['let', typeof name === 'string' ? name : transform(name)], transform(src)]
|
|
168
|
+
}
|
|
169
|
+
return ['let', ...args.map(transform)]
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
'='(lhs, rhs) {
|
|
173
|
+
// var assignment: ['=', ['var', name], init] → let
|
|
174
|
+
if (Array.isArray(lhs) && lhs[0] === 'var')
|
|
175
|
+
return ['let', ['=', lhs[1], transform(rhs)]]
|
|
176
|
+
// Chained property assignment: a.x = a.y = v → a.y = v; a.x = v
|
|
177
|
+
if (Array.isArray(lhs) && lhs[0] === '.' && Array.isArray(rhs) && rhs[0] === '=') {
|
|
178
|
+
const targets = []
|
|
179
|
+
let cur = ['=', lhs, rhs]
|
|
180
|
+
while (Array.isArray(cur) && cur[0] === '=') { targets.push(cur[1]); cur = cur[2] }
|
|
181
|
+
const val = transform(cur)
|
|
182
|
+
const stmts = []
|
|
183
|
+
for (let i = targets.length - 1; i >= 0; i--) stmts.push(['=', transform(targets[i]), val])
|
|
184
|
+
return stmts.length === 1 ? stmts[0] : [';', ...stmts]
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
'switch'(disc, ...cases) {
|
|
189
|
+
const clean = cases.map(c => {
|
|
190
|
+
if (c[0] === 'case' && Array.isArray(c[2]) && c[2][0] === ';') {
|
|
191
|
+
const body = c[2].slice(1).filter(s => typeof s !== 'number')
|
|
192
|
+
return ['case', c[1], body.length === 1 ? body[0] : [';', ...body]]
|
|
193
|
+
}
|
|
194
|
+
if (c[0] === 'default' && Array.isArray(c[1]) && c[1][0] === ';') {
|
|
195
|
+
const body = c[1].slice(1).filter(s => s != null && typeof s !== 'number')
|
|
196
|
+
return ['default', body.length === 1 ? body[0] : [';', ...body]]
|
|
197
|
+
}
|
|
198
|
+
return c
|
|
199
|
+
})
|
|
200
|
+
return transformSwitch(disc, clean)
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
// == → ===, != → !== (with prototype identity folding)
|
|
204
|
+
'=='(a, b) { return isProto(a) || isProto(b) ? 1 : ['===', transform(a), transform(b)] },
|
|
205
|
+
'!='(a, b) { return isProto(a) || isProto(b) ? 0 : ['!==', transform(a), transform(b)] },
|
|
206
|
+
'==='(a, b) { if (isProto(a) || isProto(b)) return 1 },
|
|
207
|
+
'!=='(a, b) { if (isProto(a) || isProto(b)) return 0 },
|
|
208
|
+
|
|
209
|
+
// new → call (keep TypedArrays)
|
|
210
|
+
'new'(ctor, ...cargs) {
|
|
211
|
+
const name = typeof ctor === 'string' ? ctor : (Array.isArray(ctor) && ctor[0] === '()' ? ctor[1] : null)
|
|
212
|
+
if (typeof name === 'string' && TYPED_ARRAYS.has(name)) return ['new', transform(ctor), ...cargs.map(transform)]
|
|
213
|
+
if (Array.isArray(ctor) && ctor[0] === '()') return transform(ctor)
|
|
214
|
+
return ['()', transform(ctor), ...cargs.map(transform)]
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
// instanceof → typeof / Array.isArray (jzify allows what strict mode prohibits)
|
|
218
|
+
'instanceof'(val, ctor) {
|
|
219
|
+
const t = transform(val)
|
|
220
|
+
const name = typeof ctor === 'string' ? ctor : (Array.isArray(ctor) && ctor[0] === '()' ? ctor[1] : null)
|
|
221
|
+
if (name === 'Array') return ['()', ['.', 'Array', 'isArray'], t]
|
|
222
|
+
if (name === 'Object') return ['===', ['typeof', t], [null, 'object']]
|
|
223
|
+
if (typeof name === 'string' && TYPED_ARRAYS.has(name)) return ['===', ['typeof', t], [null, 'object']]
|
|
224
|
+
return ['===', ['typeof', t], [null, 'object']]
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
// do { body } while (cond) → let _once = true; while (_once || cond) { _once = false; body }
|
|
228
|
+
// Avoids body duplication and preserves continue: `continue` jumps back to the
|
|
229
|
+
// while condition after the one-shot flag has been cleared.
|
|
230
|
+
'do'(body, cond) {
|
|
231
|
+
const flag = `do${doIdx++}`
|
|
232
|
+
return [';',
|
|
233
|
+
['let', ['=', flag, [null, true]]],
|
|
234
|
+
['while', ['||', flag, transform(cond)], ['{}', [';', ['=', flag, [null, false]], transform(body)]]]]
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
// Block body: recurse as scope for hoisting
|
|
238
|
+
'{}'(...args) { return ['{}', ...args.map(a => transformScope(a) ?? a)] },
|
|
239
|
+
|
|
240
|
+
// Export: recurse into exported declaration
|
|
241
|
+
'export'(inner) {
|
|
242
|
+
if (Array.isArray(inner) && inner[0] === 'default' && Array.isArray(inner[1]) && inner[1][0] === 'function' && inner[1][1]) {
|
|
243
|
+
const decl = transform(inner[1])
|
|
244
|
+
return [';', decl, ['export', ['default', inner[1][1]]]]
|
|
245
|
+
}
|
|
246
|
+
return ['export', transform(inner)]
|
|
247
|
+
},
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Transform a single AST node recursively. */
|
|
251
|
+
function transform(node) {
|
|
252
|
+
if (node == null || typeof node !== 'object' || !Array.isArray(node)) return node
|
|
253
|
+
const [op, ...args] = node
|
|
254
|
+
if (op == null) return node
|
|
255
|
+
const h = handlers[op]
|
|
256
|
+
return (h && h(...args)) ?? (h ? [op, ...args.map(transform)] : [op, ...args.map(transform)])
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** Transform switch statement to if/else chain. */
|
|
260
|
+
let swIdx = 0
|
|
261
|
+
function transformSwitch(discriminant, cases) {
|
|
262
|
+
const disc = transform(discriminant)
|
|
263
|
+
const tmp = `\uE000sw${swIdx++}`
|
|
264
|
+
|
|
265
|
+
// Collect case/default
|
|
266
|
+
const stmts = [['let', ['=', tmp, disc]]]
|
|
267
|
+
let chain = null
|
|
268
|
+
|
|
269
|
+
for (let i = cases.length - 1; i >= 0; i--) {
|
|
270
|
+
const c = cases[i]
|
|
271
|
+
if (c[0] === 'default') {
|
|
272
|
+
chain = transform(c[1])
|
|
273
|
+
} else if (c[0] === 'case') {
|
|
274
|
+
const cond = ['===', tmp, transform(c[1])]
|
|
275
|
+
const body = transform(c[2])
|
|
276
|
+
chain = chain != null ? ['if', cond, body, chain] : ['if', cond, body]
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (chain) stmts.push(chain)
|
|
280
|
+
return [';', ...stmts]
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// === AST → jz source codegen ===
|
|
284
|
+
|
|
285
|
+
const INDENT = ' '
|
|
286
|
+
const prec = { '=': 1, '+=': 1, '-=': 1, '*=': 1, '/=': 1, '%=': 1, '&=': 1, '|=': 1, '^=': 1, '>>=': 1, '<<=': 1, '>>>=': 1, '||=': 1, '&&=': 1,
|
|
287
|
+
'??': 2, '||': 3, '&&': 4, '|': 5, '^': 6, '&': 7, '===': 8, '!==': 8, '==': 8, '!=': 8,
|
|
288
|
+
'<': 9, '>': 9, '<=': 9, '>=': 9, '<<': 10, '>>': 10, '>>>': 10,
|
|
289
|
+
'+': 11, '-': 11, '*': 12, '/': 12, '%': 12, '**': 13 }
|
|
290
|
+
|
|
291
|
+
/** Wrap statement in { } if not already a block */
|
|
292
|
+
function wrapBlock(node, depth) {
|
|
293
|
+
if (Array.isArray(node) && node[0] === '{}') return codegen(node, depth)
|
|
294
|
+
return '{ ' + codegen(node, depth) + '; }'
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** Generate jz source from AST. Enforces semicolons. */
|
|
298
|
+
export function codegen(node, depth = 0) {
|
|
299
|
+
if (node == null) return ''
|
|
300
|
+
if (typeof node === 'number') return String(node)
|
|
301
|
+
if (typeof node === 'bigint') return node + 'n'
|
|
302
|
+
if (typeof node === 'string') return node
|
|
303
|
+
if (!Array.isArray(node)) return String(node)
|
|
304
|
+
|
|
305
|
+
const [op, ...a] = node
|
|
306
|
+
const ind = INDENT.repeat(depth), ind1 = INDENT.repeat(depth + 1)
|
|
307
|
+
|
|
308
|
+
// Literal: [, value]
|
|
309
|
+
if (op == null) return typeof a[0] === 'string' ? JSON.stringify(a[0]) : a[0] == null ? 'null' : String(a[0]) + (typeof a[0] === 'bigint' ? 'n' : '')
|
|
310
|
+
|
|
311
|
+
// Statements
|
|
312
|
+
if (op === ';') return a.map(s => codegen(s, depth)).filter(Boolean).join(';\n' + ind) + ';'
|
|
313
|
+
if (op === '{}') {
|
|
314
|
+
const body = a.map(s => codegen(s, depth + 1)).filter(Boolean).join(';\n' + ind1)
|
|
315
|
+
return '{\n' + ind1 + body + (body ? ';' : '') + '\n' + ind + '}'
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Declarations
|
|
319
|
+
if (op === 'let' || op === 'const') return op + ' ' + a.map(d => codegen(d, depth)).join(', ')
|
|
320
|
+
if (op === 'export') { const inner = codegen(a[0], depth); return inner ? 'export ' + inner : '' }
|
|
321
|
+
if (op === 'default') return 'default ' + codegen(a[0], depth)
|
|
322
|
+
|
|
323
|
+
// Control flow
|
|
324
|
+
if (op === 'if') {
|
|
325
|
+
const cond = codegen(a[0]), then = wrapBlock(a[1], depth)
|
|
326
|
+
return a[2] != null
|
|
327
|
+
? 'if (' + cond + ') ' + then + ' else ' + wrapBlock(a[2], depth)
|
|
328
|
+
: 'if (' + cond + ') ' + then
|
|
329
|
+
}
|
|
330
|
+
if (op === 'while') return 'while (' + codegen(a[0]) + ') ' + wrapBlock(a[1], depth)
|
|
331
|
+
if (op === 'for') {
|
|
332
|
+
if (a.length === 2) { // for...of / for...in
|
|
333
|
+
const [head, body] = a
|
|
334
|
+
if (Array.isArray(head) && (head[0] === 'of' || head[0] === 'in'))
|
|
335
|
+
return 'for (' + codegen(head[1]) + ' ' + head[0] + ' ' + codegen(head[2]) + ') ' + codegen(body, depth)
|
|
336
|
+
// ['let'/'const', ['in'/'of', name, obj]] — subscript wraps var→let around in/of
|
|
337
|
+
if (Array.isArray(head) && (head[0] === 'let' || head[0] === 'const') && Array.isArray(head[1]) && (head[1][0] === 'in' || head[1][0] === 'of'))
|
|
338
|
+
return 'for (' + head[0] + ' ' + codegen(head[1][1]) + ' ' + head[1][0] + ' ' + codegen(head[1][2]) + ') ' + codegen(body, depth)
|
|
339
|
+
return 'for (' + codegen(head) + ') ' + codegen(body, depth)
|
|
340
|
+
}
|
|
341
|
+
return 'for (' + (codegen(a[0]) || '') + '; ' + (codegen(a[1]) || '') + '; ' + (codegen(a[2]) || '') + ') ' + codegen(a[3], depth)
|
|
342
|
+
}
|
|
343
|
+
if (op === 'return') return 'return ' + codegen(a[0])
|
|
344
|
+
if (op === 'throw') return 'throw ' + codegen(a[0])
|
|
345
|
+
if (op === 'break') return 'break'
|
|
346
|
+
if (op === 'continue') return 'continue'
|
|
347
|
+
// catch with optional binding: ['catch', tryBlock, catchBody] or ['catch', tryBlock, paramName, catchBody]
|
|
348
|
+
if (op === 'catch') {
|
|
349
|
+
if (a.length === 3) return 'try ' + codegen(a[0], depth) + ' catch (' + a[1] + ') ' + codegen(a[2], depth)
|
|
350
|
+
return 'try ' + codegen(a[0], depth) + ' catch ' + codegen(a[1], depth)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Arrow
|
|
354
|
+
if (op === '=>') {
|
|
355
|
+
// Params: already wrapped in () by parser, or bare name
|
|
356
|
+
const p = a[0]
|
|
357
|
+
const params = Array.isArray(p) && p[0] === '()' ? codegen(p) : '(' + codegen(p) + ')'
|
|
358
|
+
const body = a[1]
|
|
359
|
+
const isBlock = Array.isArray(body) && (body[0] === '{}' || body[0] === ';' || body[0] === 'return')
|
|
360
|
+
const bodyStr = Array.isArray(body) && body[0] !== '{}' && isBlock
|
|
361
|
+
? '{ ' + codegen(body, depth) + '; }'
|
|
362
|
+
: codegen(body, depth)
|
|
363
|
+
return params + ' => ' + bodyStr
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Grouping parens / function call
|
|
367
|
+
if (op === '()') {
|
|
368
|
+
if (a.length === 1) return '(' + (a[0] == null ? '' : codegen(a[0])) + ')'
|
|
369
|
+
return codegen(a[0]) + '(' + a.slice(1).map(x => codegen(x)).join(', ') + ')'
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Property access
|
|
373
|
+
if (op === '.') return codegen(a[0]) + '.' + a[1]
|
|
374
|
+
if (op === '?.') return codegen(a[0]) + '?.' + a[1]
|
|
375
|
+
if (op === '[]') return codegen(a[0]) + '[' + codegen(a[1]) + ']'
|
|
376
|
+
|
|
377
|
+
// Array/object literals
|
|
378
|
+
if (op === '[') return '[' + a.map(x => codegen(x)).join(', ') + ']'
|
|
379
|
+
if (op === ':') return codegen(a[0]) + ': ' + codegen(a[1])
|
|
380
|
+
if (op === 'str') return JSON.stringify(a[0])
|
|
381
|
+
if (op === '//') return '/' + a[0] + '/' + (a[1] || '')
|
|
382
|
+
|
|
383
|
+
// Comma
|
|
384
|
+
if (op === ',') return a.map(x => codegen(x)).join(', ')
|
|
385
|
+
// Template literal: alternating string/expr parts. String parts are [null, "str"], expr parts are AST nodes.
|
|
386
|
+
if (op === '`') return '`' + a.map(p => {
|
|
387
|
+
if (Array.isArray(p) && p[0] == null && typeof p[1] === 'string') return p[1].replace(/[`\\$]/g, c => '\\' + c)
|
|
388
|
+
return '${' + codegen(p) + '}'
|
|
389
|
+
}).join('') + '`'
|
|
390
|
+
|
|
391
|
+
// Spread
|
|
392
|
+
if (op === '...') return '...' + codegen(a[0])
|
|
393
|
+
|
|
394
|
+
// Import
|
|
395
|
+
if (op === 'import') return 'import ' + codegen(a[0])
|
|
396
|
+
if (op === 'from') return codegen(a[0]) + ' from ' + codegen(a[1])
|
|
397
|
+
|
|
398
|
+
// Unary prefix
|
|
399
|
+
if (a.length === 1) {
|
|
400
|
+
if (op === '++' || op === '--') return a[0] == null ? op : op + codegen(a[0])
|
|
401
|
+
if (op === 'typeof') return 'typeof ' + codegen(a[0])
|
|
402
|
+
if (op === 'u-') return '-' + codegen(a[0])
|
|
403
|
+
if (op === 'u+') return '+' + codegen(a[0])
|
|
404
|
+
return op + codegen(a[0])
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Postfix
|
|
408
|
+
if (a.length === 2 && a[1] === null) return codegen(a[0]) + op
|
|
409
|
+
|
|
410
|
+
// Binary
|
|
411
|
+
if (a.length === 2 && prec[op]) return codegen(a[0]) + ' ' + op + ' ' + codegen(a[1])
|
|
412
|
+
|
|
413
|
+
// Ternary
|
|
414
|
+
if (op === '?' || op === '?:') return codegen(a[0]) + ' ? ' + codegen(a[1]) + ' : ' + codegen(a[2])
|
|
415
|
+
|
|
416
|
+
// Fallback
|
|
417
|
+
return op + '(' + a.map(x => codegen(x)).join(', ') + ')'
|
|
418
|
+
}
|
package/src/key.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** Static property-key evaluation for computed member names. */
|
|
2
|
+
|
|
3
|
+
const NO_VALUE = Symbol('no-static-property-key')
|
|
4
|
+
|
|
5
|
+
export function staticPropertyKey(node) {
|
|
6
|
+
const value = staticValue(node)
|
|
7
|
+
return value === NO_VALUE ? null : String(value)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function staticValue(node) {
|
|
11
|
+
if (node === undefined) return undefined
|
|
12
|
+
if (node === null || typeof node === 'number' || typeof node === 'string' || typeof node === 'boolean') return node
|
|
13
|
+
if (!Array.isArray(node)) return NO_VALUE
|
|
14
|
+
|
|
15
|
+
const [op, ...args] = node
|
|
16
|
+
if (op == null) return args.length ? args[0] : undefined
|
|
17
|
+
if (op === 'str') return args[0]
|
|
18
|
+
if (op === '[]' && args.length === 1) return staticValue(args[0])
|
|
19
|
+
if (op === '()' && args[0] === 'String' && args.length === 2) {
|
|
20
|
+
const value = staticValue(args[1])
|
|
21
|
+
return value === NO_VALUE ? NO_VALUE : String(value)
|
|
22
|
+
}
|
|
23
|
+
if (op === '()' && args[0] === 'Number' && args.length === 2) {
|
|
24
|
+
const value = staticValue(args[1])
|
|
25
|
+
return value === NO_VALUE ? NO_VALUE : Number(value)
|
|
26
|
+
}
|
|
27
|
+
if (op === '?:' || op === '?') {
|
|
28
|
+
const cond = staticValue(args[0])
|
|
29
|
+
return cond === NO_VALUE ? NO_VALUE : staticValue(cond ? args[1] : args[2])
|
|
30
|
+
}
|
|
31
|
+
if (op === '&&' || op === '||') {
|
|
32
|
+
const left = staticValue(args[0])
|
|
33
|
+
if (left === NO_VALUE) return NO_VALUE
|
|
34
|
+
return op === '&&' ? (left ? staticValue(args[1]) : left) : (left ? left : staticValue(args[1]))
|
|
35
|
+
}
|
|
36
|
+
if (op === '??') {
|
|
37
|
+
const left = staticValue(args[0])
|
|
38
|
+
return left === NO_VALUE ? NO_VALUE : left == null ? staticValue(args[1]) : left
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (args.length === 1) {
|
|
42
|
+
const value = staticValue(args[0])
|
|
43
|
+
if (value === NO_VALUE) return NO_VALUE
|
|
44
|
+
if (op === 'u+') return +value
|
|
45
|
+
if (op === 'u-') return -value
|
|
46
|
+
if (op === '!') return !value
|
|
47
|
+
if (op === '~') return ~value
|
|
48
|
+
return NO_VALUE
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (args.length === 2) {
|
|
52
|
+
const left = staticValue(args[0])
|
|
53
|
+
const right = staticValue(args[1])
|
|
54
|
+
if (left === NO_VALUE || right === NO_VALUE) return NO_VALUE
|
|
55
|
+
switch (op) {
|
|
56
|
+
case '+': return typeof left === 'string' || typeof right === 'string' ? String(left) + String(right) : Number(left) + Number(right)
|
|
57
|
+
case '-': return Number(left) - Number(right)
|
|
58
|
+
case '*': return Number(left) * Number(right)
|
|
59
|
+
case '/': return Number(left) / Number(right)
|
|
60
|
+
case '%': return Number(left) % Number(right)
|
|
61
|
+
case '**': return Number(left) ** Number(right)
|
|
62
|
+
case '&': return Number(left) & Number(right)
|
|
63
|
+
case '|': return Number(left) | Number(right)
|
|
64
|
+
case '^': return Number(left) ^ Number(right)
|
|
65
|
+
case '<<': return Number(left) << Number(right)
|
|
66
|
+
case '>>': return Number(left) >> Number(right)
|
|
67
|
+
case '>>>': return Number(left) >>> Number(right)
|
|
68
|
+
default: return NO_VALUE
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return NO_VALUE
|
|
73
|
+
}
|