jz 0.3.0 → 0.3.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/README.md +12 -1
- package/cli.js +35 -7
- package/package.json +1 -1
- package/src/jzify.js +43 -3
package/README.md
CHANGED
|
@@ -82,7 +82,16 @@ Options are passed as `jz(source, opts)` or `compile(source, opts)`. Common ones
|
|
|
82
82
|
|
|
83
83
|
```sh
|
|
84
84
|
# Compile
|
|
85
|
-
jz program.js
|
|
85
|
+
jz program.js # → program.wasm
|
|
86
|
+
jz program.js --wat # → program.wat
|
|
87
|
+
jz program.js -o out.wasm # custom output (- for stdout)
|
|
88
|
+
|
|
89
|
+
# Optimization level: -O0 off, -O1 size-only, -O2 default, -O3 aggressive
|
|
90
|
+
# aliases: -Os/--optimize size, -Ob/balanced, -Of/speed
|
|
91
|
+
jz program.js -O3
|
|
92
|
+
|
|
93
|
+
# Runtime-service lowering: js (default) or wasi
|
|
94
|
+
jz program.js --host wasi
|
|
86
95
|
|
|
87
96
|
# Evaluate
|
|
88
97
|
jz -e "1 + 2" # 3
|
|
@@ -91,6 +100,8 @@ jz -e "1 + 2" # 3
|
|
|
91
100
|
jz --help
|
|
92
101
|
```
|
|
93
102
|
|
|
103
|
+
Other flags: `--strict` (no auto-`jzify`, reject dynamic fallbacks), `--jzify` (transform JS → jz, no compile), `--no-alloc` (omit `_alloc`/`_clear`), `--names` (emit wasm `name` section), `--resolve` (Node.js bare-specifier resolution), `--imports <file.json>` (host import specs).
|
|
104
|
+
|
|
94
105
|
## Language
|
|
95
106
|
|
|
96
107
|
JZ is a strict functional JS subset. Built-in `jzify` transform extends support to legacy patterns.
|
package/cli.js
CHANGED
|
@@ -32,13 +32,21 @@ Examples:
|
|
|
32
32
|
jz program.js --wat # → program.wat
|
|
33
33
|
jz program.js -o out.wasm # custom output name
|
|
34
34
|
jz program.js -o - # write to stdout
|
|
35
|
+
jz program.js -O3 # aggressive optimization
|
|
36
|
+
jz program.js -Os # optimize for size
|
|
37
|
+
jz program.js --host wasi # emit WASI Preview 1 imports
|
|
35
38
|
jz --strict program.js # strict mode
|
|
36
39
|
jz --jzify lib.js # → lib.jz
|
|
37
40
|
jz -e "1 + 2"
|
|
38
41
|
|
|
39
42
|
Options:
|
|
40
43
|
--output, -o <file> Output file (.wat, .wasm, or - for stdout)
|
|
41
|
-
--
|
|
44
|
+
-O<n>, --optimize <n> Optimization level: 0 off, 1 size-only, 2 default,
|
|
45
|
+
3 aggressive. Aliases: -Os/size, -Ob/balanced, -Of/speed.
|
|
46
|
+
--host <js|wasi> Runtime-service lowering (default js)
|
|
47
|
+
--no-alloc Omit _alloc/_clear allocator exports (standalone wasm)
|
|
48
|
+
--names Emit wasm name section for profilers/debuggers
|
|
49
|
+
--strict Strict jz mode (no auto-transform), reject dynamic fallbacks
|
|
42
50
|
--jzify Transform JS to jz (no compilation)
|
|
43
51
|
--eval, -e Evaluate expression or file
|
|
44
52
|
--wat Output WAT text instead of binary
|
|
@@ -109,16 +117,31 @@ async function handleJzify(args) {
|
|
|
109
117
|
}
|
|
110
118
|
}
|
|
111
119
|
|
|
120
|
+
// -O<n>/-Os/-Ob/-Of and --optimize <val> → value accepted by compile()'s `optimize` opt
|
|
121
|
+
const OPT_ALIAS = { s: 'size', b: 'balanced', f: 'speed' }
|
|
122
|
+
function parseOptimize(v) {
|
|
123
|
+
if (v == null) return undefined
|
|
124
|
+
if (/^\d+$/.test(v)) return +v
|
|
125
|
+
return OPT_ALIAS[v] ?? v
|
|
126
|
+
}
|
|
127
|
+
|
|
112
128
|
async function handleCompile(args) {
|
|
113
129
|
let inputFile = null, outputFile = null, wat = false, strict = false, resolveNode = false, importsFile = null
|
|
130
|
+
let optimize, host, alloc = true, names = false
|
|
114
131
|
|
|
115
132
|
for (let i = 0; i < args.length; i++) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
else if (
|
|
119
|
-
else if (
|
|
120
|
-
else if (
|
|
121
|
-
else if (
|
|
133
|
+
const a = args[i]
|
|
134
|
+
if (a === '--output' || a === '-o') outputFile = args[++i]
|
|
135
|
+
else if (a === '--wat') wat = true
|
|
136
|
+
else if (a === '--strict') strict = true
|
|
137
|
+
else if (a === '--resolve') resolveNode = true
|
|
138
|
+
else if (a === '--imports') importsFile = args[++i]
|
|
139
|
+
else if (a === '--optimize' || a === '-O') optimize = parseOptimize(args[++i])
|
|
140
|
+
else if (/^-O.+/.test(a)) optimize = parseOptimize(a.slice(2))
|
|
141
|
+
else if (a === '--host') host = args[++i]
|
|
142
|
+
else if (a === '--no-alloc') alloc = false
|
|
143
|
+
else if (a === '--names') names = true
|
|
144
|
+
else if (!inputFile) inputFile = a
|
|
122
145
|
}
|
|
123
146
|
|
|
124
147
|
if (!inputFile) throw new Error('No input file specified')
|
|
@@ -178,7 +201,12 @@ async function handleCompile(args) {
|
|
|
178
201
|
const opts = {
|
|
179
202
|
wat,
|
|
180
203
|
jzify: !strict && !inputFile.endsWith('.jz'),
|
|
204
|
+
strict,
|
|
181
205
|
importMetaUrl: pathToFileURL(resolve(inputFile)).href,
|
|
206
|
+
...(optimize !== undefined && { optimize }),
|
|
207
|
+
...(host && { host }),
|
|
208
|
+
...(alloc === false && { alloc: false }),
|
|
209
|
+
...(names && { profileNames: true }),
|
|
182
210
|
...(Object.keys(modules).length && { modules }),
|
|
183
211
|
}
|
|
184
212
|
|
package/package.json
CHANGED
package/src/jzify.js
CHANGED
|
@@ -210,13 +210,35 @@ function transformScope(node) {
|
|
|
210
210
|
// Hoist functions AFTER imports (imports must be processed first for scope resolution)
|
|
211
211
|
const imports = rest.filter(s => Array.isArray(s) && s[0] === 'import')
|
|
212
212
|
const nonImports = rest.filter(s => !(Array.isArray(s) && s[0] === 'import'))
|
|
213
|
-
const all = [...imports, ...hoisted, ...nonImports]
|
|
213
|
+
const all = dedupeRedecls([...imports, ...hoisted, ...nonImports])
|
|
214
214
|
return all.length === 0 ? null : all.length === 1 ? all[0] : [';', ...all]
|
|
215
215
|
}
|
|
216
216
|
|
|
217
217
|
return transform(node)
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
/**
|
|
221
|
+
* Drop redundant re-declarations of the same name within one scope's statement
|
|
222
|
+
* list. JS allows `function f(){} var f;`, `var x; var x;`, `var x = 1; var x;` —
|
|
223
|
+
* jzify lowers `function`→`const` and `var`→`let`, which would otherwise emit two
|
|
224
|
+
* bindings for one slot (and a typed-slot clash in codegen). The first declaration
|
|
225
|
+
* wins; a later redeclaration keeps only its initializer, as a plain assignment.
|
|
226
|
+
*/
|
|
227
|
+
function dedupeRedecls(stmts) {
|
|
228
|
+
const nameOf = s => Array.isArray(s) && (s[0] === 'let' || s[0] === 'const' || s[0] === 'var')
|
|
229
|
+
? (typeof s[1] === 'string' ? s[1]
|
|
230
|
+
: Array.isArray(s[1]) && s[1][0] === '=' && typeof s[1][1] === 'string' ? s[1][1] : null)
|
|
231
|
+
: null
|
|
232
|
+
const seen = new Set(), out = []
|
|
233
|
+
for (const s of stmts) {
|
|
234
|
+
const n = nameOf(s)
|
|
235
|
+
if (n == null) { out.push(s); continue }
|
|
236
|
+
if (seen.has(n)) { if (Array.isArray(s[1]) && s[1][0] === '=') out.push(['=', s[1][1], s[1][2]]); continue }
|
|
237
|
+
seen.add(n); out.push(s)
|
|
238
|
+
}
|
|
239
|
+
return out
|
|
240
|
+
}
|
|
241
|
+
|
|
220
242
|
/** Wrap function body for arrow conversion */
|
|
221
243
|
function wrapArrowBody(body) {
|
|
222
244
|
const t = transformScope(body)
|
|
@@ -247,6 +269,19 @@ function usesArguments(node) {
|
|
|
247
269
|
return false
|
|
248
270
|
}
|
|
249
271
|
|
|
272
|
+
// `arguments` is the implicit object only if the function body doesn't declare a
|
|
273
|
+
// local of that name. Scan the body's own statement list (not nested scopes) for
|
|
274
|
+
// `var/let/const arguments` — a regular `function` with `var arguments;` just has
|
|
275
|
+
// an ordinary local, no arguments object.
|
|
276
|
+
function bindsArguments(body) {
|
|
277
|
+
const isArgDecl = s => Array.isArray(s) && (s[0] === 'var' || s[0] === 'let' || s[0] === 'const') &&
|
|
278
|
+
s.slice(1).some(d => d === 'arguments' || (Array.isArray(d) && d[0] === '=' && d[1] === 'arguments'))
|
|
279
|
+
let n = body
|
|
280
|
+
if (Array.isArray(n) && n[0] === '{}') n = n[1]
|
|
281
|
+
if (Array.isArray(n) && n[0] === ';') return n.slice(1).some(isArgDecl)
|
|
282
|
+
return isArgDecl(n)
|
|
283
|
+
}
|
|
284
|
+
|
|
250
285
|
function renameArguments(node, to) {
|
|
251
286
|
if (node === 'arguments') return to
|
|
252
287
|
if (!Array.isArray(node)) return node
|
|
@@ -278,8 +313,13 @@ function paramList(params) {
|
|
|
278
313
|
const isDestructurePat = p => Array.isArray(p) && (p[0] === '[]' || p[0] === '{}' || (p[0] === '=' && isDestructurePat(p[1])))
|
|
279
314
|
|
|
280
315
|
function lowerArguments(params, body) {
|
|
316
|
+
// A function body that declares its own `arguments` local: it's an ordinary
|
|
317
|
+
// variable, not the implicit object \u2014 rename it out of jz's reserved set,
|
|
318
|
+
// no rest param synthesized.
|
|
319
|
+
if (bindsArguments(body)) body = renameArguments(body, `\uE001arg${argsIdx++}`)
|
|
281
320
|
const paramsNeedLowering = paramList(params).some(isDestructurePat)
|
|
282
|
-
|
|
321
|
+
const usesArgsObj = usesArguments(params) || usesArguments(body)
|
|
322
|
+
if (!paramsNeedLowering && !usesArgsObj) return [params, body]
|
|
283
323
|
const name = `\uE001arg${argsIdx++}`
|
|
284
324
|
const decls = []
|
|
285
325
|
for (const [idx, param] of paramList(params).entries()) {
|
|
@@ -293,7 +333,7 @@ function lowerArguments(params, body) {
|
|
|
293
333
|
}
|
|
294
334
|
decls.push(['=', param, ['[]', name, [null, idx]]])
|
|
295
335
|
}
|
|
296
|
-
const renamed = renameArguments(body, name)
|
|
336
|
+
const renamed = usesArgsObj ? renameArguments(body, name) : body
|
|
297
337
|
return [['()', ['...', name]], decls.length ? prependParamDecls(['let', ...decls], renamed) : renamed]
|
|
298
338
|
}
|
|
299
339
|
|