gofront 1.7.0 → 1.8.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.
Files changed (128) hide show
  1. package/README.md +13 -13
  2. package/package.json +8 -37
  3. package/src/backend/js/expressions.js +1341 -148
  4. package/src/backend/js/fmt-runtime.js +573 -0
  5. package/src/backend/js/index.js +375 -57
  6. package/src/backend/js/runtime.js +1460 -133
  7. package/src/backend/js/statements.js +900 -86
  8. package/src/backend/js/stdlib/bits.js +87 -14
  9. package/src/backend/js/stdlib/builder.js +41 -6
  10. package/src/backend/js/stdlib/bytes.js +253 -7
  11. package/src/backend/js/stdlib/errors.js +2 -1
  12. package/src/backend/js/stdlib/fmt.js +349 -10
  13. package/src/backend/js/stdlib/index.js +8 -3
  14. package/src/backend/js/stdlib/json.js +247 -52
  15. package/src/backend/js/stdlib/maps.js +0 -8
  16. package/src/backend/js/stdlib/math.js +17 -1
  17. package/src/backend/js/stdlib/simd.js +10 -0
  18. package/src/backend/js/stdlib/slices.js +36 -6
  19. package/src/backend/js/stdlib/strconv.js +10 -5
  20. package/src/backend/js/stdlib/strings.js +83 -4
  21. package/src/backend/js/stdlib/testing.js +7 -0
  22. package/src/backend/js/stdlib/time.js +79 -55
  23. package/src/backend/js/stdlib/unicode.js +1 -1
  24. package/src/backend/js/stdlib/utf8.js +1 -1
  25. package/src/backend/js/templ.js +38 -76
  26. package/src/backend/js/type-info.js +272 -0
  27. package/src/backend/shared/fmt-types.js +188 -0
  28. package/src/backend/wasm/boundary.js +436 -80
  29. package/src/backend/wasm/emit-builtins.js +303 -13
  30. package/src/backend/wasm/emit-exprs.js +1397 -203
  31. package/src/backend/wasm/emit-maps.js +61 -13
  32. package/src/backend/wasm/emit-stmts.js +1359 -164
  33. package/src/backend/wasm/emit.js +512 -77
  34. package/src/backend/wasm/encode.js +43 -13
  35. package/src/backend/wasm/globals.js +452 -0
  36. package/src/backend/wasm/index.js +194 -2489
  37. package/src/backend/wasm/method-expr.js +381 -0
  38. package/src/backend/wasm/module-emitter.js +2240 -0
  39. package/src/backend/wasm/monomorph.js +62 -4
  40. package/src/backend/wasm/prescan.js +397 -0
  41. package/src/backend/wasm/stdlib/bytes.js +1804 -127
  42. package/src/backend/wasm/stdlib/core.js +1574 -199
  43. package/src/backend/wasm/stdlib/index.js +0 -5
  44. package/src/backend/wasm/stdlib/io.js +1567 -150
  45. package/src/backend/wasm/stdlib/packages.js +2 -0
  46. package/src/backend/wasm/stdlib/simd.js +293 -28
  47. package/src/backend/wasm/stdlib/time.js +1041 -12
  48. package/src/backend/wasm/types.js +200 -15
  49. package/src/backend/wasm/variadic.js +93 -0
  50. package/src/backend/wasm/wat.js +47 -20
  51. package/src/{cli-core.js → cli/cli-core.js} +236 -10
  52. package/src/{test-runner.js → cli/test-runner.js} +332 -39
  53. package/src/{vendor.js → cli/vendor.js} +3 -4
  54. package/src/compiler.js +107 -269
  55. package/src/index.js +15 -169
  56. package/src/{lexer.js → lexer/lexer.js} +102 -55
  57. package/src/{tokens.js → lexer/tokens.js} +1 -0
  58. package/src/link/sourcemap.js +97 -0
  59. package/src/link/stdlib.js +515 -0
  60. package/src/link/wasm.js +294 -0
  61. package/src/lower/bounds.js +394 -0
  62. package/src/lower/boxing.js +1 -1
  63. package/src/lower/captures.js +176 -32
  64. package/src/lower/index.js +26 -18
  65. package/src/lower/json-codecs.js +1398 -0
  66. package/src/lower/ownership.js +22 -1
  67. package/src/lower/range.js +86 -1
  68. package/src/lower/reachability.js +132 -0
  69. package/src/parser/declarations.js +58 -13
  70. package/src/parser/expressions.js +29 -7
  71. package/src/parser/index.js +2 -1
  72. package/src/parser/statements.js +37 -22
  73. package/src/{templ-parser.js → parser/templ.js} +138 -15
  74. package/src/parser/types.js +69 -9
  75. package/src/resolver.js +22 -1
  76. package/src/typechecker/assignability.js +176 -17
  77. package/src/typechecker/const-fold.js +356 -0
  78. package/src/typechecker/constants.js +256 -0
  79. package/src/typechecker/expressions.js +931 -122
  80. package/src/typechecker/index.js +459 -67
  81. package/src/typechecker/init-order.js +330 -0
  82. package/src/typechecker/resolve.js +451 -56
  83. package/src/typechecker/statements.js +90 -9
  84. package/src/typechecker/stdlib/core.js +333 -55
  85. package/src/typechecker/stdlib/excluded.js +898 -0
  86. package/src/typechecker/stdlib/extended.js +322 -26
  87. package/src/typechecker/{stdlib.js → stdlib/index.js} +5 -5
  88. package/src/typechecker/stdlib/simd.js +65 -15
  89. package/src/typechecker/termination.js +1 -1
  90. package/src/typechecker/types.js +149 -25
  91. package/stdlib/bytes/iter.go +192 -0
  92. package/stdlib/bytes/search.go +240 -0
  93. package/stdlib/bytes/split.go +218 -0
  94. package/stdlib/cmp/cmp.go +65 -0
  95. package/stdlib/encoding/json/errors.go +31 -0
  96. package/stdlib/encoding/json/indent.go +71 -0
  97. package/stdlib/encoding/json/number.go +12 -0
  98. package/stdlib/encoding/json/stream.go +173 -0
  99. package/stdlib/errors/overlay.go +12 -0
  100. package/stdlib/hash/hash.go +53 -0
  101. package/stdlib/internal/jsonrt/dec.go +738 -0
  102. package/stdlib/internal/jsonrt/enc.go +366 -0
  103. package/stdlib/internal/jsonrt/indent.go +135 -0
  104. package/stdlib/internal/jsonrt/number.go +27 -0
  105. package/stdlib/internal/jsonrt/reader.go +73 -0
  106. package/stdlib/internal/jsonrt/scanner.go +649 -0
  107. package/stdlib/io/io.go +346 -0
  108. package/stdlib/iter/iter.go +18 -0
  109. package/stdlib/maps/iter.go +65 -0
  110. package/stdlib/math/extra.go +270 -0
  111. package/stdlib/slices/extra.go +73 -0
  112. package/stdlib/slices/iter.go +113 -0
  113. package/stdlib/sort/sort.go +561 -0
  114. package/stdlib/strconv/quote.go +341 -0
  115. package/stdlib/strings/extra.go +35 -0
  116. package/stdlib/strings/iter.go +122 -0
  117. package/stdlib/sync/extra.go +105 -0
  118. package/stdlib/unicode/unicode.go +112 -0
  119. package/stdlib/unicode/utf8/bytes.go +239 -0
  120. package/src/backend/wasm/const-fold.js +0 -205
  121. package/src/backend/wasm/emit-stdlib.js +0 -13
  122. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  123. /package/src/{colors.js → cli/colors.js} +0 -0
  124. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  125. /package/src/{minifier.js → cli/minifier.js} +0 -0
  126. /package/src/{project-config.js → cli/project-config.js} +0 -0
  127. /package/src/{pwa.js → cli/pwa.js} +0 -0
  128. /package/src/{templ-lexer.js → lexer/templ-lexer.js} +0 -0
package/src/index.js CHANGED
@@ -14,30 +14,30 @@
14
14
  // gofront init [dir] — scaffold a new GoFront project
15
15
 
16
16
  import { statSync } from "node:fs";
17
- import { dirname, relative, resolve } from "node:path";
18
- import { copyAssets } from "./asset-manager.js";
17
+ import { dirname, resolve } from "node:path";
19
18
  import {
20
- formatBuildSummary,
19
+ devReporters,
21
20
  formatPrepSummary,
22
21
  formatWrittenDesc,
23
22
  GOFRONT_VERSION,
23
+ HELP_TEXT,
24
24
  handleBuild,
25
25
  handleCheck,
26
26
  handleDev,
27
27
  handleInit,
28
28
  handlePrep,
29
29
  handleTest,
30
- maybeMinify,
31
30
  parseBuildArgs,
32
31
  parseCheckArgs,
33
32
  parseDevArgs,
34
33
  parseLegacyArgs,
35
34
  parsePrepArgs,
36
35
  parseTestArgs,
37
- runCompile,
38
- writeCompileOutput,
39
- } from "./cli-core.js";
40
- import { colors, formatDiagnostic, log, ms, stamp } from "./colors.js";
36
+ reportBuild,
37
+ reportCheck,
38
+ runSingleShot,
39
+ } from "./cli/cli-core.js";
40
+ import { colors, log, stamp } from "./cli/colors.js";
41
41
 
42
42
  // ── Parse CLI args ───────────────────────────────────────────
43
43
 
@@ -48,35 +48,7 @@ if (args[0] === "--version" || args[0] === "-v") {
48
48
  }
49
49
 
50
50
  if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
51
- console.log(
52
- `
53
- GoFront — a Go-inspired language that compiles to JavaScript and WebAssembly
54
-
55
- Usage:
56
- gofront dev [dir] [options] Start dev server with live reload (default port 3000)
57
- gofront build [dir] [options] Build production bundle (-o <dir>, --pwa, --minify, --emit-wat, --release, --wasm-opt, --js-only)
58
- gofront check [dir|dir/...] Type-check only (dir/... recurses into every package)
59
- gofront test [dir|dir/...] [--dom] Run unit tests (-v verbose, -run <regex>)
60
- gofront prep [dir] [--minify] Copy static assets and bundle vendor dependencies
61
- gofront init [dir] Scaffold a new GoFront project
62
- gofront <file.go> Compile single file and print to stdout
63
- gofront <dir> (or gofront .) Compile all *.go in directory as one bundle
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)
67
- gofront <input> --check Type-check only
68
- gofront <input> --watch Watch for changes and recompile
69
- gofront <input> -o out.js --serve Watch + serve with live reload (default port 3000)
70
- gofront <input> -o out.js --serve --port 8080 Use a custom port
71
- gofront <input> -o out.js --copy-assets Compile and synchronize static assets
72
- gofront <input> --source-map Append inline source map to output
73
- gofront <input> --minify Minify output
74
- gofront <input> --minify --mangle Minify and mangle identifiers
75
- gofront <file.go> --ast Dump AST (debug)
76
- gofront <file.go> --tokens Dump tokens (debug)
77
- gofront --version Print version
78
- `.trim(),
79
- );
51
+ console.log(HELP_TEXT);
80
52
  process.exit(0);
81
53
  }
82
54
 
@@ -131,19 +103,7 @@ if (args[0] === "test") {
131
103
  if (args[0] === "check") {
132
104
  const { targetDir, forceTarget } = parseCheckArgs(args.slice(1));
133
105
  try {
134
- const { elapsedMs, packages } = handleCheck(targetDir, { forceTarget });
135
- const okLine = (label, t) =>
136
- log.info(`${colors.cyan(label)} — ${colors.green("OK")} ${ms(t)}`);
137
- if (packages) {
138
- for (const pkg of packages) {
139
- okLine(relative(process.cwd(), pkg.dir) || ".", pkg.elapsedMs);
140
- }
141
- log.info(
142
- `${colors.bold(`${packages.length} packages`)} — ${colors.bold(colors.green("OK"))} ${ms(elapsedMs)}`,
143
- );
144
- } else {
145
- okLine(targetDir, elapsedMs);
146
- }
106
+ reportCheck(handleCheck(targetDir, { forceTarget }), targetDir);
147
107
  process.exit(0);
148
108
  } catch (e) {
149
109
  log.fail(e.message);
@@ -157,13 +117,7 @@ if (args[0] === "build") {
157
117
  const buildOptions = parseBuildArgs(args.slice(1));
158
118
  try {
159
119
  const result = await handleBuild(buildOptions.targetDir, buildOptions);
160
- for (const line of formatBuildSummary(result)) {
161
- log.info(
162
- line
163
- .replace(/^build complete/, colors.green("build complete"))
164
- .replace(/\(([\d.]+ kB)\)$/, (_, s) => colors.dim(`(${s})`)),
165
- );
166
- }
120
+ reportBuild(result);
167
121
  process.exit(0);
168
122
  } catch (e) {
169
123
  log.fail(e.message);
@@ -200,22 +154,19 @@ if (args[0] === "dev") {
200
154
  }
201
155
  }
202
156
 
157
+ const flags = parseLegacyArgs(args);
203
158
  const {
204
159
  inputArg,
205
160
  outputFile,
206
- checkOnly,
207
- dumpAst,
208
- dumpTokens,
209
161
  sourceMap,
210
162
  serve: serveMode,
211
163
  watch: watchMode,
212
164
  copyAssets: copyAssetsFlag,
213
- wasmOpt,
214
165
  minify: minifyOutput,
215
166
  mangle: mangleOutput,
216
167
  port: servePort,
217
168
  forceTarget,
218
- } = parseLegacyArgs(args);
169
+ } = flags;
219
170
 
220
171
  // ── Determine input mode ─────────────────────────────────────
221
172
 
@@ -228,96 +179,10 @@ try {
228
179
  process.exit(1);
229
180
  }
230
181
 
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
-
242
182
  // ── Single-shot mode ─────────────────────────────────────────
243
183
 
244
184
  if (!watchMode) {
245
- let result;
246
- const startMs = performance.now();
247
- try {
248
- result = runCompile(inputPath, isDir, {
249
- sourceMap,
250
- outputFile,
251
- dumpTokens,
252
- dumpAst,
253
- wasmOpt,
254
- forceTarget,
255
- });
256
- } catch (e) {
257
- log.fail(e.message);
258
- process.exit(1);
259
- }
260
-
261
- if (result.tokens) {
262
- for (const tok of result.tokens) console.log(tok.toString());
263
- process.exit(0);
264
- }
265
- if (result.ast) {
266
- console.log(JSON.stringify(result.ast, null, 2));
267
- process.exit(0);
268
- }
269
-
270
- if (checkOnly) {
271
- const elapsedMs = (performance.now() - startMs).toFixed(0);
272
- log.info(
273
- `${colors.cyan(inputArg)} — ${colors.green("OK")} ${ms(elapsedMs)}`,
274
- );
275
- process.exit(0);
276
- }
277
-
278
- let js;
279
- try {
280
- js = maybeMinify(result.js, {
281
- minify: minifyOutput,
282
- mangle: mangleOutput,
283
- sourceMap,
284
- });
285
- } catch (e) {
286
- log.fail(`minify failed: ${e.message}`);
287
- process.exit(1);
288
- }
289
-
290
- const elapsedMs = (performance.now() - startMs).toFixed(0);
291
-
292
- if (outputFile) {
293
- try {
294
- const written = writeCompileOutput(resolve(outputFile), result, js);
295
- log.info(
296
- `wrote ${formatWrittenDesc(written, outputFile)} ${ms(elapsedMs)}`,
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
- }
305
- } catch (e) {
306
- log.fail(`cannot write '${outputFile}': ${e.message}`);
307
- process.exit(1);
308
- }
309
- } else {
310
- console.log(js);
311
- if (result.wasm) {
312
- log.warn(
313
- "wasm packages were linked but app.wasm needs -o <file> to be written",
314
- );
315
- }
316
- }
317
-
318
- if (copyAssetsFlag) copyProjectAssets(true);
319
-
320
- process.exit(0);
185
+ process.exit(runSingleShot(inputPath, isDir, flags));
321
186
  }
322
187
 
323
188
  // ── Watch mode ───────────────────────────────────────────────
@@ -342,26 +207,7 @@ try {
342
207
  mangle: mangleOutput,
343
208
  copyAssets: copyAssetsFlag,
344
209
  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
- }
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
- },
210
+ ...devReporters({ outputFile, copyAssets: copyAssetsFlag }),
365
211
  });
366
212
  } catch (e) {
367
213
  log.fail(e.message);
@@ -52,8 +52,8 @@ const KEYWORDS = new Set([
52
52
  "case",
53
53
  "default",
54
54
  "map",
55
- "new",
56
55
  "fallthrough",
56
+ "goto",
57
57
  "defer",
58
58
  "async",
59
59
  "await",
@@ -86,6 +86,11 @@ const ASSIGN_OP = {
86
86
  ":": [T.DEFINE, ":=", T.COLON, ":"],
87
87
  };
88
88
 
89
+ const UNICODE_LETTER = /^\p{L}$/u;
90
+ const UNICODE_DIGIT = /^\p{Nd}$/u;
91
+ const isUnicodeLetter = (cp) => UNICODE_LETTER.test(String.fromCodePoint(cp));
92
+ const isUnicodeDigit = (cp) => UNICODE_DIGIT.test(String.fromCodePoint(cp));
93
+
89
94
  export class LexError extends Error {
90
95
  constructor(msg, line, col, filename, sourceCode) {
91
96
  const loc = filename ? `${filename}:${line}:${col}` : `${line}:${col}`;
@@ -103,6 +108,17 @@ export class LexError extends Error {
103
108
  }
104
109
  }
105
110
 
111
+ const SIMPLE_ESCAPES = {
112
+ a: 7,
113
+ b: 8,
114
+ f: 12,
115
+ n: 10,
116
+ r: 13,
117
+ t: 9,
118
+ v: 11,
119
+ "\\": 92,
120
+ };
121
+
106
122
  export class Lexer {
107
123
  constructor(source, filename = null) {
108
124
  this.src = source;
@@ -113,6 +129,8 @@ export class Lexer {
113
129
  this.filename = filename;
114
130
  this.target = null;
115
131
  this.targetDirective = null;
132
+ this.intrinsics = new Map(); // line -> name of a //gofront:intrinsic
133
+ this.embedDirectives = []; // {line, col} of each //go:embed line comment
116
134
  }
117
135
 
118
136
  // ── Primitives ──────────────────────────────────────────────
@@ -158,6 +176,15 @@ export class Lexer {
158
176
  while (this.pos < this.src.length && this.peek() !== "\n")
159
177
  this.advance();
160
178
  const comment = this.src.slice(start, this.pos);
179
+ if (
180
+ /^\/\/go:embed(?:\s|$)/.test(comment) &&
181
+ /^[ \t]*$/.test(
182
+ this.src.slice(this.src.lastIndexOf("\n", start - 1) + 1, start),
183
+ )
184
+ )
185
+ this.embedDirectives.push({ line, col });
186
+ const intrinsic = comment.match(/^\/\/gofront:intrinsic\s+(\S+)/);
187
+ if (intrinsic) this.intrinsics.set(line, intrinsic[1]);
161
188
  const match = comment.match(/^\/\/gofront:target\s+(\S+)/);
162
189
  if (match) {
163
190
  this.target = match[1];
@@ -222,29 +249,7 @@ export class Lexer {
222
249
  if (parts === null) parts = [];
223
250
  if (this.pos > start) parts.push(this.src.slice(start, this.pos));
224
251
  this.advance(); // skip \
225
- const esc = this.advance();
226
- switch (esc) {
227
- case "n":
228
- parts.push("\n");
229
- break;
230
- case "t":
231
- parts.push("\t");
232
- break;
233
- case "r":
234
- parts.push("\r");
235
- break;
236
- case '"':
237
- parts.push('"');
238
- break;
239
- case "\\":
240
- parts.push("\\");
241
- break;
242
- case "0":
243
- parts.push("\0");
244
- break;
245
- default:
246
- parts.push(`\\${esc}`);
247
- }
252
+ parts.push(String.fromCodePoint(this.readEscape('"')));
248
253
  start = this.pos;
249
254
  } else {
250
255
  this.advance();
@@ -253,6 +258,46 @@ export class Lexer {
253
258
  throw new LexError("Unterminated string", l, c, this.filename, this.src);
254
259
  }
255
260
 
261
+ // Reads the escape after a consumed backslash and returns its code point.
262
+ // `quote` is the enclosing delimiter: `\'` is valid only in runes and
263
+ // `\"` only in strings. `\xNN` and octal escapes give U+0000-U+00FF (see
264
+ // plan §1.1: strings are JS strings, so \x80-\xff are code points, not raw bytes).
265
+ readEscape(quote) {
266
+ const l = this.line,
267
+ c = this.col;
268
+ const fail = (msg) => {
269
+ throw new LexError(msg, l, c, this.filename, this.src);
270
+ };
271
+ const esc = this.advance();
272
+ const simple = SIMPLE_ESCAPES[esc];
273
+ if (simple !== undefined) return simple;
274
+ if (esc === quote) return esc.charCodeAt(0);
275
+ if (esc === "x") return this.readEscapeDigits(2, 16, fail);
276
+ if (esc === "u" || esc === "U") {
277
+ const v = this.readEscapeDigits(esc === "u" ? 4 : 8, 16, fail);
278
+ if (v > 0x10ffff || (v >= 0xd800 && v < 0xe000))
279
+ fail("escape is invalid Unicode code point");
280
+ return v;
281
+ }
282
+ if (esc >= "0" && esc <= "7") {
283
+ const v = Number(esc) * 64 + this.readEscapeDigits(2, 8, fail);
284
+ if (v > 255) fail("octal escape value exceeds 255");
285
+ return v;
286
+ }
287
+ return fail(`unknown escape sequence: \\${esc}`);
288
+ }
289
+
290
+ readEscapeDigits(n, radix, fail) {
291
+ let v = 0;
292
+ for (let i = 0; i < n; i++) {
293
+ const d = Number.parseInt(this.src[this.pos] ?? "", radix);
294
+ if (Number.isNaN(d)) fail("invalid character in escape sequence");
295
+ v = v * radix + d;
296
+ this.advance();
297
+ }
298
+ return v;
299
+ }
300
+
256
301
  readRawString() {
257
302
  const l = this.line,
258
303
  c = this.col;
@@ -279,35 +324,7 @@ export class Lexer {
279
324
  let code;
280
325
  if (this.peek() === "\\") {
281
326
  this.advance();
282
- const esc = this.advance();
283
- switch (esc) {
284
- case "n":
285
- code = 10;
286
- break;
287
- case "t":
288
- code = 9;
289
- break;
290
- case "r":
291
- code = 13;
292
- break;
293
- case "'":
294
- code = 39;
295
- break;
296
- case "\\":
297
- code = 92;
298
- break;
299
- case "0":
300
- code = 0;
301
- break;
302
- default:
303
- throw new LexError(
304
- `Unknown escape in rune literal: \\${esc}`,
305
- l,
306
- c,
307
- this.filename,
308
- this.src,
309
- );
310
- }
327
+ code = this.readEscape("'");
311
328
  } else if (this.peek() !== "'") {
312
329
  code = this.src.codePointAt(this.pos);
313
330
  this.advance();
@@ -392,6 +409,20 @@ export class Lexer {
392
409
  return { n, isFloat: false };
393
410
  }
394
411
 
412
+ // The leading "." is already consumed; reads digits and an optional exponent.
413
+ _readLeadingDotFloat() {
414
+ let n = "0.";
415
+ while (this.pos < this.src.length) {
416
+ const c = this.src.charCodeAt(this.pos);
417
+ if (c >= 48 && c <= 57) n += this.advance();
418
+ else if (c === 95) this.advance();
419
+ else break;
420
+ }
421
+ const c = this.src.charCodeAt(this.pos);
422
+ if (c === 101 || c === 69) n = this._readDecimalExponent(n);
423
+ return { n };
424
+ }
425
+
395
426
  _readDecimalExponent(n) {
396
427
  n += this.advance(); // consume e/E
397
428
  if (this.peek() === "+" || this.peek() === "-") n += this.advance();
@@ -450,6 +481,13 @@ export class Lexer {
450
481
  return { n, isFloat };
451
482
  }
452
483
 
484
+ _startsUnicodeIdent() {
485
+ return (
486
+ this.peek().charCodeAt(0) > 127 &&
487
+ isUnicodeLetter(this.src.codePointAt(this.pos))
488
+ );
489
+ }
490
+
453
491
  readIdent() {
454
492
  const start = this.pos;
455
493
  while (this.pos < this.src.length) {
@@ -461,6 +499,10 @@ export class Lexer {
461
499
  code === 95
462
500
  ) {
463
501
  this.pos++;
502
+ } else if (code > 127) {
503
+ const cp = this.src.codePointAt(this.pos);
504
+ if (!isUnicodeLetter(cp) && !isUnicodeDigit(cp)) break;
505
+ this.pos += cp > 0xffff ? 2 : 1;
464
506
  } else {
465
507
  break;
466
508
  }
@@ -526,7 +568,7 @@ export class Lexer {
526
568
  }
527
569
 
528
570
  // Identifiers & keywords
529
- if (/[a-zA-Z_]/.test(ch)) {
571
+ if (/[a-zA-Z_]/.test(ch) || this._startsUnicodeIdent()) {
530
572
  const id = this.readIdent();
531
573
  this.push(KEYWORDS.has(id) ? id : T.IDENT, id, l, c);
532
574
  continue;
@@ -541,6 +583,8 @@ export class Lexer {
541
583
  this.push(T.EOF, "", this.line, this.col);
542
584
  this.tokens.target = this.target;
543
585
  this.tokens.targetDirective = this.targetDirective;
586
+ this.tokens.intrinsics = this.intrinsics;
587
+ this.tokens.embedDirectives = this.embedDirectives;
544
588
  return this.tokens;
545
589
  }
546
590
  _readPlusOp(l, c) {
@@ -614,7 +658,10 @@ export class Lexer {
614
658
  else this.push(T.PIPE, "|", l, c);
615
659
  break;
616
660
  case ".":
617
- if (this.peek() === "." && this.peek(1) === ".") {
661
+ if (/[0-9]/.test(this.peek() ?? "")) {
662
+ const { n } = this._readLeadingDotFloat();
663
+ this.push(T.FLOAT, n, l, c);
664
+ } else if (this.peek() === "." && this.peek(1) === ".") {
618
665
  this.advance();
619
666
  this.advance();
620
667
  this.push(T.ELLIPSIS, "...", l, c);
@@ -80,6 +80,7 @@ export const T = {
80
80
  RSHIFT: ">>",
81
81
  ELLIPSIS: "...",
82
82
  FALLTHROUGH: "fallthrough",
83
+ GOTO: "goto",
83
84
 
84
85
  // Delimiters
85
86
  LPAREN: "(",
@@ -0,0 +1,97 @@
1
+ // Source-map merging for the bundle: chunk concatenation with line-offset
2
+ // mappings, the inline JS source map and the function-level wasm map.
3
+
4
+ import { relative, resolve } from "node:path";
5
+ import { buildSourceMap, buildWasmSourceMap } from "../backend/js/index.js";
6
+
7
+ function normalizeChunk(c) {
8
+ if (typeof c === "string") {
9
+ return { js: c, sourceFiles: [], sourcesContent: [], mappings: [] };
10
+ }
11
+ return c;
12
+ }
13
+
14
+ function appendChunkMappings(dest, mappings, localToUnifiedIdx, lineOffset) {
15
+ if (!mappings) return;
16
+ for (const m of mappings) {
17
+ dest.push({
18
+ genLine: m.genLine + lineOffset,
19
+ srcLine: m.srcLine,
20
+ srcFileIdx: localToUnifiedIdx[m.srcFileIdx ?? 0] ?? 0,
21
+ });
22
+ }
23
+ }
24
+
25
+ export function mergeCompilationChunks(chunks) {
26
+ const activeChunks = chunks
27
+ .filter(Boolean)
28
+ .map(normalizeChunk)
29
+ .filter((c) => c.js.length > 0);
30
+
31
+ if (activeChunks.length === 0) {
32
+ return { js: "", sourceFiles: [], sourcesContent: [], mappings: [] };
33
+ }
34
+
35
+ const unifiedSources = [];
36
+ const unifiedSourcesContent = [];
37
+ const fileIdxMap = new Map();
38
+
39
+ function getOrAddFile(filePath, content) {
40
+ const key = resolve(filePath);
41
+ let idx = fileIdxMap.get(key);
42
+ if (idx === undefined) {
43
+ idx = unifiedSources.length;
44
+ fileIdxMap.set(key, idx);
45
+ unifiedSources.push(key);
46
+ unifiedSourcesContent.push(content);
47
+ }
48
+ return idx;
49
+ }
50
+
51
+ const combinedJsParts = [];
52
+ const mergedMappings = [];
53
+ let currentLineOffset = 0;
54
+
55
+ for (const chunk of activeChunks) {
56
+ combinedJsParts.push(chunk.js);
57
+ const localToUnifiedIdx = (chunk.sourceFiles ?? []).map((file, i) =>
58
+ getOrAddFile(file, chunk.sourcesContent?.[i] ?? ""),
59
+ );
60
+ appendChunkMappings(
61
+ mergedMappings,
62
+ chunk.mappings,
63
+ localToUnifiedIdx,
64
+ currentLineOffset,
65
+ );
66
+ currentLineOffset += chunk.js.split("\n").length;
67
+ }
68
+
69
+ return {
70
+ js: combinedJsParts.join("\n"),
71
+ sourceFiles: unifiedSources,
72
+ sourcesContent: unifiedSourcesContent,
73
+ mappings: mergedMappings,
74
+ };
75
+ }
76
+
77
+ export function attachSourceMap(merged, outputDir) {
78
+ const relativeSources = merged.sourceFiles.map((f) => relative(outputDir, f));
79
+ const mapJson = buildSourceMap(
80
+ relativeSources,
81
+ merged.mappings,
82
+ merged.sourcesContent,
83
+ );
84
+ const b64 = Buffer.from(mapJson).toString("base64");
85
+ merged.js += `\n//# sourceMappingURL=data:application/json;base64,${b64}`;
86
+ }
87
+
88
+ export const WASM_MAP_URL = "app.wasm.map";
89
+
90
+ // Function-level `app.wasm.map` (sources relative to the output dir).
91
+ export function buildWasmMap(info, outputDir) {
92
+ if (!info || info.mappings.length === 0) return null;
93
+ return buildWasmSourceMap(
94
+ info.sources.map((f) => relative(outputDir, f)),
95
+ info.mappings,
96
+ );
97
+ }