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.
Files changed (91) hide show
  1. package/README.md +174 -466
  2. package/package.json +16 -20
  3. package/src/backend/js/expressions.js +288 -128
  4. package/src/backend/js/index.js +138 -56
  5. package/src/backend/js/runtime.js +1055 -53
  6. package/src/backend/js/source-map.js +32 -0
  7. package/src/backend/js/statements.js +151 -83
  8. package/src/backend/js/stdlib/base64.js +12 -0
  9. package/src/backend/js/stdlib/binary.js +102 -0
  10. package/src/backend/js/stdlib/bits.js +15 -1
  11. package/src/backend/js/stdlib/builder.js +28 -9
  12. package/src/backend/js/stdlib/bytes.js +75 -18
  13. package/src/backend/js/stdlib/errors.js +22 -3
  14. package/src/backend/js/stdlib/fmt.js +95 -20
  15. package/src/backend/js/stdlib/hex.js +17 -0
  16. package/src/backend/js/stdlib/html.js +1 -1
  17. package/src/backend/js/stdlib/index.js +19 -0
  18. package/src/backend/js/stdlib/io.js +105 -3
  19. package/src/backend/js/stdlib/maps.js +1 -1
  20. package/src/backend/js/stdlib/math.js +13 -1
  21. package/src/backend/js/stdlib/path.js +20 -6
  22. package/src/backend/js/stdlib/rand.js +10 -5
  23. package/src/backend/js/stdlib/sha256.js +12 -0
  24. package/src/backend/js/stdlib/shared.js +17 -0
  25. package/src/backend/js/stdlib/slices.js +53 -27
  26. package/src/backend/js/stdlib/sort.js +1 -1
  27. package/src/backend/js/stdlib/strconv.js +52 -11
  28. package/src/backend/js/stdlib/strings.js +152 -16
  29. package/src/backend/js/stdlib/sync.js +13 -0
  30. package/src/backend/js/stdlib/testing.js +2 -2
  31. package/src/backend/js/stdlib/time.js +93 -22
  32. package/src/backend/js/stdlib/unicode.js +2 -2
  33. package/src/backend/js/templ.js +1 -1
  34. package/src/backend/wasm/boundary.js +1677 -58
  35. package/src/backend/wasm/const-fold.js +205 -0
  36. package/src/backend/wasm/emit-builtins.js +1230 -0
  37. package/src/backend/wasm/emit-exprs.js +2096 -0
  38. package/src/backend/wasm/emit-maps.js +1999 -0
  39. package/src/backend/wasm/emit-shared.js +519 -0
  40. package/src/backend/wasm/emit-stdlib.js +13 -0
  41. package/src/backend/wasm/emit-stmts.js +2466 -0
  42. package/src/backend/wasm/emit.js +656 -5031
  43. package/src/backend/wasm/encode.js +259 -85
  44. package/src/backend/wasm/glue.js +19 -5
  45. package/src/backend/wasm/index.js +2432 -86
  46. package/src/backend/wasm/monomorph.js +711 -0
  47. package/src/backend/wasm/optimize.js +236 -0
  48. package/src/backend/wasm/stdlib/bytes.js +5039 -0
  49. package/src/backend/wasm/stdlib/core.js +8903 -0
  50. package/src/backend/wasm/stdlib/index.js +40 -0
  51. package/src/backend/wasm/stdlib/io.js +2161 -0
  52. package/src/backend/wasm/stdlib/packages.js +2400 -0
  53. package/src/backend/wasm/stdlib/pure.js +720 -0
  54. package/src/backend/wasm/stdlib/time.js +1200 -0
  55. package/src/backend/wasm/types.js +312 -36
  56. package/src/backend/wasm/wat.js +49 -2
  57. package/src/cli-core.js +276 -73
  58. package/src/compiler.js +162 -25
  59. package/src/dts-parser.js +79 -126
  60. package/src/index.js +88 -121
  61. package/src/lexer.js +80 -29
  62. package/src/lower/boxing.js +4 -6
  63. package/src/lower/captures.js +12 -23
  64. package/src/lower/escape.js +4 -9
  65. package/src/lower/functions.js +277 -5
  66. package/src/lower/globals.js +7 -15
  67. package/src/lower/index.js +5 -2
  68. package/src/lower/ownership.js +35 -19
  69. package/src/lower/range.js +2 -5
  70. package/src/lower/walk.js +25 -0
  71. package/src/minifier.js +20 -68
  72. package/src/parser/declarations.js +3 -6
  73. package/src/parser/expressions.js +3 -8
  74. package/src/parser/index.js +18 -3
  75. package/src/parser/statements.js +25 -38
  76. package/src/parser/types.js +1 -2
  77. package/src/resolver.js +3 -27
  78. package/src/templ-lexer.js +29 -63
  79. package/src/templ-parser.js +22 -45
  80. package/src/test-runner.js +20 -0
  81. package/src/typechecker/assignability.js +123 -14
  82. package/src/typechecker/expressions.js +244 -4
  83. package/src/typechecker/index.js +27 -1
  84. package/src/typechecker/resolve.js +71 -12
  85. package/src/typechecker/statements.js +0 -7
  86. package/src/typechecker/stdlib/core.js +292 -103
  87. package/src/typechecker/stdlib/extended.js +471 -4
  88. package/src/typechecker/stdlib/shared.js +53 -0
  89. package/src/typechecker/stdlib.js +2 -0
  90. package/src/typechecker/types.js +545 -8
  91. package/src/vendor.js +2 -2
package/src/compiler.js CHANGED
@@ -18,9 +18,13 @@
18
18
  // de-qualifies it because the dependency is inlined.
19
19
 
20
20
  import { readdirSync, readFileSync, statSync } from "node:fs";
21
- import { basename, dirname, join, relative, resolve } from "node:path";
22
- import { buildSourceMap, CodeGen } from "./backend/js/index.js";
23
- import { compileWasmModule } from "./backend/wasm/index.js";
21
+ import { basename, dirname, join, relative, resolve, sep } from "node:path";
22
+ import {
23
+ buildSourceMap,
24
+ buildWasmSourceMap,
25
+ CodeGen,
26
+ } from "./backend/js/index.js";
27
+ import { compileWasmModule, optimizeWasm } from "./backend/wasm/index.js";
24
28
  import { log } from "./colors.js";
25
29
  import { parseDts } from "./dts-parser.js";
26
30
  import { Lexer } from "./lexer.js";
@@ -34,7 +38,7 @@ import {
34
38
  } from "./resolver.js";
35
39
  import { TemplLexer } from "./templ-lexer.js";
36
40
  import { TemplParser } from "./templ-parser.js";
37
- import { TypeChecker } from "./typechecker/index.js";
41
+ import { TypeCheckError, TypeChecker } from "./typechecker/index.js";
38
42
  import { WASM_SUPPORTED_STDLIB } from "./typechecker/types.js";
39
43
 
40
44
  // ── Parse cache ──────────────────────────────────────────────
@@ -67,6 +71,9 @@ function parseGoFrontFile(filePath) {
67
71
  ? new TemplParser(tokens, filename, source).parse()
68
72
  : new Parser(tokens, filename, source).parse();
69
73
  ast._source = source;
74
+ ast._path = resolve(filePath);
75
+ // compileFiles overwrites `target` with the resolved one; keep the directive.
76
+ ast._declaredTarget = ast.target;
70
77
  _parseCache.set(filePath, { mtime, ast });
71
78
  return ast;
72
79
  }
@@ -163,6 +170,49 @@ function attachSourceMap(merged, outputDir) {
163
170
  merged.js += `\n//# sourceMappingURL=data:application/json;base64,${b64}`;
164
171
  }
165
172
 
173
+ const WASM_MAP_URL = "app.wasm.map";
174
+
175
+ // Function-level `app.wasm.map` (sources relative to the output dir).
176
+ function buildWasmMap(info, outputDir) {
177
+ if (!info || info.mappings.length === 0) return null;
178
+ return buildWasmSourceMap(
179
+ info.sources.map((f) => relative(outputDir, f)),
180
+ info.mappings,
181
+ );
182
+ }
183
+
184
+ // Shared tail of compileFiles / compileSingleFile: link `app.wasm`, run
185
+ // wasm-opt when requested and produce the wasm source map.
186
+ function finishWasm(options, merged, pkgTarget, outputDir) {
187
+ const wantMap = Boolean(options.sourceMap && !options.isDependency);
188
+ let { wasm, wat, sourceMapInfo } = linkIntoBundle(
189
+ options,
190
+ merged,
191
+ pkgTarget,
192
+ {
193
+ sourceMappingURL: wantMap ? WASM_MAP_URL : null,
194
+ },
195
+ );
196
+ let wasmOptInfo = null;
197
+ let wasmSourceMap =
198
+ wantMap && wasm ? buildWasmMap(sourceMapInfo, outputDir) : null;
199
+
200
+ if (wasm && options.wasmOpt) {
201
+ const opt = optimizeWasm(wasm, {
202
+ sourceMap: Boolean(wasmSourceMap),
203
+ inputSourceMap: wasmSourceMap,
204
+ sourceMapUrl: WASM_MAP_URL,
205
+ emitWat: Boolean(options.emitWat),
206
+ converge: Boolean(options.release),
207
+ });
208
+ wasm = opt.wasm;
209
+ if (opt.wat) wat = opt.wat;
210
+ wasmOptInfo = opt;
211
+ if (opt.sourceMap) wasmSourceMap = opt.sourceMap;
212
+ }
213
+ return { wasm, wat, wasmOptInfo, wasmSourceMap };
214
+ }
215
+
166
216
  // ── WASM linking ──────────────────────────────────────────────
167
217
  //
168
218
  // Every `wasm`/`both` package reached during a build registers a unit in the
@@ -205,7 +255,10 @@ function registerPackageUnit(options, unit) {
205
255
  return emitTarget;
206
256
  }
207
257
 
208
- function linkWasmUnits(units, { emitWat = false, rootTarget = "js" } = {}) {
258
+ function linkWasmUnits(
259
+ units,
260
+ { emitWat = false, rootTarget = "js", sourceMappingURL = null } = {},
261
+ ) {
209
262
  if (!units.some((u) => u.target === "wasm")) return null;
210
263
  checkLinkCollisions(units);
211
264
 
@@ -217,13 +270,27 @@ function linkWasmUnits(units, { emitWat = false, rootTarget = "js" } = {}) {
217
270
  for (const b of u.bundledPackages) bundledPackages.add(b);
218
271
  programs.push(...u.programs);
219
272
  }
220
- const checker = { types };
273
+ const checker = {
274
+ types,
275
+ errors: [],
276
+ err(msg, node) {
277
+ const e = new TypeCheckError(
278
+ msg,
279
+ node,
280
+ this._currentFile,
281
+ this._currentSource,
282
+ );
283
+ this.errors.push(e);
284
+ return e;
285
+ },
286
+ };
221
287
  const lowerRes = lower(programs, checker);
222
288
  return compileWasmModule(programs, checker, lowerRes, {
223
289
  boundary: true,
224
290
  bundledPackages,
225
291
  emitWat,
226
292
  callMain: rootTarget === "wasm",
293
+ sourceMappingURL,
227
294
  });
228
295
  }
229
296
 
@@ -291,17 +358,23 @@ function spliceFacade(merged, facade) {
291
358
  }
292
359
 
293
360
  // Root compiles only: link every registered unit into app.wasm and splice the
294
- // facade into the merged bundle. Returns `{ wasm, wat }` (nulls when there
295
- // is nothing to link).
296
- function linkIntoBundle(options, merged, pkgTarget) {
297
- if (options.isDependency) return { wasm: null, wat: null };
361
+ // facade into the merged bundle. Returns `{ wasm, wat, sourceMapInfo }`
362
+ // (nulls when there is nothing to link).
363
+ function linkIntoBundle(options, merged, pkgTarget, { sourceMappingURL } = {}) {
364
+ const none = { wasm: null, wat: null, sourceMapInfo: null };
365
+ if (options.isDependency) return none;
298
366
  const linked = linkWasmUnits(options.wasmUnits, {
299
367
  emitWat: options.emitWat,
300
368
  rootTarget: pkgTarget,
369
+ sourceMappingURL,
301
370
  });
302
- if (!linked) return { wasm: null, wat: null };
371
+ if (!linked) return none;
303
372
  spliceFacade(merged, linked.facade);
304
- return { wasm: linked.wasm, wat: linked.wat ?? null };
373
+ return {
374
+ wasm: linked.wasm,
375
+ wat: linked.wat ?? null,
376
+ sourceMapInfo: linked.sourceMapInfo ?? null,
377
+ };
305
378
  }
306
379
 
307
380
  // ── Import resolution ─────────────────────────────────────────
@@ -309,7 +382,7 @@ function linkIntoBundle(options, merged, pkgTarget) {
309
382
  // Shared by compileFiles (multi-file) and the single-file path in index.js.
310
383
  // Mutates checker, jsImports, bundledPackages, and preambles in place.
311
384
 
312
- export function resolveImports(
385
+ function resolveImports(
313
386
  programs,
314
387
  fromFile,
315
388
  checker,
@@ -350,6 +423,11 @@ export function resolveImports(
350
423
  "package 'gom' is not available in wasm packages",
351
424
  impNode,
352
425
  );
426
+ } else if (path === "gofront/shared" && pkgTarget === "both") {
427
+ checker.err(
428
+ "'gofront/shared' requires a wasm-only package (//gofront:target wasm): a both package has no linear memory on the JS side",
429
+ impNode,
430
+ );
353
431
  } else if (!isLocalPath(path)) {
354
432
  if (!isBuiltinPackage(path)) {
355
433
  checker.recordBlocker("js: import", path);
@@ -472,6 +550,7 @@ export function resolveImports(
472
550
  nameUsed,
473
551
  dep.exportedSymbols,
474
552
  dep.exportedTypes,
553
+ dep.target,
475
554
  );
476
555
  checker.trackImport(nameUsed, { _line }, p._filename, p._source);
477
556
  }
@@ -481,6 +560,29 @@ export function resolveImports(
481
560
 
482
561
  // ── Main entry points ─────────────────────────────────────────
483
562
 
563
+ // Package target after build-time overrides (Task H6.3): `forceTarget`
564
+ // (`--js-only`) compiles every package to JS; `targetOverrides` maps package
565
+ // directories relative to the root source dir (`"engine/physics"`, `"."` for
566
+ // the root) to a target, replacing the `//gofront:target` directive.
567
+ const PACKAGE_TARGETS = new Set(["js", "wasm", "both"]);
568
+
569
+ export function resolvePackageTarget(declared, pkgDir, options = {}) {
570
+ if (options.forceTarget) return options.forceTarget;
571
+ const overrides = options.targetOverrides;
572
+ if (!overrides || !options.rootDir) return declared;
573
+ const rel = relative(resolve(options.rootDir), resolve(pkgDir))
574
+ .split(sep)
575
+ .join("/");
576
+ const key = rel === "" ? "." : rel;
577
+ const t = overrides[key] ?? (key === "." ? overrides[""] : undefined);
578
+ if (t === undefined) return declared;
579
+ if (!PACKAGE_TARGETS.has(t))
580
+ throw new Error(
581
+ `gofront.json: invalid target '${t}' for package '${key}' (expected js, wasm or both)`,
582
+ );
583
+ return t;
584
+ }
585
+
484
586
  export function compileSingleFile(inputPath, options = {}) {
485
587
  const {
486
588
  sourceMap = false,
@@ -502,6 +604,7 @@ export function compileSingleFile(inputPath, options = {}) {
502
604
 
503
605
  const ast = new Parser(tokens, basename(inputPath), source).parse();
504
606
  ast._source = source;
607
+ ast._path = resolve(inputPath);
505
608
  if (dumpAst) return { ast };
506
609
 
507
610
  const outputDir =
@@ -513,7 +616,11 @@ export function compileSingleFile(inputPath, options = {}) {
513
616
  const bundledPackages = new Set();
514
617
  const preambles = [];
515
618
 
516
- const pkgTarget = ast.target ?? options.target ?? "js";
619
+ const pkgTarget = resolvePackageTarget(
620
+ ast.target ?? options.target ?? "js",
621
+ dirname(resolve(inputPath)),
622
+ options,
623
+ );
517
624
  ast.target = pkgTarget;
518
625
  checker.target = pkgTarget;
519
626
  checker.pkgName = ast.pkg?.name ?? "main";
@@ -534,6 +641,7 @@ export function compileSingleFile(inputPath, options = {}) {
534
641
  if (errors.length > 0) {
535
642
  throw new Error(errors.map((e) => e.message).join("\n"));
536
643
  }
644
+ for (const w of checker.warnings) log.warn(w);
537
645
 
538
646
  if (pkgTarget === "wasm" || pkgTarget === "both") {
539
647
  registerWasmUnit(options, {
@@ -562,13 +670,26 @@ export function compileSingleFile(inputPath, options = {}) {
562
670
  };
563
671
 
564
672
  const merged = mergeCompilationChunks([...preambles, mainChunk]);
565
- const { wasm, wat } = linkIntoBundle(options, merged, pkgTarget);
673
+ const { wasm, wat, wasmOptInfo, wasmSourceMap } = finishWasm(
674
+ { ...options, sourceMap },
675
+ merged,
676
+ pkgTarget,
677
+ outputDir,
678
+ );
566
679
 
567
680
  if (sourceMap && !options.isDependency) {
568
681
  attachSourceMap(merged, outputDir);
569
682
  }
570
683
 
571
- return { js: merged.js, css: cg.getCss(), target: pkgTarget, wasm, wat };
684
+ return {
685
+ js: merged.js,
686
+ css: cg.getCss(),
687
+ target: pkgTarget,
688
+ wasm,
689
+ wat,
690
+ wasmOptInfo,
691
+ wasmSourceMap,
692
+ };
572
693
  }
573
694
 
574
695
  export function compileDir(dir, options = {}) {
@@ -591,8 +712,12 @@ export function compilePackageTestsWasm(dir, options = {}) {
591
712
  }
592
713
 
593
714
  export function compileFiles(files, options = {}) {
594
- options = { ...options, wasmUnits: options.wasmUnits ?? [] };
595
715
  const fromDir = options.fromDir ?? dirname(resolve(files[0]));
716
+ options = {
717
+ ...options,
718
+ wasmUnits: options.wasmUnits ?? [],
719
+ rootDir: options.rootDir ?? fromDir,
720
+ };
596
721
  const outputDir =
597
722
  options.outputDir ??
598
723
  (options.outputFile ? dirname(resolve(options.outputFile)) : fromDir);
@@ -622,9 +747,10 @@ export function compileFiles(files, options = {}) {
622
747
  // Validate target directives across files in this package
623
748
  const declaredTargets = new Map();
624
749
  for (const p of programs) {
625
- if (p.target) {
626
- if (!declaredTargets.has(p.target)) declaredTargets.set(p.target, []);
627
- declaredTargets.get(p.target).push(p._filename);
750
+ const declared = p._declaredTarget;
751
+ if (declared) {
752
+ if (!declaredTargets.has(declared)) declaredTargets.set(declared, []);
753
+ declaredTargets.get(declared).push(p._filename);
628
754
  }
629
755
  }
630
756
  if (declaredTargets.size > 1) {
@@ -635,10 +761,13 @@ export function compileFiles(files, options = {}) {
635
761
  `Conflicting //gofront:target directives in package '${pkgName}': ${list}`,
636
762
  );
637
763
  }
638
- const pkgTarget =
764
+ const pkgTarget = resolvePackageTarget(
639
765
  declaredTargets.size === 1
640
766
  ? declaredTargets.keys().next().value
641
- : (options.target ?? "js");
767
+ : (options.target ?? "js"),
768
+ fromDir,
769
+ options,
770
+ );
642
771
  for (const p of programs) p.target = pkgTarget;
643
772
 
644
773
  // ── 2. Resolve imports ────────────────────────────────────────
@@ -668,6 +797,7 @@ export function compileFiles(files, options = {}) {
668
797
  const msgs = errors.map((e) => e.message).join("\n");
669
798
  throw new Error(msgs);
670
799
  }
800
+ for (const w of checker.warnings) log.warn(w);
671
801
 
672
802
  // ── 4. Code generation ────────────────────────────────────────
673
803
  const emitTarget = registerPackageUnit(options, {
@@ -693,13 +823,18 @@ export function compileFiles(files, options = {}) {
693
823
 
694
824
  const mainChunk = {
695
825
  js: mainJs,
696
- sourceFiles: files.map((f) => resolve(f)),
697
- sourcesContent: files.map((f) => readFileSync(f, "utf8")),
826
+ sourceFiles: programs.map((p) => p._path),
827
+ sourcesContent: programs.map((p) => p._source),
698
828
  mappings: emitTarget === "wasm" ? [] : codegen.getMappings(),
699
829
  };
700
830
 
701
831
  const merged = mergeCompilationChunks([...preambles, mainChunk]);
702
- const { wasm, wat } = linkIntoBundle(options, merged, pkgTarget);
832
+ const { wasm, wat, wasmOptInfo, wasmSourceMap } = finishWasm(
833
+ options,
834
+ merged,
835
+ pkgTarget,
836
+ outputDir,
837
+ );
703
838
 
704
839
  if (options.sourceMap && !options.isDependency) {
705
840
  attachSourceMap(merged, outputDir);
@@ -717,6 +852,8 @@ export function compileFiles(files, options = {}) {
717
852
  css: allCss,
718
853
  wasm,
719
854
  wat,
855
+ wasmOptInfo,
856
+ wasmSourceMap,
720
857
  programs,
721
858
  exportedSymbols: checker.getExportedSymbols(),
722
859
  exportedTypes: checker.getExportedTypes(),
package/src/dts-parser.js CHANGED
@@ -109,10 +109,9 @@ export class DtsParser {
109
109
 
110
110
  readIdent() {
111
111
  this.skip();
112
- let s = "";
113
- while (!this.eof() && /[a-zA-Z0-9_$]/.test(this.ch()))
114
- s += this.src[this.pos++];
115
- return s;
112
+ const start = this.pos;
113
+ while (!this.eof() && /[a-zA-Z0-9_$]/.test(this.ch())) this.pos++;
114
+ return this.src.slice(start, this.pos);
116
115
  }
117
116
 
118
117
  consume(ch) {
@@ -171,14 +170,14 @@ export class DtsParser {
171
170
  }
172
171
 
173
172
  _trackBracketDepth(c, depths) {
174
- const open = { "<": 0, "(": 1, "[": 2, "{": 3 };
175
- const close = { ">": 0, ")": 1, "]": 2, "}": 3 };
176
- if (c in open) {
177
- depths[open[c]]++;
173
+ const oi = "<([{".indexOf(c);
174
+ if (oi >= 0) {
175
+ depths[oi]++;
178
176
  return true;
179
177
  }
180
- if (c in close && depths[close[c]] > 0) {
181
- depths[close[c]]--;
178
+ const ci = ">)]}".indexOf(c);
179
+ if (ci >= 0 && depths[ci] > 0) {
180
+ depths[ci]--;
182
181
  return true;
183
182
  }
184
183
  return false;
@@ -392,7 +391,7 @@ export class DtsParser {
392
391
  }
393
392
  }
394
393
 
395
- _parseMemberFuncDecl(out) {
394
+ _parseFuncDecl() {
396
395
  const name = this.readIdent();
397
396
  this.skipGenerics();
398
397
  const params = this.parseParams();
@@ -403,12 +402,14 @@ export class DtsParser {
403
402
  ret = this.parseType();
404
403
  }
405
404
  this.consume(";");
406
- if (name)
407
- out[name] = {
405
+ return {
406
+ name,
407
+ val: {
408
408
  kind: "func",
409
409
  params: params.map((p) => p.type),
410
410
  returns: [ret],
411
- };
411
+ },
412
+ };
412
413
  }
413
414
 
414
415
  _parseMemberNewSig() {
@@ -422,7 +423,7 @@ export class DtsParser {
422
423
  this.consume(";");
423
424
  }
424
425
 
425
- _parseMemberTypeDecl(out) {
426
+ _parseTypeDecl() {
426
427
  const name = this.readIdent();
427
428
  this.skipGenerics();
428
429
  this.skip();
@@ -432,10 +433,10 @@ export class DtsParser {
432
433
  type = this.parseType();
433
434
  }
434
435
  this.consume(";");
435
- if (name) out[name] = type;
436
+ return { name, type };
436
437
  }
437
438
 
438
- _parseMemberClassDecl(out) {
439
+ _parseClassDecl() {
439
440
  const name = this.readIdent();
440
441
  this.skipGenerics();
441
442
  while (!this.eof() && this.ch() !== "{") this.pos++;
@@ -443,10 +444,20 @@ export class DtsParser {
443
444
  const body = this.parseBody();
444
445
  this.consume("}");
445
446
  this.consume(";");
446
- if (name) out[name] = { kind: "namespace", name, members: body };
447
+ return { name, ns: { kind: "namespace", name, members: body } };
448
+ }
449
+
450
+ _parseNamespaceDecl() {
451
+ const name = this.readIdent() || this._readStringLit();
452
+ this.skip();
453
+ this.consume("{");
454
+ const body = this.parseBody();
455
+ this.consume("}");
456
+ this.consume(";");
457
+ return { name, ns: { kind: "namespace", name, members: body } };
447
458
  }
448
459
 
449
- _parseMemberVarDecl(out) {
460
+ _parseVarDecl() {
450
461
  const name = this.readIdent();
451
462
  this.skip();
452
463
  this.consume("?");
@@ -461,7 +472,7 @@ export class DtsParser {
461
472
  this.skipTypeExpr();
462
473
  }
463
474
  this.consume(";");
464
- if (name) out[name] = type;
475
+ return { name, type };
465
476
  }
466
477
 
467
478
  _parseMember(out) {
@@ -477,28 +488,29 @@ export class DtsParser {
477
488
  }
478
489
  this.skip();
479
490
  switch (kw) {
480
- case "function":
481
- this._parseMemberFuncDecl(out);
491
+ case "function": {
492
+ const { name, val } = this._parseFuncDecl();
493
+ if (name) out[name] = val;
482
494
  break;
495
+ }
483
496
  case "new":
484
497
  this._parseMemberNewSig();
485
498
  break;
486
- case "type":
487
- this._parseMemberTypeDecl(out);
499
+ case "type": {
500
+ const { name, type } = this._parseTypeDecl();
501
+ if (name) out[name] = type;
488
502
  break;
503
+ }
489
504
  case "interface":
490
- case "class":
491
- this._parseMemberClassDecl(out);
505
+ case "class": {
506
+ const { name, ns } = this._parseClassDecl();
507
+ if (name) out[name] = ns;
492
508
  break;
509
+ }
493
510
  case "namespace":
494
511
  case "module": {
495
- const name = this.readIdent() || this._readStringLit();
496
- this.skip();
497
- this.consume("{");
498
- const body = this.parseBody();
499
- this.consume("}");
500
- this.consume(";");
501
- if (name) out[name] = { kind: "namespace", name, members: body };
512
+ const { name, ns } = this._parseNamespaceDecl();
513
+ if (name) out[name] = ns;
502
514
  break;
503
515
  }
504
516
  case "enum": {
@@ -509,9 +521,11 @@ export class DtsParser {
509
521
  }
510
522
  case "const":
511
523
  case "let":
512
- case "var":
513
- this._parseMemberVarDecl(out);
524
+ case "var": {
525
+ const { name, type } = this._parseVarDecl();
526
+ if (name) out[name] = type;
514
527
  break;
528
+ }
515
529
  default:
516
530
  this._parseMemberDefault(kw, out);
517
531
  break;
@@ -550,13 +564,15 @@ export class DtsParser {
550
564
  this.consume(";");
551
565
  }
552
566
  }
567
+
553
568
  _readStringLit() {
554
569
  this.skip();
555
570
  const c = this.ch();
556
571
  if (c !== '"' && c !== "'") return null;
557
572
  this.pos++;
558
- let s = "";
559
- while (!this.eof() && this.ch() !== c) s += this.src[this.pos++];
573
+ const start = this.pos;
574
+ while (!this.eof() && this.ch() !== c) this.pos++;
575
+ const s = this.src.slice(start, this.pos);
560
576
  this.pos++;
561
577
  return s;
562
578
  }
@@ -612,85 +628,6 @@ export class DtsParser {
612
628
  return { types, values };
613
629
  }
614
630
 
615
- _parseTopLevelTypeAlias(types) {
616
- const name = this.readIdent();
617
- this.skipGenerics();
618
- this.skip();
619
- let type = ANY;
620
- if (this.ch() === "=") {
621
- this.pos++;
622
- type = this.parseType();
623
- }
624
- this.consume(";");
625
- if (name) types.set(name, type);
626
- }
627
-
628
- _parseTopLevelClassDecl(kw, types, values) {
629
- const name = this.readIdent();
630
- this.skipGenerics();
631
- while (!this.eof() && this.ch() !== "{") this.pos++;
632
- this.consume("{");
633
- const body = this.parseBody();
634
- this.consume("}");
635
- this.consume(";");
636
- if (name) {
637
- const ns = { kind: "namespace", name, members: body };
638
- types.set(name, ns);
639
- if (kw === "class") values.set(name, ns);
640
- }
641
- }
642
-
643
- _parseTopLevelNamespaceDecl(types, values) {
644
- const name = this.readIdent() || this._readStringLit();
645
- this.skip();
646
- this.consume("{");
647
- const body = this.parseBody();
648
- this.consume("}");
649
- this.consume(";");
650
- if (name) {
651
- const ns = { kind: "namespace", name, members: body };
652
- types.set(name, ns);
653
- values.set(name, ns);
654
- }
655
- }
656
-
657
- _parseTopLevelFuncDecl(values) {
658
- const name = this.readIdent();
659
- this.skipGenerics();
660
- const params = this.parseParams();
661
- this.skip();
662
- let ret = VOID;
663
- if (this.ch() === ":") {
664
- this.pos++;
665
- ret = this.parseType();
666
- }
667
- this.consume(";");
668
- if (name)
669
- values.set(name, {
670
- kind: "func",
671
- params: params.map((p) => p.type),
672
- returns: [ret],
673
- });
674
- }
675
-
676
- _parseTopLevelVarDecl(values) {
677
- const name = this.readIdent();
678
- this.skip();
679
- this.consume("?");
680
- let type = ANY;
681
- if (this.ch() === ":") {
682
- this.pos++;
683
- type = this.parseType();
684
- }
685
- this.skip();
686
- if (this.ch() === "=") {
687
- this.pos++;
688
- this.skipTypeExpr();
689
- }
690
- this.consume(";");
691
- if (name) values.set(name, type);
692
- }
693
-
694
631
  _parseTopLevelDecl(kw, types, values) {
695
632
  switch (kw) {
696
633
  case "import":
@@ -698,17 +635,29 @@ export class DtsParser {
698
635
  this.skipTypeExpr();
699
636
  this.consume(";");
700
637
  break;
701
- case "type":
702
- this._parseTopLevelTypeAlias(types);
638
+ case "type": {
639
+ const { name, type } = this._parseTypeDecl();
640
+ if (name) types.set(name, type);
703
641
  break;
642
+ }
704
643
  case "interface":
705
- case "class":
706
- this._parseTopLevelClassDecl(kw, types, values);
644
+ case "class": {
645
+ const { name, ns } = this._parseClassDecl();
646
+ if (name) {
647
+ types.set(name, ns);
648
+ if (kw === "class") values.set(name, ns);
649
+ }
707
650
  break;
651
+ }
708
652
  case "namespace":
709
- case "module":
710
- this._parseTopLevelNamespaceDecl(types, values);
653
+ case "module": {
654
+ const { name, ns } = this._parseNamespaceDecl();
655
+ if (name) {
656
+ types.set(name, ns);
657
+ values.set(name, ns);
658
+ }
711
659
  break;
660
+ }
712
661
  case "enum": {
713
662
  const name = this.readIdent();
714
663
  this.skipBlock();
@@ -719,14 +668,18 @@ export class DtsParser {
719
668
  }
720
669
  break;
721
670
  }
722
- case "function":
723
- this._parseTopLevelFuncDecl(values);
671
+ case "function": {
672
+ const { name, val } = this._parseFuncDecl();
673
+ if (name) values.set(name, val);
724
674
  break;
675
+ }
725
676
  case "const":
726
677
  case "let":
727
- case "var":
728
- this._parseTopLevelVarDecl(values);
678
+ case "var": {
679
+ const { name, type } = this._parseVarDecl();
680
+ if (name) values.set(name, type);
729
681
  break;
682
+ }
730
683
  default:
731
684
  this.skipTypeExpr();
732
685
  this.consume(";");