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 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 # → program.wasm
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
- --strict Strict jz mode (no auto-transform)
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
- if (args[i] === '--output' || args[i] === '-o') outputFile = args[++i]
117
- else if (args[i] === '--wat') wat = true
118
- else if (args[i] === '--strict') strict = true
119
- else if (args[i] === '--resolve') resolveNode = true
120
- else if (args[i] === '--imports') importsFile = args[++i]
121
- else if (!inputFile) inputFile = args[i]
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jz",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Modern functional JS compiling to WASM",
5
5
  "main": "index.js",
6
6
  "type": "module",
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
- if (!paramsNeedLowering && !usesArguments(params) && !usesArguments(body)) return [params, body]
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