gofront 1.6.0 → 1.7.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 (80) hide show
  1. package/README.md +35 -39
  2. package/package.json +37 -4
  3. package/src/backend/js/expressions.js +559 -144
  4. package/src/backend/js/index.js +174 -49
  5. package/src/backend/js/runtime.js +1518 -54
  6. package/src/backend/js/statements.js +91 -73
  7. package/src/backend/js/stdlib/base64.js +12 -0
  8. package/src/backend/js/stdlib/binary.js +102 -0
  9. package/src/backend/js/stdlib/bits.js +15 -1
  10. package/src/backend/js/stdlib/builder.js +28 -9
  11. package/src/backend/js/stdlib/bytes.js +75 -18
  12. package/src/backend/js/stdlib/errors.js +25 -1
  13. package/src/backend/js/stdlib/fmt.js +70 -13
  14. package/src/backend/js/stdlib/hex.js +17 -0
  15. package/src/backend/js/stdlib/html.js +1 -1
  16. package/src/backend/js/stdlib/index.js +28 -0
  17. package/src/backend/js/stdlib/io.js +105 -3
  18. package/src/backend/js/stdlib/json.js +972 -0
  19. package/src/backend/js/stdlib/math.js +13 -1
  20. package/src/backend/js/stdlib/path.js +18 -4
  21. package/src/backend/js/stdlib/rand.js +10 -5
  22. package/src/backend/js/stdlib/sha256.js +12 -0
  23. package/src/backend/js/stdlib/simd.js +24 -0
  24. package/src/backend/js/stdlib/slices.js +52 -26
  25. package/src/backend/js/stdlib/strconv.js +21 -5
  26. package/src/backend/js/stdlib/strings.js +148 -15
  27. package/src/backend/js/stdlib/sync.js +13 -0
  28. package/src/backend/js/stdlib/time.js +90 -19
  29. package/src/backend/js/stdlib/unicode.js +2 -2
  30. package/src/backend/wasm/boundary.js +1225 -41
  31. package/src/backend/wasm/const-fold.js +205 -0
  32. package/src/backend/wasm/emit-builtins.js +220 -76
  33. package/src/backend/wasm/emit-exprs.js +475 -276
  34. package/src/backend/wasm/emit-maps.js +144 -8
  35. package/src/backend/wasm/emit-stdlib.js +12 -3005
  36. package/src/backend/wasm/emit-stmts.js +264 -270
  37. package/src/backend/wasm/emit.js +124 -87
  38. package/src/backend/wasm/encode.js +140 -29
  39. package/src/backend/wasm/index.js +1001 -32
  40. package/src/backend/wasm/optimize.js +17 -6
  41. package/src/backend/wasm/stdlib/bytes.js +5039 -0
  42. package/src/backend/wasm/stdlib/core.js +8903 -0
  43. package/src/backend/wasm/stdlib/index.js +43 -0
  44. package/src/backend/wasm/stdlib/io.js +2161 -0
  45. package/src/backend/wasm/stdlib/packages.js +2400 -0
  46. package/src/backend/wasm/stdlib/pure.js +720 -0
  47. package/src/backend/wasm/stdlib/simd.js +234 -0
  48. package/src/backend/wasm/stdlib/time.js +1200 -0
  49. package/src/backend/wasm/types.js +174 -1
  50. package/src/backend/wasm/wat.js +37 -0
  51. package/src/cli-core.js +1 -1
  52. package/src/compiler.js +18 -4
  53. package/src/dts-parser.js +150 -140
  54. package/src/index.js +2 -6
  55. package/src/lexer.js +80 -29
  56. package/src/lower/boxing.js +1 -7
  57. package/src/lower/captures.js +2 -6
  58. package/src/lower/embedding.js +2 -1
  59. package/src/lower/ownership.js +30 -9
  60. package/src/minifier.js +20 -68
  61. package/src/parser/declarations.js +3 -6
  62. package/src/parser/expressions.js +3 -8
  63. package/src/parser/index.js +18 -3
  64. package/src/parser/statements.js +25 -38
  65. package/src/parser/struct-tag.js +144 -0
  66. package/src/parser/types.js +64 -8
  67. package/src/resolver.js +2 -27
  68. package/src/templ-lexer.js +29 -63
  69. package/src/templ-parser.js +22 -45
  70. package/src/typechecker/assignability.js +126 -13
  71. package/src/typechecker/expressions.js +168 -16
  72. package/src/typechecker/index.js +35 -8
  73. package/src/typechecker/resolve.js +187 -8
  74. package/src/typechecker/statements.js +68 -9
  75. package/src/typechecker/stdlib/core.js +280 -89
  76. package/src/typechecker/stdlib/extended.js +490 -1
  77. package/src/typechecker/stdlib/simd.js +73 -0
  78. package/src/typechecker/stdlib.js +2 -0
  79. package/src/typechecker/types.js +362 -9
  80. package/src/vendor.js +86 -11
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;
@@ -204,9 +203,48 @@ export class DtsParser {
204
203
 
205
204
  // ── Type parsing ─────────────────────────────────────────────
206
205
 
206
+ _parseTupleElement() {
207
+ this.skip();
208
+ if (this.startsWith("...")) this.pos += 3;
209
+
210
+ const savedPos = this.pos;
211
+ const ident = this.readIdent();
212
+ if (ident) {
213
+ this.skip();
214
+ if (this.ch() === "?") {
215
+ this.pos++;
216
+ this.skip();
217
+ }
218
+ if (this.ch() === ":") {
219
+ this.pos++;
220
+ return this.parseType(false);
221
+ }
222
+ }
223
+ this.pos = savedPos;
224
+ return this.parseType(false);
225
+ }
226
+
227
+ _parseTupleType(inReturn) {
228
+ this.consume("[");
229
+ const elements = [];
230
+ while (!this.eof() && this.peek() !== "]") {
231
+ this.skip();
232
+ if (this.ch() === "]") break;
233
+ elements.push(this._parseTupleElement());
234
+ this.skip();
235
+ if (this.ch() !== ",") break;
236
+ this.pos++;
237
+ }
238
+ this.consume("]");
239
+ if (inReturn) {
240
+ return { kind: "tuple", types: elements };
241
+ }
242
+ return { kind: "slice", elem: ANY };
243
+ }
244
+
207
245
  // Parse a TypeScript type expression and return a GoFront type.
208
246
  // Always returns something; falls back to ANY for complex types.
209
- parseType() {
247
+ parseType(inReturn = false) {
210
248
  this.skip();
211
249
 
212
250
  // Skip modifiers
@@ -227,13 +265,16 @@ export class DtsParser {
227
265
  this.skip();
228
266
  if (this.ch() === "|" || this.ch() === "&") this.pos++;
229
267
 
230
- let base = this._parseBaseType();
268
+ let base = this._parseBaseType(inReturn);
231
269
 
232
270
  this.skip();
233
271
  // Array suffix: T[] or T[][]
234
272
  while (this.startsWith("[]")) {
235
273
  this.pos += 2;
236
- base = { kind: "slice", elem: base };
274
+ base = {
275
+ kind: "slice",
276
+ elem: base.kind === "tuple" ? { kind: "slice", elem: ANY } : base,
277
+ };
237
278
  this.skip();
238
279
  }
239
280
 
@@ -286,14 +327,11 @@ export class DtsParser {
286
327
  return { kind: "basic", name: "any", alias: name };
287
328
  }
288
329
 
289
- _parseBaseType() {
330
+ _parseBaseType(inReturn = false) {
290
331
  this.skip();
291
332
  const c = this.ch();
292
333
  if (c === "[") {
293
- this.pos++;
294
- this.skipTypeExpr("]");
295
- this.consume("]");
296
- return ANY;
334
+ return this._parseTupleType(inReturn);
297
335
  }
298
336
  if (c === "{") {
299
337
  this.skipBlock();
@@ -392,7 +430,7 @@ export class DtsParser {
392
430
  }
393
431
  }
394
432
 
395
- _parseMemberFuncDecl(out) {
433
+ _parseFuncDecl() {
396
434
  const name = this.readIdent();
397
435
  this.skipGenerics();
398
436
  const params = this.parseParams();
@@ -400,15 +438,26 @@ export class DtsParser {
400
438
  let ret = VOID;
401
439
  if (this.ch() === ":") {
402
440
  this.pos++;
403
- ret = this.parseType();
441
+ ret = this.parseType(true);
404
442
  }
405
443
  this.consume(";");
406
- if (name)
407
- out[name] = {
444
+ const isVariadic = params.some((p) => p.variadic);
445
+ return {
446
+ name,
447
+ val: {
408
448
  kind: "func",
409
- params: params.map((p) => p.type),
410
- returns: [ret],
411
- };
449
+ params: params.map((p) =>
450
+ p.variadic && p.type?.kind === "slice" ? p.type.elem : p.type,
451
+ ),
452
+ returns:
453
+ ret.kind === "tuple"
454
+ ? ret.types.length > 0
455
+ ? ret.types
456
+ : [VOID]
457
+ : [ret],
458
+ ...(isVariadic ? { variadic: true } : {}),
459
+ },
460
+ };
412
461
  }
413
462
 
414
463
  _parseMemberNewSig() {
@@ -422,7 +471,7 @@ export class DtsParser {
422
471
  this.consume(";");
423
472
  }
424
473
 
425
- _parseMemberTypeDecl(out) {
474
+ _parseTypeDecl() {
426
475
  const name = this.readIdent();
427
476
  this.skipGenerics();
428
477
  this.skip();
@@ -432,10 +481,10 @@ export class DtsParser {
432
481
  type = this.parseType();
433
482
  }
434
483
  this.consume(";");
435
- if (name) out[name] = type;
484
+ return { name, type };
436
485
  }
437
486
 
438
- _parseMemberClassDecl(out) {
487
+ _parseClassDecl() {
439
488
  const name = this.readIdent();
440
489
  this.skipGenerics();
441
490
  while (!this.eof() && this.ch() !== "{") this.pos++;
@@ -443,10 +492,20 @@ export class DtsParser {
443
492
  const body = this.parseBody();
444
493
  this.consume("}");
445
494
  this.consume(";");
446
- if (name) out[name] = { kind: "namespace", name, members: body };
495
+ return { name, ns: { kind: "namespace", name, members: body } };
447
496
  }
448
497
 
449
- _parseMemberVarDecl(out) {
498
+ _parseNamespaceDecl() {
499
+ const name = this.readIdent() || this._readStringLit();
500
+ this.skip();
501
+ this.consume("{");
502
+ const body = this.parseBody();
503
+ this.consume("}");
504
+ this.consume(";");
505
+ return { name, ns: { kind: "namespace", name, members: body } };
506
+ }
507
+
508
+ _parseVarDecl() {
450
509
  const name = this.readIdent();
451
510
  this.skip();
452
511
  this.consume("?");
@@ -461,7 +520,7 @@ export class DtsParser {
461
520
  this.skipTypeExpr();
462
521
  }
463
522
  this.consume(";");
464
- if (name) out[name] = type;
523
+ return { name, type };
465
524
  }
466
525
 
467
526
  _parseMember(out) {
@@ -477,28 +536,29 @@ export class DtsParser {
477
536
  }
478
537
  this.skip();
479
538
  switch (kw) {
480
- case "function":
481
- this._parseMemberFuncDecl(out);
539
+ case "function": {
540
+ const { name, val } = this._parseFuncDecl();
541
+ if (name) out[name] = val;
482
542
  break;
543
+ }
483
544
  case "new":
484
545
  this._parseMemberNewSig();
485
546
  break;
486
- case "type":
487
- this._parseMemberTypeDecl(out);
547
+ case "type": {
548
+ const { name, type } = this._parseTypeDecl();
549
+ if (name) out[name] = type;
488
550
  break;
551
+ }
489
552
  case "interface":
490
- case "class":
491
- this._parseMemberClassDecl(out);
553
+ case "class": {
554
+ const { name, ns } = this._parseClassDecl();
555
+ if (name) out[name] = ns;
492
556
  break;
557
+ }
493
558
  case "namespace":
494
559
  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 };
560
+ const { name, ns } = this._parseNamespaceDecl();
561
+ if (name) out[name] = ns;
502
562
  break;
503
563
  }
504
564
  case "enum": {
@@ -509,9 +569,11 @@ export class DtsParser {
509
569
  }
510
570
  case "const":
511
571
  case "let":
512
- case "var":
513
- this._parseMemberVarDecl(out);
572
+ case "var": {
573
+ const { name, type } = this._parseVarDecl();
574
+ if (name) out[name] = type;
514
575
  break;
576
+ }
515
577
  default:
516
578
  this._parseMemberDefault(kw, out);
517
579
  break;
@@ -531,13 +593,22 @@ export class DtsParser {
531
593
  let ret = VOID;
532
594
  if (this.ch() === ":") {
533
595
  this.pos++;
534
- ret = this.parseType();
596
+ ret = this.parseType(true);
535
597
  }
536
598
  this.consume(";");
599
+ const isVariadic = params.some((p) => p.variadic);
537
600
  out[name] = {
538
601
  kind: "func",
539
- params: params.map((p) => p.type),
540
- returns: [ret],
602
+ params: params.map((p) =>
603
+ p.variadic && p.type?.kind === "slice" ? p.type.elem : p.type,
604
+ ),
605
+ returns:
606
+ ret.kind === "tuple"
607
+ ? ret.types.length > 0
608
+ ? ret.types
609
+ : [VOID]
610
+ : [ret],
611
+ ...(isVariadic ? { variadic: true } : {}),
541
612
  };
542
613
  } else if (this.ch() === ":") {
543
614
  this.pos++;
@@ -550,13 +621,15 @@ export class DtsParser {
550
621
  this.consume(";");
551
622
  }
552
623
  }
624
+
553
625
  _readStringLit() {
554
626
  this.skip();
555
627
  const c = this.ch();
556
628
  if (c !== '"' && c !== "'") return null;
557
629
  this.pos++;
558
- let s = "";
559
- while (!this.eof() && this.ch() !== c) s += this.src[this.pos++];
630
+ const start = this.pos;
631
+ while (!this.eof() && this.ch() !== c) this.pos++;
632
+ const s = this.src.slice(start, this.pos);
560
633
  this.pos++;
561
634
  return s;
562
635
  }
@@ -612,85 +685,6 @@ export class DtsParser {
612
685
  return { types, values };
613
686
  }
614
687
 
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
688
  _parseTopLevelDecl(kw, types, values) {
695
689
  switch (kw) {
696
690
  case "import":
@@ -698,17 +692,29 @@ export class DtsParser {
698
692
  this.skipTypeExpr();
699
693
  this.consume(";");
700
694
  break;
701
- case "type":
702
- this._parseTopLevelTypeAlias(types);
695
+ case "type": {
696
+ const { name, type } = this._parseTypeDecl();
697
+ if (name) types.set(name, type);
703
698
  break;
699
+ }
704
700
  case "interface":
705
- case "class":
706
- this._parseTopLevelClassDecl(kw, types, values);
701
+ case "class": {
702
+ const { name, ns } = this._parseClassDecl();
703
+ if (name) {
704
+ types.set(name, ns);
705
+ if (kw === "class") values.set(name, ns);
706
+ }
707
707
  break;
708
+ }
708
709
  case "namespace":
709
- case "module":
710
- this._parseTopLevelNamespaceDecl(types, values);
710
+ case "module": {
711
+ const { name, ns } = this._parseNamespaceDecl();
712
+ if (name) {
713
+ types.set(name, ns);
714
+ values.set(name, ns);
715
+ }
711
716
  break;
717
+ }
712
718
  case "enum": {
713
719
  const name = this.readIdent();
714
720
  this.skipBlock();
@@ -719,14 +725,18 @@ export class DtsParser {
719
725
  }
720
726
  break;
721
727
  }
722
- case "function":
723
- this._parseTopLevelFuncDecl(values);
728
+ case "function": {
729
+ const { name, val } = this._parseFuncDecl();
730
+ if (name) values.set(name, val);
724
731
  break;
732
+ }
725
733
  case "const":
726
734
  case "let":
727
- case "var":
728
- this._parseTopLevelVarDecl(values);
735
+ case "var": {
736
+ const { name, type } = this._parseVarDecl();
737
+ if (name) values.set(name, type);
729
738
  break;
739
+ }
730
740
  default:
731
741
  this.skipTypeExpr();
732
742
  this.consume(";");
package/src/index.js CHANGED
@@ -13,11 +13,6 @@
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 { createRequire } from "node:module";
17
-
18
- const _require = createRequire(import.meta.url);
19
- const { version } = _require("../package.json");
20
-
21
16
  import { statSync } from "node:fs";
22
17
  import { dirname, relative, resolve } from "node:path";
23
18
  import { copyAssets } from "./asset-manager.js";
@@ -25,6 +20,7 @@ import {
25
20
  formatBuildSummary,
26
21
  formatPrepSummary,
27
22
  formatWrittenDesc,
23
+ GOFRONT_VERSION,
28
24
  handleBuild,
29
25
  handleCheck,
30
26
  handleDev,
@@ -47,7 +43,7 @@ import { colors, formatDiagnostic, log, ms, stamp } from "./colors.js";
47
43
 
48
44
  const args = process.argv.slice(2);
49
45
  if (args[0] === "--version" || args[0] === "-v") {
50
- console.log(`gofront ${version}`);
46
+ console.log(`gofront ${GOFRONT_VERSION}`);
51
47
  process.exit(0);
52
48
  }
53
49
 
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
- const parts = [];
207
- while (this.pos < this.src.length && this.peek() !== '"') {
208
- if (this.peek() === "\n") this.err("Unterminated string literal");
209
- if (this.peek() === "\\") {
210
- this.advance();
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
- parts.push(this.advance());
250
+ this.advance();
236
251
  }
237
252
  }
238
- if (this.pos >= this.src.length)
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 && /[0-9_]/.test(this.peek())) {
387
- const ch = this.advance();
388
- if (ch !== "_") n += ch;
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 && /[0-9_]/.test(this.peek())) {
396
- const ch = this.advance();
397
- if (ch !== "_") n += ch;
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 (this.peek() === "." && /[0-9]/.test(this.peek(1))) {
400
- isFloat = true;
401
- n += this.advance();
402
- while (this.pos < this.src.length && /[0-9_]/.test(this.peek())) {
403
- const ch = this.advance();
404
- if (ch !== "_") n += ch;
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.peek() === "e" || this.peek() === "E") {
408
- isFloat = true;
409
- n = this._readDecimalExponent(n);
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
- let s = "";
416
- while (this.pos < this.src.length && /[a-zA-Z0-9_]/.test(this.peek()))
417
- s += this.advance();
418
- return s;
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 ──────────────────────────────────────
@@ -6,13 +6,7 @@ import { forEachChild } from "./walk.js";
6
6
  export function isReferenceType(t) {
7
7
  if (!t) return false;
8
8
  const base = t.kind === "named" ? t.underlying : t;
9
- return (
10
- base?.kind === "struct" ||
11
- base?.kind === "slice" ||
12
- base?.kind === "map" ||
13
- base?.kind === "func" ||
14
- base?.kind === "interface"
15
- );
9
+ return base?.kind === "struct" || base?.kind === "func";
16
10
  }
17
11
 
18
12
  // Scan AST node for _addressTaken idents on scalars and populate a Set of boxed variable names.
@@ -2,7 +2,7 @@
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, nodeMutatesVar } from "./ownership.js";
5
+ import { collectMutatedVars } from "./ownership.js";
6
6
  import { forEachChild } from "./walk.js";
7
7
 
8
8
  /**
@@ -151,11 +151,7 @@ export function analyzeCaptures(fnDecl) {
151
151
  result.capturesByClosure.set(node, captured);
152
152
 
153
153
  for (const varName of captured) {
154
- if (
155
- mutatedVars.has(varName) ||
156
- nodeMutatesVar(fnDecl.body, varName) ||
157
- nodeMutatesVar(node.body, varName)
158
- ) {
154
+ if (mutatedVars.has(varName)) {
159
155
  result.mutatedCaptures.add(varName);
160
156
  }
161
157
  }
@@ -9,7 +9,8 @@ export function computeEmbeddedStubs(name, methodDecls, checker) {
9
9
  const declared = new Set(methodDecls.map((m) => m.name));
10
10
  const stubs = [];
11
11
 
12
- for (const embed of resolvedType._embeds) {
12
+ for (const rawEmbed of resolvedType._embeds) {
13
+ const embed = rawEmbed.kind === "pointer" ? rawEmbed.base : rawEmbed;
13
14
  const embedName = embed.kind === "named" ? embed.name : null;
14
15
  if (!embedName) continue;
15
16
  const embedBase = embed.kind === "named" ? embed.underlying : embed;