jz 0.0.0 → 0.1.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/LICENSE +21 -0
- package/README.md +381 -0
- package/cli.js +163 -0
- package/index.js +217 -0
- package/module/array.js +1317 -0
- package/module/collection.js +791 -0
- package/module/console.js +190 -0
- package/module/core.js +642 -0
- package/module/function.js +180 -0
- package/module/index.js +15 -0
- package/module/json.js +504 -0
- package/module/math.js +389 -0
- package/module/number.js +605 -0
- package/module/object.js +357 -0
- package/module/regex.js +913 -0
- package/module/schema.js +104 -0
- package/module/string.js +928 -0
- package/module/symbol.js +54 -0
- package/module/timer.js +253 -0
- package/module/typedarray.js +711 -0
- package/package.json +54 -5
- package/src/analyze.js +1906 -0
- package/src/compile.js +2175 -0
- package/src/ctx.js +243 -0
- package/src/emit.js +2095 -0
- package/src/host.js +524 -0
- package/src/ir.js +649 -0
- package/src/jzify.js +391 -0
- package/src/optimize.js +1352 -0
- package/src/prepare.js +1598 -0
- package/wasi.js +74 -0
package/src/jzify.js
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
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(body)) return [params, body]
|
|
126
|
+
const name = `\uE001arg${argsIdx++}`
|
|
127
|
+
const items = paramList(params)
|
|
128
|
+
items.push(['...', name])
|
|
129
|
+
const inner = items.length === 1 ? items[0] : [',', ...items]
|
|
130
|
+
return [['()', inner], renameArguments(body, name)]
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const handlers = {
|
|
134
|
+
// Named IIFE: (function name(p){b})(a) → let name = arrow; name(a)
|
|
135
|
+
'()'(callee, ...rest) {
|
|
136
|
+
if (Array.isArray(callee) && callee[0] === '()' && Array.isArray(callee[1]) && callee[1][0] === 'function' && callee[1][1]) {
|
|
137
|
+
const [, name, params, body] = callee[1]
|
|
138
|
+
const [p2, b2] = lowerArguments(params, body)
|
|
139
|
+
return [';', ['let', ['=', name, ['=>', p2, wrapArrowBody(b2)]]], ['()', name, ...rest.map(transform)]]
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
// function → arrow (named → hoisted const)
|
|
144
|
+
'function'(name, params, body) {
|
|
145
|
+
const [p2, b2] = lowerArguments(params, body)
|
|
146
|
+
const arrow = ['=>', p2, wrapArrowBody(b2)]
|
|
147
|
+
if (name) { const decl = ['const', ['=', name, arrow]]; decl._hoisted = true; return decl }
|
|
148
|
+
return arrow
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
'var'(...args) { return ['let', ...args.map(transform)] },
|
|
152
|
+
|
|
153
|
+
'='(lhs, rhs) {
|
|
154
|
+
// var assignment: ['=', ['var', name], init] → let
|
|
155
|
+
if (Array.isArray(lhs) && lhs[0] === 'var')
|
|
156
|
+
return ['let', ['=', lhs[1], transform(rhs)]]
|
|
157
|
+
// Chained property assignment: a.x = a.y = v → a.y = v; a.x = v
|
|
158
|
+
if (Array.isArray(lhs) && lhs[0] === '.' && Array.isArray(rhs) && rhs[0] === '=') {
|
|
159
|
+
const targets = []
|
|
160
|
+
let cur = ['=', lhs, rhs]
|
|
161
|
+
while (Array.isArray(cur) && cur[0] === '=') { targets.push(cur[1]); cur = cur[2] }
|
|
162
|
+
const val = transform(cur)
|
|
163
|
+
const stmts = []
|
|
164
|
+
for (let i = targets.length - 1; i >= 0; i--) stmts.push(['=', transform(targets[i]), val])
|
|
165
|
+
return stmts.length === 1 ? stmts[0] : [';', ...stmts]
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
'switch'(disc, ...cases) {
|
|
170
|
+
const clean = cases.map(c => {
|
|
171
|
+
if (c[0] === 'case' && Array.isArray(c[2]) && c[2][0] === ';') {
|
|
172
|
+
const body = c[2].slice(1).filter(s => typeof s !== 'number')
|
|
173
|
+
return ['case', c[1], body.length === 1 ? body[0] : [';', ...body]]
|
|
174
|
+
}
|
|
175
|
+
if (c[0] === 'default' && Array.isArray(c[1]) && c[1][0] === ';') {
|
|
176
|
+
const body = c[1].slice(1).filter(s => s != null && typeof s !== 'number')
|
|
177
|
+
return ['default', body.length === 1 ? body[0] : [';', ...body]]
|
|
178
|
+
}
|
|
179
|
+
return c
|
|
180
|
+
})
|
|
181
|
+
return transformSwitch(disc, clean)
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
// == → ===, != → !== (with prototype identity folding)
|
|
185
|
+
'=='(a, b) { return isProto(a) || isProto(b) ? 1 : ['===', transform(a), transform(b)] },
|
|
186
|
+
'!='(a, b) { return isProto(a) || isProto(b) ? 0 : ['!==', transform(a), transform(b)] },
|
|
187
|
+
'==='(a, b) { if (isProto(a) || isProto(b)) return 1 },
|
|
188
|
+
'!=='(a, b) { if (isProto(a) || isProto(b)) return 0 },
|
|
189
|
+
|
|
190
|
+
// new → call (keep TypedArrays)
|
|
191
|
+
'new'(ctor, ...cargs) {
|
|
192
|
+
const name = typeof ctor === 'string' ? ctor : (Array.isArray(ctor) && ctor[0] === '()' ? ctor[1] : null)
|
|
193
|
+
if (typeof name === 'string' && TYPED_ARRAYS.has(name)) return ['new', transform(ctor), ...cargs.map(transform)]
|
|
194
|
+
if (Array.isArray(ctor) && ctor[0] === '()') return transform(ctor)
|
|
195
|
+
return ['()', transform(ctor), ...cargs.map(transform)]
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
// instanceof → typeof / Array.isArray (jzify allows what strict mode prohibits)
|
|
199
|
+
'instanceof'(val, ctor) {
|
|
200
|
+
const t = transform(val)
|
|
201
|
+
const name = typeof ctor === 'string' ? ctor : (Array.isArray(ctor) && ctor[0] === '()' ? ctor[1] : null)
|
|
202
|
+
if (name === 'Array') return ['()', ['.', 'Array', 'isArray'], t]
|
|
203
|
+
if (name === 'Object') return ['===', ['typeof', t], [null, 'object']]
|
|
204
|
+
if (typeof name === 'string' && TYPED_ARRAYS.has(name)) return ['===', ['typeof', t], [null, 'object']]
|
|
205
|
+
return ['===', ['typeof', t], [null, 'object']]
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
// do { body } while (cond) → for (let _once = true; _once || cond; _once = false) body
|
|
209
|
+
// Avoids body duplication; matches JS continue semantics (continue runs cond, not body).
|
|
210
|
+
'do'(body, cond) {
|
|
211
|
+
const flag = `do${doIdx++}`
|
|
212
|
+
return ['for',
|
|
213
|
+
[';',
|
|
214
|
+
['let', ['=', flag, [null, true]]],
|
|
215
|
+
['||', flag, transform(cond)],
|
|
216
|
+
['=', flag, [null, false]]],
|
|
217
|
+
transform(body)]
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
// Block body: recurse as scope for hoisting
|
|
221
|
+
'{}'(...args) { return ['{}', ...args.map(a => transformScope(a) ?? a)] },
|
|
222
|
+
|
|
223
|
+
// Export: recurse into exported declaration
|
|
224
|
+
'export'(inner) {
|
|
225
|
+
if (Array.isArray(inner) && inner[0] === 'default' && Array.isArray(inner[1]) && inner[1][0] === 'function' && inner[1][1]) {
|
|
226
|
+
const decl = transform(inner[1])
|
|
227
|
+
return [';', decl, ['export', ['default', inner[1][1]]]]
|
|
228
|
+
}
|
|
229
|
+
return ['export', transform(inner)]
|
|
230
|
+
},
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Transform a single AST node recursively. */
|
|
234
|
+
function transform(node) {
|
|
235
|
+
if (node == null || typeof node !== 'object' || !Array.isArray(node)) return node
|
|
236
|
+
const [op, ...args] = node
|
|
237
|
+
if (op == null) return node
|
|
238
|
+
const h = handlers[op]
|
|
239
|
+
return (h && h(...args)) ?? (h ? [op, ...args.map(transform)] : [op, ...args.map(transform)])
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** Transform switch statement to if/else chain. */
|
|
243
|
+
let swIdx = 0
|
|
244
|
+
function transformSwitch(discriminant, cases) {
|
|
245
|
+
const disc = transform(discriminant)
|
|
246
|
+
const tmp = `\uE000sw${swIdx++}`
|
|
247
|
+
|
|
248
|
+
// Collect case/default
|
|
249
|
+
const stmts = [['let', ['=', tmp, disc]]]
|
|
250
|
+
let chain = null
|
|
251
|
+
|
|
252
|
+
for (let i = cases.length - 1; i >= 0; i--) {
|
|
253
|
+
const c = cases[i]
|
|
254
|
+
if (c[0] === 'default') {
|
|
255
|
+
chain = transform(c[1])
|
|
256
|
+
} else if (c[0] === 'case') {
|
|
257
|
+
const cond = ['===', tmp, transform(c[1])]
|
|
258
|
+
const body = transform(c[2])
|
|
259
|
+
chain = chain != null ? ['if', cond, body, chain] : ['if', cond, body]
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (chain) stmts.push(chain)
|
|
263
|
+
return [';', ...stmts]
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// === AST → jz source codegen ===
|
|
267
|
+
|
|
268
|
+
const INDENT = ' '
|
|
269
|
+
const prec = { '=': 1, '+=': 1, '-=': 1, '*=': 1, '/=': 1, '%=': 1, '&=': 1, '|=': 1, '^=': 1, '>>=': 1, '<<=': 1, '>>>=': 1, '||=': 1, '&&=': 1,
|
|
270
|
+
'??': 2, '||': 3, '&&': 4, '|': 5, '^': 6, '&': 7, '===': 8, '!==': 8, '==': 8, '!=': 8,
|
|
271
|
+
'<': 9, '>': 9, '<=': 9, '>=': 9, '<<': 10, '>>': 10, '>>>': 10,
|
|
272
|
+
'+': 11, '-': 11, '*': 12, '/': 12, '%': 12, '**': 13 }
|
|
273
|
+
|
|
274
|
+
/** Wrap statement in { } if not already a block */
|
|
275
|
+
function wrapBlock(node, depth) {
|
|
276
|
+
if (Array.isArray(node) && node[0] === '{}') return codegen(node, depth)
|
|
277
|
+
return '{ ' + codegen(node, depth) + '; }'
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** Generate jz source from AST. Enforces semicolons. */
|
|
281
|
+
export function codegen(node, depth = 0) {
|
|
282
|
+
if (node == null) return ''
|
|
283
|
+
if (typeof node === 'number') return String(node)
|
|
284
|
+
if (typeof node === 'bigint') return node + 'n'
|
|
285
|
+
if (typeof node === 'string') return node
|
|
286
|
+
if (!Array.isArray(node)) return String(node)
|
|
287
|
+
|
|
288
|
+
const [op, ...a] = node
|
|
289
|
+
const ind = INDENT.repeat(depth), ind1 = INDENT.repeat(depth + 1)
|
|
290
|
+
|
|
291
|
+
// Literal: [, value]
|
|
292
|
+
if (op == null) return typeof a[0] === 'string' ? JSON.stringify(a[0]) : a[0] == null ? 'null' : String(a[0]) + (typeof a[0] === 'bigint' ? 'n' : '')
|
|
293
|
+
|
|
294
|
+
// Statements
|
|
295
|
+
if (op === ';') return a.map(s => codegen(s, depth)).filter(Boolean).join(';\n' + ind) + ';'
|
|
296
|
+
if (op === '{}') {
|
|
297
|
+
const body = a.map(s => codegen(s, depth + 1)).filter(Boolean).join(';\n' + ind1)
|
|
298
|
+
return '{\n' + ind1 + body + (body ? ';' : '') + '\n' + ind + '}'
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Declarations
|
|
302
|
+
if (op === 'let' || op === 'const') return op + ' ' + a.map(d => codegen(d, depth)).join(', ')
|
|
303
|
+
if (op === 'export') { const inner = codegen(a[0], depth); return inner ? 'export ' + inner : '' }
|
|
304
|
+
if (op === 'default') return 'default ' + codegen(a[0], depth)
|
|
305
|
+
|
|
306
|
+
// Control flow
|
|
307
|
+
if (op === 'if') {
|
|
308
|
+
const cond = codegen(a[0]), then = wrapBlock(a[1], depth)
|
|
309
|
+
return a[2] != null
|
|
310
|
+
? 'if (' + cond + ') ' + then + ' else ' + wrapBlock(a[2], depth)
|
|
311
|
+
: 'if (' + cond + ') ' + then
|
|
312
|
+
}
|
|
313
|
+
if (op === 'while') return 'while (' + codegen(a[0]) + ') ' + wrapBlock(a[1], depth)
|
|
314
|
+
if (op === 'for') {
|
|
315
|
+
if (a.length === 2) { // for...of / for...in
|
|
316
|
+
const [head, body] = a
|
|
317
|
+
if (Array.isArray(head) && (head[0] === 'of' || head[0] === 'in'))
|
|
318
|
+
return 'for (' + codegen(head[1]) + ' ' + head[0] + ' ' + codegen(head[2]) + ') ' + codegen(body, depth)
|
|
319
|
+
return 'for (' + codegen(head) + ') ' + codegen(body, depth)
|
|
320
|
+
}
|
|
321
|
+
return 'for (' + (codegen(a[0]) || '') + '; ' + (codegen(a[1]) || '') + '; ' + (codegen(a[2]) || '') + ') ' + codegen(a[3], depth)
|
|
322
|
+
}
|
|
323
|
+
if (op === 'return') return 'return ' + codegen(a[0])
|
|
324
|
+
if (op === 'throw') return 'throw ' + codegen(a[0])
|
|
325
|
+
if (op === 'break') return 'break'
|
|
326
|
+
if (op === 'continue') return 'continue'
|
|
327
|
+
if (op === 'catch') return 'try ' + codegen(a[0], depth) + ' catch (' + a[1] + ') ' + codegen(a[2], depth)
|
|
328
|
+
|
|
329
|
+
// Arrow
|
|
330
|
+
if (op === '=>') {
|
|
331
|
+
// Params: already wrapped in () by parser, or bare name
|
|
332
|
+
const p = a[0]
|
|
333
|
+
const params = Array.isArray(p) && p[0] === '()' ? codegen(p) : '(' + codegen(p) + ')'
|
|
334
|
+
const body = a[1]
|
|
335
|
+
const isBlock = Array.isArray(body) && (body[0] === '{}' || body[0] === ';' || body[0] === 'return')
|
|
336
|
+
const bodyStr = Array.isArray(body) && body[0] !== '{}' && isBlock
|
|
337
|
+
? '{ ' + codegen(body, depth) + '; }'
|
|
338
|
+
: codegen(body, depth)
|
|
339
|
+
return params + ' => ' + bodyStr
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Grouping parens / function call
|
|
343
|
+
if (op === '()') {
|
|
344
|
+
if (a.length === 1) return '(' + (a[0] == null ? '' : codegen(a[0])) + ')'
|
|
345
|
+
return codegen(a[0]) + '(' + a.slice(1).map(x => codegen(x)).join(', ') + ')'
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Property access
|
|
349
|
+
if (op === '.') return codegen(a[0]) + '.' + a[1]
|
|
350
|
+
if (op === '?.') return codegen(a[0]) + '?.' + a[1]
|
|
351
|
+
if (op === '[]') return codegen(a[0]) + '[' + codegen(a[1]) + ']'
|
|
352
|
+
|
|
353
|
+
// Array/object literals
|
|
354
|
+
if (op === '[') return '[' + a.map(x => codegen(x)).join(', ') + ']'
|
|
355
|
+
if (op === ':') return codegen(a[0]) + ': ' + codegen(a[1])
|
|
356
|
+
if (op === 'str') return JSON.stringify(a[0])
|
|
357
|
+
if (op === '//') return '/' + a[0] + '/' + (a[1] || '')
|
|
358
|
+
|
|
359
|
+
// Comma
|
|
360
|
+
if (op === ',') return a.map(x => codegen(x)).join(', ')
|
|
361
|
+
// Template literal
|
|
362
|
+
if (op === '`') return '`' + a.map((p, i) => i % 2 ? '${' + codegen(p) + '}' : (p?.[1] ?? '')).join('') + '`'
|
|
363
|
+
|
|
364
|
+
// Spread
|
|
365
|
+
if (op === '...') return '...' + codegen(a[0])
|
|
366
|
+
|
|
367
|
+
// Import
|
|
368
|
+
if (op === 'import') return 'import ' + codegen(a[0])
|
|
369
|
+
if (op === 'from') return codegen(a[0]) + ' from ' + codegen(a[1])
|
|
370
|
+
|
|
371
|
+
// Unary prefix
|
|
372
|
+
if (a.length === 1) {
|
|
373
|
+
if (op === '++' || op === '--') return a[0] == null ? op : op + codegen(a[0])
|
|
374
|
+
if (op === 'typeof') return 'typeof ' + codegen(a[0])
|
|
375
|
+
if (op === 'u-') return '-' + codegen(a[0])
|
|
376
|
+
if (op === 'u+') return '+' + codegen(a[0])
|
|
377
|
+
return op + codegen(a[0])
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Postfix
|
|
381
|
+
if (a.length === 2 && a[1] === null) return codegen(a[0]) + op
|
|
382
|
+
|
|
383
|
+
// Binary
|
|
384
|
+
if (a.length === 2 && prec[op]) return codegen(a[0]) + ' ' + op + ' ' + codegen(a[1])
|
|
385
|
+
|
|
386
|
+
// Ternary
|
|
387
|
+
if (op === '?' || op === '?:') return codegen(a[0]) + ' ? ' + codegen(a[1]) + ' : ' + codegen(a[2])
|
|
388
|
+
|
|
389
|
+
// Fallback
|
|
390
|
+
return op + '(' + a.map(x => codegen(x)).join(', ') + ')'
|
|
391
|
+
}
|