gofront 1.5.1 → 1.6.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 +174 -466
- package/package.json +16 -20
- package/src/backend/js/expressions.js +288 -128
- package/src/backend/js/index.js +138 -56
- package/src/backend/js/runtime.js +1055 -53
- package/src/backend/js/source-map.js +32 -0
- package/src/backend/js/statements.js +151 -83
- package/src/backend/js/stdlib/base64.js +12 -0
- package/src/backend/js/stdlib/binary.js +102 -0
- package/src/backend/js/stdlib/bits.js +15 -1
- package/src/backend/js/stdlib/builder.js +28 -9
- package/src/backend/js/stdlib/bytes.js +75 -18
- package/src/backend/js/stdlib/errors.js +22 -3
- package/src/backend/js/stdlib/fmt.js +95 -20
- package/src/backend/js/stdlib/hex.js +17 -0
- package/src/backend/js/stdlib/html.js +1 -1
- package/src/backend/js/stdlib/index.js +19 -0
- package/src/backend/js/stdlib/io.js +105 -3
- package/src/backend/js/stdlib/maps.js +1 -1
- package/src/backend/js/stdlib/math.js +13 -1
- package/src/backend/js/stdlib/path.js +20 -6
- package/src/backend/js/stdlib/rand.js +10 -5
- package/src/backend/js/stdlib/sha256.js +12 -0
- package/src/backend/js/stdlib/shared.js +17 -0
- package/src/backend/js/stdlib/slices.js +53 -27
- package/src/backend/js/stdlib/sort.js +1 -1
- package/src/backend/js/stdlib/strconv.js +52 -11
- package/src/backend/js/stdlib/strings.js +152 -16
- package/src/backend/js/stdlib/sync.js +13 -0
- package/src/backend/js/stdlib/testing.js +2 -2
- package/src/backend/js/stdlib/time.js +93 -22
- package/src/backend/js/stdlib/unicode.js +2 -2
- package/src/backend/js/templ.js +1 -1
- package/src/backend/wasm/boundary.js +1677 -58
- package/src/backend/wasm/const-fold.js +205 -0
- package/src/backend/wasm/emit-builtins.js +1230 -0
- package/src/backend/wasm/emit-exprs.js +2096 -0
- package/src/backend/wasm/emit-maps.js +1999 -0
- package/src/backend/wasm/emit-shared.js +519 -0
- package/src/backend/wasm/emit-stdlib.js +13 -0
- package/src/backend/wasm/emit-stmts.js +2466 -0
- package/src/backend/wasm/emit.js +656 -5031
- package/src/backend/wasm/encode.js +259 -85
- package/src/backend/wasm/glue.js +19 -5
- package/src/backend/wasm/index.js +2432 -86
- package/src/backend/wasm/monomorph.js +711 -0
- package/src/backend/wasm/optimize.js +236 -0
- package/src/backend/wasm/stdlib/bytes.js +5039 -0
- package/src/backend/wasm/stdlib/core.js +8903 -0
- package/src/backend/wasm/stdlib/index.js +40 -0
- package/src/backend/wasm/stdlib/io.js +2161 -0
- package/src/backend/wasm/stdlib/packages.js +2400 -0
- package/src/backend/wasm/stdlib/pure.js +720 -0
- package/src/backend/wasm/stdlib/time.js +1200 -0
- package/src/backend/wasm/types.js +312 -36
- package/src/backend/wasm/wat.js +49 -2
- package/src/cli-core.js +276 -73
- package/src/compiler.js +162 -25
- package/src/dts-parser.js +79 -126
- package/src/index.js +88 -121
- package/src/lexer.js +80 -29
- package/src/lower/boxing.js +4 -6
- package/src/lower/captures.js +12 -23
- package/src/lower/escape.js +4 -9
- package/src/lower/functions.js +277 -5
- package/src/lower/globals.js +7 -15
- package/src/lower/index.js +5 -2
- package/src/lower/ownership.js +35 -19
- package/src/lower/range.js +2 -5
- package/src/lower/walk.js +25 -0
- package/src/minifier.js +20 -68
- package/src/parser/declarations.js +3 -6
- package/src/parser/expressions.js +3 -8
- package/src/parser/index.js +18 -3
- package/src/parser/statements.js +25 -38
- package/src/parser/types.js +1 -2
- package/src/resolver.js +3 -27
- package/src/templ-lexer.js +29 -63
- package/src/templ-parser.js +22 -45
- package/src/test-runner.js +20 -0
- package/src/typechecker/assignability.js +123 -14
- package/src/typechecker/expressions.js +244 -4
- package/src/typechecker/index.js +27 -1
- package/src/typechecker/resolve.js +71 -12
- package/src/typechecker/statements.js +0 -7
- package/src/typechecker/stdlib/core.js +292 -103
- package/src/typechecker/stdlib/extended.js +471 -4
- package/src/typechecker/stdlib/shared.js +53 -0
- package/src/typechecker/stdlib.js +2 -0
- package/src/typechecker/types.js +545 -8
- package/src/vendor.js +2 -2
package/src/index.js
CHANGED
|
@@ -13,18 +13,14 @@
|
|
|
13
13
|
// gofront <file.go> --tokens — dump tokens of first file (debug)
|
|
14
14
|
// gofront init [dir] — scaffold a new GoFront project
|
|
15
15
|
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
const _require = createRequire(import.meta.url);
|
|
19
|
-
const { version } = _require("../package.json");
|
|
20
|
-
|
|
21
|
-
import { statSync, watch } from "node:fs";
|
|
16
|
+
import { statSync } from "node:fs";
|
|
22
17
|
import { dirname, relative, resolve } from "node:path";
|
|
23
18
|
import { copyAssets } from "./asset-manager.js";
|
|
24
19
|
import {
|
|
25
20
|
formatBuildSummary,
|
|
26
21
|
formatPrepSummary,
|
|
27
22
|
formatWrittenDesc,
|
|
23
|
+
GOFRONT_VERSION,
|
|
28
24
|
handleBuild,
|
|
29
25
|
handleCheck,
|
|
30
26
|
handleDev,
|
|
@@ -35,30 +31,30 @@ import {
|
|
|
35
31
|
parseBuildArgs,
|
|
36
32
|
parseCheckArgs,
|
|
37
33
|
parseDevArgs,
|
|
34
|
+
parseLegacyArgs,
|
|
38
35
|
parsePrepArgs,
|
|
39
36
|
parseTestArgs,
|
|
40
37
|
runCompile,
|
|
41
38
|
writeCompileOutput,
|
|
42
39
|
} from "./cli-core.js";
|
|
43
40
|
import { colors, formatDiagnostic, log, ms, stamp } from "./colors.js";
|
|
44
|
-
import { createDevServer } from "./dev-server.js";
|
|
45
41
|
|
|
46
42
|
// ── Parse CLI args ───────────────────────────────────────────
|
|
47
43
|
|
|
48
44
|
const args = process.argv.slice(2);
|
|
49
45
|
if (args[0] === "--version" || args[0] === "-v") {
|
|
50
|
-
console.log(`gofront ${
|
|
46
|
+
console.log(`gofront ${GOFRONT_VERSION}`);
|
|
51
47
|
process.exit(0);
|
|
52
48
|
}
|
|
53
49
|
|
|
54
50
|
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
55
51
|
console.log(
|
|
56
52
|
`
|
|
57
|
-
GoFront — a Go-inspired language that compiles to JavaScript
|
|
53
|
+
GoFront — a Go-inspired language that compiles to JavaScript and WebAssembly
|
|
58
54
|
|
|
59
55
|
Usage:
|
|
60
56
|
gofront dev [dir] [options] Start dev server with live reload (default port 3000)
|
|
61
|
-
gofront build [dir] [options] Build production bundle (-o <dir>, --pwa, --minify, --emit-wat)
|
|
57
|
+
gofront build [dir] [options] Build production bundle (-o <dir>, --pwa, --minify, --emit-wat, --release, --wasm-opt, --js-only)
|
|
62
58
|
gofront check [dir|dir/...] Type-check only (dir/... recurses into every package)
|
|
63
59
|
gofront test [dir|dir/...] [--dom] Run unit tests (-v verbose, -run <regex>)
|
|
64
60
|
gofront prep [dir] [--minify] Copy static assets and bundle vendor dependencies
|
|
@@ -66,6 +62,8 @@ Usage:
|
|
|
66
62
|
gofront <file.go> Compile single file and print to stdout
|
|
67
63
|
gofront <dir> (or gofront .) Compile all *.go in directory as one bundle
|
|
68
64
|
gofront <input> -o out.js Compile and write to file (wasm packages also emit app.wasm)
|
|
65
|
+
gofront <input> --release Production build with minification and Binaryen wasm optimization
|
|
66
|
+
gofront <input> --wasm-opt Optimize wasm output with Binaryen (-O3 + GUFA)
|
|
69
67
|
gofront <input> --check Type-check only
|
|
70
68
|
gofront <input> --watch Watch for changes and recompile
|
|
71
69
|
gofront <input> -o out.js --serve Watch + serve with live reload (default port 3000)
|
|
@@ -131,9 +129,9 @@ if (args[0] === "test") {
|
|
|
131
129
|
// ── check subcommand ──────────────────────────────────────────
|
|
132
130
|
|
|
133
131
|
if (args[0] === "check") {
|
|
134
|
-
const { targetDir } = parseCheckArgs(args.slice(1));
|
|
132
|
+
const { targetDir, forceTarget } = parseCheckArgs(args.slice(1));
|
|
135
133
|
try {
|
|
136
|
-
const { elapsedMs, packages } = handleCheck(targetDir);
|
|
134
|
+
const { elapsedMs, packages } = handleCheck(targetDir, { forceTarget });
|
|
137
135
|
const okLine = (label, t) =>
|
|
138
136
|
log.info(`${colors.cyan(label)} — ${colors.green("OK")} ${ms(t)}`);
|
|
139
137
|
if (packages) {
|
|
@@ -202,20 +200,22 @@ if (args[0] === "dev") {
|
|
|
202
200
|
}
|
|
203
201
|
}
|
|
204
202
|
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
203
|
+
const {
|
|
204
|
+
inputArg,
|
|
205
|
+
outputFile,
|
|
206
|
+
checkOnly,
|
|
207
|
+
dumpAst,
|
|
208
|
+
dumpTokens,
|
|
209
|
+
sourceMap,
|
|
210
|
+
serve: serveMode,
|
|
211
|
+
watch: watchMode,
|
|
212
|
+
copyAssets: copyAssetsFlag,
|
|
213
|
+
wasmOpt,
|
|
214
|
+
minify: minifyOutput,
|
|
215
|
+
mangle: mangleOutput,
|
|
216
|
+
port: servePort,
|
|
217
|
+
forceTarget,
|
|
218
|
+
} = parseLegacyArgs(args);
|
|
219
219
|
|
|
220
220
|
// ── Determine input mode ─────────────────────────────────────
|
|
221
221
|
|
|
@@ -228,6 +228,17 @@ try {
|
|
|
228
228
|
process.exit(1);
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
function copyProjectAssets(verbose) {
|
|
232
|
+
try {
|
|
233
|
+
const { copied, skipped } = copyAssets(resolve("."));
|
|
234
|
+
if (verbose && (copied > 0 || skipped > 0)) {
|
|
235
|
+
log.info(`copied ${copied} assets (${skipped} skipped)`);
|
|
236
|
+
}
|
|
237
|
+
} catch (e) {
|
|
238
|
+
log.warn(`asset copy failed: ${e.message}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
231
242
|
// ── Single-shot mode ─────────────────────────────────────────
|
|
232
243
|
|
|
233
244
|
if (!watchMode) {
|
|
@@ -239,6 +250,8 @@ if (!watchMode) {
|
|
|
239
250
|
outputFile,
|
|
240
251
|
dumpTokens,
|
|
241
252
|
dumpAst,
|
|
253
|
+
wasmOpt,
|
|
254
|
+
forceTarget,
|
|
242
255
|
});
|
|
243
256
|
} catch (e) {
|
|
244
257
|
log.fail(e.message);
|
|
@@ -282,6 +295,13 @@ if (!watchMode) {
|
|
|
282
295
|
log.info(
|
|
283
296
|
`wrote ${formatWrittenDesc(written, outputFile)} ${ms(elapsedMs)}`,
|
|
284
297
|
);
|
|
298
|
+
if (result.wasmOptInfo && result.wasmOptInfo.originalSize > 0) {
|
|
299
|
+
const origKb = (result.wasmOptInfo.originalSize / 1024).toFixed(1);
|
|
300
|
+
const optKb = (result.wasmOptInfo.optimizedSize / 1024).toFixed(1);
|
|
301
|
+
log.info(
|
|
302
|
+
`wasm-opt: app.wasm ${origKb} kB → ${optKb} kB (-${result.wasmOptInfo.percentSaved}%) [${result.wasmOptInfo.engine}]`,
|
|
303
|
+
);
|
|
304
|
+
}
|
|
285
305
|
} catch (e) {
|
|
286
306
|
log.fail(`cannot write '${outputFile}': ${e.message}`);
|
|
287
307
|
process.exit(1);
|
|
@@ -295,110 +315,57 @@ if (!watchMode) {
|
|
|
295
315
|
}
|
|
296
316
|
}
|
|
297
317
|
|
|
298
|
-
if (copyAssetsFlag)
|
|
299
|
-
try {
|
|
300
|
-
const projectDir = resolve(".");
|
|
301
|
-
const { copied, skipped } = copyAssets(projectDir);
|
|
302
|
-
if (copied > 0 || skipped > 0) {
|
|
303
|
-
log.info(`copied ${copied} assets (${skipped} skipped)`);
|
|
304
|
-
}
|
|
305
|
-
} catch (e) {
|
|
306
|
-
log.warn(`asset copy failed: ${e.message}`);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
318
|
+
if (copyAssetsFlag) copyProjectAssets(true);
|
|
309
319
|
|
|
310
320
|
process.exit(0);
|
|
311
321
|
}
|
|
312
322
|
|
|
313
323
|
// ── Watch mode ───────────────────────────────────────────────
|
|
314
324
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if (!outputFile) {
|
|
319
|
-
log.fail("--serve requires -o <output file>");
|
|
320
|
-
process.exit(1);
|
|
321
|
-
}
|
|
322
|
-
const serveDir = dirname(resolve(outputFile));
|
|
323
|
-
devServer = createDevServer(serveDir, servePort);
|
|
325
|
+
if (serveMode && !outputFile) {
|
|
326
|
+
log.fail("--serve requires -o <output file>");
|
|
327
|
+
process.exit(1);
|
|
324
328
|
}
|
|
325
329
|
|
|
326
|
-
|
|
327
|
-
try {
|
|
328
|
-
const startMs = performance.now();
|
|
329
|
-
const result = runCompile(inputPath, isDir, { sourceMap, outputFile });
|
|
330
|
-
const js = maybeMinify(result.js, {
|
|
331
|
-
minify: minifyOutput,
|
|
332
|
-
mangle: mangleOutput,
|
|
333
|
-
sourceMap,
|
|
334
|
-
});
|
|
335
|
-
const elapsedMs = (performance.now() - startMs).toFixed(0);
|
|
336
|
-
const changeNote = changedFile ? ` — ${changedFile} changed` : "";
|
|
337
|
-
const timing = colors.dim(`(${elapsedMs}ms${changeNote})`);
|
|
338
|
-
if (outputFile) {
|
|
339
|
-
const written = writeCompileOutput(resolve(outputFile), result, js);
|
|
340
|
-
console.error(
|
|
341
|
-
`${stamp()} ${colors.bold("gofront:")} ${colors.green("OK")} — wrote ${formatWrittenDesc(written, outputFile)} ${timing}`,
|
|
342
|
-
);
|
|
343
|
-
} else {
|
|
344
|
-
// Clear screen then print
|
|
345
|
-
process.stdout.write("\x1Bc");
|
|
346
|
-
console.log(js);
|
|
347
|
-
console.error(
|
|
348
|
-
`${stamp()} ${colors.bold("gofront:")} ${colors.green("OK")} ${timing}`,
|
|
349
|
-
);
|
|
350
|
-
}
|
|
330
|
+
const prefix = () => `${stamp()} ${colors.bold("gofront:")}`;
|
|
351
331
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
332
|
+
try {
|
|
333
|
+
await handleDev(".", {
|
|
334
|
+
srcDir: inputPath,
|
|
335
|
+
outputFile,
|
|
336
|
+
serve: serveMode,
|
|
337
|
+
serveDir: outputFile ? dirname(resolve(outputFile)) : undefined,
|
|
338
|
+
port: servePort,
|
|
339
|
+
silent: false,
|
|
340
|
+
sourceMap,
|
|
341
|
+
minify: minifyOutput,
|
|
342
|
+
mangle: mangleOutput,
|
|
343
|
+
copyAssets: copyAssetsFlag,
|
|
344
|
+
forceTarget,
|
|
345
|
+
onBuild: ({ js, written, elapsedMs, changedFile }) => {
|
|
346
|
+
const changeNote = changedFile ? ` — ${changedFile} changed` : "";
|
|
347
|
+
const timing = colors.dim(`(${elapsedMs}ms${changeNote})`);
|
|
348
|
+
if (outputFile) {
|
|
349
|
+
console.error(
|
|
350
|
+
`${prefix()} ${colors.green("OK")} — wrote ${formatWrittenDesc(written, outputFile)} ${timing}`,
|
|
351
|
+
);
|
|
352
|
+
} else {
|
|
353
|
+
// Clear screen then print
|
|
354
|
+
process.stdout.write("\x1Bc");
|
|
355
|
+
console.log(js);
|
|
356
|
+
console.error(`${prefix()} ${colors.green("OK")} ${timing}`);
|
|
357
357
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// Initial build
|
|
372
|
-
buildOnce();
|
|
373
|
-
|
|
374
|
-
// Determine what to watch
|
|
375
|
-
const watchTarget = isDir ? inputPath : dirname(inputPath);
|
|
376
|
-
|
|
377
|
-
function handleCssWatch(filename) {
|
|
378
|
-
if (copyAssetsFlag) {
|
|
379
|
-
try {
|
|
380
|
-
copyAssets(resolve("."));
|
|
381
|
-
} catch (e) {
|
|
382
|
-
log.warn(`asset copy failed: ${e.message}`);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
devServer?.notifyCss?.(filename);
|
|
358
|
+
if (copyAssetsFlag) copyProjectAssets(false);
|
|
359
|
+
},
|
|
360
|
+
onError: (e) => {
|
|
361
|
+
console.error(`${prefix()} ${colors.bold(colors.red("ERROR"))}`);
|
|
362
|
+
for (const line of formatDiagnostic(e.message).split("\n"))
|
|
363
|
+
console.error(` ${line}`);
|
|
364
|
+
},
|
|
365
|
+
});
|
|
366
|
+
} catch (e) {
|
|
367
|
+
log.fail(e.message);
|
|
368
|
+
process.exit(1);
|
|
386
369
|
}
|
|
387
370
|
|
|
388
|
-
|
|
389
|
-
let cssDebounce = null;
|
|
390
|
-
watch(watchTarget, { recursive: true }, (_event, filename) => {
|
|
391
|
-
if (!filename) return;
|
|
392
|
-
if (filename.endsWith(".css")) {
|
|
393
|
-
clearTimeout(cssDebounce);
|
|
394
|
-
cssDebounce = setTimeout(() => handleCssWatch(filename), 50);
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
if (!filename.endsWith(".go") && !filename.endsWith(".templ")) return;
|
|
398
|
-
clearTimeout(debounce);
|
|
399
|
-
debounce = setTimeout(() => buildOnce(filename), 80);
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
console.error(
|
|
403
|
-
`${stamp()} ${colors.bold("gofront:")} watching ${colors.cyan(inputArg)} ...`,
|
|
404
|
-
);
|
|
371
|
+
console.error(`${prefix()} watching ${colors.cyan(inputArg)} ...`);
|
package/src/lexer.js
CHANGED
|
@@ -203,11 +203,25 @@ export class Lexer {
|
|
|
203
203
|
const l = this.line,
|
|
204
204
|
c = this.col;
|
|
205
205
|
this.advance(); // opening "
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
206
|
+
let start = this.pos;
|
|
207
|
+
let parts = null;
|
|
208
|
+
while (this.pos < this.src.length) {
|
|
209
|
+
const ch = this.src[this.pos];
|
|
210
|
+
if (ch === '"') {
|
|
211
|
+
if (parts === null) {
|
|
212
|
+
const str = this.src.slice(start, this.pos);
|
|
213
|
+
this.advance(); // closing "
|
|
214
|
+
return str;
|
|
215
|
+
}
|
|
216
|
+
parts.push(this.src.slice(start, this.pos));
|
|
217
|
+
this.advance(); // closing "
|
|
218
|
+
return parts.join("");
|
|
219
|
+
}
|
|
220
|
+
if (ch === "\n") this.err("Unterminated string literal");
|
|
221
|
+
if (ch === "\\") {
|
|
222
|
+
if (parts === null) parts = [];
|
|
223
|
+
if (this.pos > start) parts.push(this.src.slice(start, this.pos));
|
|
224
|
+
this.advance(); // skip \
|
|
211
225
|
const esc = this.advance();
|
|
212
226
|
switch (esc) {
|
|
213
227
|
case "n":
|
|
@@ -231,14 +245,12 @@ export class Lexer {
|
|
|
231
245
|
default:
|
|
232
246
|
parts.push(`\\${esc}`);
|
|
233
247
|
}
|
|
248
|
+
start = this.pos;
|
|
234
249
|
} else {
|
|
235
|
-
|
|
250
|
+
this.advance();
|
|
236
251
|
}
|
|
237
252
|
}
|
|
238
|
-
|
|
239
|
-
throw new LexError("Unterminated string", l, c, this.filename, this.src);
|
|
240
|
-
this.advance(); // closing "
|
|
241
|
-
return parts.join("");
|
|
253
|
+
throw new LexError("Unterminated string", l, c, this.filename, this.src);
|
|
242
254
|
}
|
|
243
255
|
|
|
244
256
|
readRawString() {
|
|
@@ -383,39 +395,78 @@ export class Lexer {
|
|
|
383
395
|
_readDecimalExponent(n) {
|
|
384
396
|
n += this.advance(); // consume e/E
|
|
385
397
|
if (this.peek() === "+" || this.peek() === "-") n += this.advance();
|
|
386
|
-
while (this.pos < this.src.length
|
|
387
|
-
const
|
|
388
|
-
if (
|
|
398
|
+
while (this.pos < this.src.length) {
|
|
399
|
+
const c = this.src.charCodeAt(this.pos);
|
|
400
|
+
if (c >= 48 && c <= 57) {
|
|
401
|
+
n += this.advance();
|
|
402
|
+
} else if (c === 95) {
|
|
403
|
+
this.advance();
|
|
404
|
+
} else {
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
389
407
|
}
|
|
390
408
|
return n;
|
|
391
409
|
}
|
|
392
410
|
|
|
393
411
|
_readDecimalNumber(n) {
|
|
394
412
|
let isFloat = false;
|
|
395
|
-
while (this.pos < this.src.length
|
|
396
|
-
const
|
|
397
|
-
if (
|
|
413
|
+
while (this.pos < this.src.length) {
|
|
414
|
+
const c = this.src.charCodeAt(this.pos);
|
|
415
|
+
if (c >= 48 && c <= 57) {
|
|
416
|
+
n += this.advance();
|
|
417
|
+
} else if (c === 95) {
|
|
418
|
+
this.advance(); // skip _
|
|
419
|
+
} else {
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
398
422
|
}
|
|
399
|
-
if (
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
423
|
+
if (
|
|
424
|
+
this.pos + 1 < this.src.length &&
|
|
425
|
+
this.src.charCodeAt(this.pos) === 46
|
|
426
|
+
) {
|
|
427
|
+
const next = this.src.charCodeAt(this.pos + 1);
|
|
428
|
+
if (next >= 48 && next <= 57) {
|
|
429
|
+
isFloat = true;
|
|
430
|
+
n += this.advance();
|
|
431
|
+
while (this.pos < this.src.length) {
|
|
432
|
+
const c = this.src.charCodeAt(this.pos);
|
|
433
|
+
if (c >= 48 && c <= 57) {
|
|
434
|
+
n += this.advance();
|
|
435
|
+
} else if (c === 95) {
|
|
436
|
+
this.advance(); // skip _
|
|
437
|
+
} else {
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
405
441
|
}
|
|
406
442
|
}
|
|
407
|
-
if (this.
|
|
408
|
-
|
|
409
|
-
|
|
443
|
+
if (this.pos < this.src.length) {
|
|
444
|
+
const c = this.src.charCodeAt(this.pos);
|
|
445
|
+
if (c === 101 || c === 69) {
|
|
446
|
+
isFloat = true;
|
|
447
|
+
n = this._readDecimalExponent(n);
|
|
448
|
+
}
|
|
410
449
|
}
|
|
411
450
|
return { n, isFloat };
|
|
412
451
|
}
|
|
413
452
|
|
|
414
453
|
readIdent() {
|
|
415
|
-
|
|
416
|
-
while (this.pos < this.src.length
|
|
417
|
-
|
|
418
|
-
|
|
454
|
+
const start = this.pos;
|
|
455
|
+
while (this.pos < this.src.length) {
|
|
456
|
+
const code = this.src.charCodeAt(this.pos);
|
|
457
|
+
if (
|
|
458
|
+
(code >= 97 && code <= 122) ||
|
|
459
|
+
(code >= 65 && code <= 90) ||
|
|
460
|
+
(code >= 48 && code <= 57) ||
|
|
461
|
+
code === 95
|
|
462
|
+
) {
|
|
463
|
+
this.pos++;
|
|
464
|
+
} else {
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
this.col += this.pos - start;
|
|
469
|
+
return this.src.slice(start, this.pos);
|
|
419
470
|
}
|
|
420
471
|
|
|
421
472
|
// ── Semicolon insertion ──────────────────────────────────────
|
package/src/lower/boxing.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// src/lower/boxing.js
|
|
2
2
|
// Identifies address-taken scalar variables that require heap boxing ({ value: x }).
|
|
3
3
|
|
|
4
|
+
import { forEachChild } from "./walk.js";
|
|
5
|
+
|
|
4
6
|
export function isReferenceType(t) {
|
|
5
7
|
if (!t) return false;
|
|
6
8
|
const base = t.kind === "named" ? t.underlying : t;
|
|
@@ -8,8 +10,7 @@ export function isReferenceType(t) {
|
|
|
8
10
|
base?.kind === "struct" ||
|
|
9
11
|
base?.kind === "slice" ||
|
|
10
12
|
base?.kind === "map" ||
|
|
11
|
-
base?.kind === "func"
|
|
12
|
-
base?.kind === "interface"
|
|
13
|
+
base?.kind === "func"
|
|
13
14
|
);
|
|
14
15
|
}
|
|
15
16
|
|
|
@@ -28,9 +29,6 @@ export function scanAddressTaken(node, boxedVars = new Set()) {
|
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
// Recurse into FuncLit too — closures may take address of outer vars
|
|
31
|
-
|
|
32
|
-
if (key.startsWith("_")) continue;
|
|
33
|
-
scanAddressTaken(node[key], boxedVars);
|
|
34
|
-
}
|
|
32
|
+
forEachChild(node, (child) => scanAddressTaken(child, boxedVars));
|
|
35
33
|
return boxedVars;
|
|
36
34
|
}
|
package/src/lower/captures.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
// Identifies free variables captured by closures and determines which ones
|
|
3
3
|
// are mutated and thus require heap-allocated environment cells for WASM closures.
|
|
4
4
|
|
|
5
|
-
import { collectMutatedVars
|
|
5
|
+
import { collectMutatedVars } from "./ownership.js";
|
|
6
|
+
import { forEachChild } from "./walk.js";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Result of capture analysis on a function or closure.
|
|
@@ -41,17 +42,14 @@ export function collectDeclaredNames(node, names = new Set()) {
|
|
|
41
42
|
// Do not descend into nested functions when collecting local declarations
|
|
42
43
|
if (node.kind === "FuncLit") return names;
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
if (key.startsWith("_")) continue;
|
|
46
|
-
collectDeclaredNames(node[key], names);
|
|
47
|
-
}
|
|
45
|
+
forEachChild(node, (child) => collectDeclaredNames(child, names));
|
|
48
46
|
return names;
|
|
49
47
|
}
|
|
50
48
|
|
|
51
49
|
/**
|
|
52
50
|
* Finds all identifier references in a node that are not shadowed by inner declarations.
|
|
53
51
|
*/
|
|
54
|
-
|
|
52
|
+
function collectReferencedNames(node, localDecls, refs = new Set()) {
|
|
55
53
|
if (!node || typeof node !== "object") return refs;
|
|
56
54
|
if (Array.isArray(node)) {
|
|
57
55
|
for (const item of node) collectReferencedNames(item, localDecls, refs);
|
|
@@ -73,10 +71,9 @@ export function collectReferencedNames(node, localDecls, refs = new Set()) {
|
|
|
73
71
|
if (r.name && r.name !== "_") innerLocals.add(r.name);
|
|
74
72
|
}
|
|
75
73
|
collectDeclaredNames(node.body, innerLocals);
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
74
|
+
forEachChild(node, (child) =>
|
|
75
|
+
collectReferencedNames(child, innerLocals, refs),
|
|
76
|
+
);
|
|
80
77
|
return refs;
|
|
81
78
|
}
|
|
82
79
|
|
|
@@ -89,10 +86,9 @@ export function collectReferencedNames(node, localDecls, refs = new Set()) {
|
|
|
89
86
|
}
|
|
90
87
|
|
|
91
88
|
// For nested closures, free variables are also referenced
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
89
|
+
forEachChild(node, (child) =>
|
|
90
|
+
collectReferencedNames(child, localDecls, refs),
|
|
91
|
+
);
|
|
96
92
|
return refs;
|
|
97
93
|
}
|
|
98
94
|
|
|
@@ -155,11 +151,7 @@ export function analyzeCaptures(fnDecl) {
|
|
|
155
151
|
result.capturesByClosure.set(node, captured);
|
|
156
152
|
|
|
157
153
|
for (const varName of captured) {
|
|
158
|
-
if (
|
|
159
|
-
mutatedVars.has(varName) ||
|
|
160
|
-
nodeMutatesVar(fnDecl.body, varName) ||
|
|
161
|
-
nodeMutatesVar(node.body, varName)
|
|
162
|
-
) {
|
|
154
|
+
if (mutatedVars.has(varName)) {
|
|
163
155
|
result.mutatedCaptures.add(varName);
|
|
164
156
|
}
|
|
165
157
|
}
|
|
@@ -168,10 +160,7 @@ export function analyzeCaptures(fnDecl) {
|
|
|
168
160
|
return;
|
|
169
161
|
}
|
|
170
162
|
|
|
171
|
-
|
|
172
|
-
if (key.startsWith("_")) continue;
|
|
173
|
-
walkClosures(node[key], scopeStack);
|
|
174
|
-
}
|
|
163
|
+
forEachChild(node, (child) => walkClosures(child, scopeStack));
|
|
175
164
|
};
|
|
176
165
|
|
|
177
166
|
walkClosures(fnDecl.body, [rootScope]);
|
package/src/lower/escape.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { collectDeclaredNames } from "./captures.js";
|
|
8
8
|
import { rootIdentName } from "./ownership.js";
|
|
9
|
+
import { forEachChild } from "./walk.js";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Checks whether an expression evaluates to one of the target pointer variables
|
|
@@ -15,7 +16,7 @@ import { rootIdentName } from "./ownership.js";
|
|
|
15
16
|
* @param {Set<string>} targetNames Set of identifier names being tracked
|
|
16
17
|
* @returns {boolean}
|
|
17
18
|
*/
|
|
18
|
-
|
|
19
|
+
function evaluatesToPointer(expr, targetNames) {
|
|
19
20
|
if (!expr || typeof expr !== "object") return false;
|
|
20
21
|
if (expr.kind === "Ident") {
|
|
21
22
|
return targetNames.has(expr.name);
|
|
@@ -144,10 +145,7 @@ export function paramEscapes(body, paramName) {
|
|
|
144
145
|
captured = true;
|
|
145
146
|
return;
|
|
146
147
|
}
|
|
147
|
-
|
|
148
|
-
if (k.startsWith("_")) continue;
|
|
149
|
-
checkClosure(cNode[k]);
|
|
150
|
-
}
|
|
148
|
+
forEachChild(cNode, checkClosure);
|
|
151
149
|
};
|
|
152
150
|
checkClosure(node.body);
|
|
153
151
|
if (captured) {
|
|
@@ -158,10 +156,7 @@ export function paramEscapes(body, paramName) {
|
|
|
158
156
|
return;
|
|
159
157
|
}
|
|
160
158
|
|
|
161
|
-
|
|
162
|
-
if (key.startsWith("_")) continue;
|
|
163
|
-
walk(node[key]);
|
|
164
|
-
}
|
|
159
|
+
forEachChild(node, walk);
|
|
165
160
|
};
|
|
166
161
|
|
|
167
162
|
walk(body);
|