gofront 1.6.0 → 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 (68) hide show
  1. package/package.json +1 -1
  2. package/src/backend/js/expressions.js +238 -109
  3. package/src/backend/js/index.js +38 -1
  4. package/src/backend/js/runtime.js +953 -53
  5. package/src/backend/js/statements.js +55 -67
  6. package/src/backend/js/stdlib/base64.js +12 -0
  7. package/src/backend/js/stdlib/binary.js +102 -0
  8. package/src/backend/js/stdlib/bits.js +15 -1
  9. package/src/backend/js/stdlib/builder.js +28 -9
  10. package/src/backend/js/stdlib/bytes.js +75 -18
  11. package/src/backend/js/stdlib/errors.js +20 -1
  12. package/src/backend/js/stdlib/fmt.js +70 -13
  13. package/src/backend/js/stdlib/hex.js +17 -0
  14. package/src/backend/js/stdlib/html.js +1 -1
  15. package/src/backend/js/stdlib/index.js +16 -0
  16. package/src/backend/js/stdlib/io.js +105 -3
  17. package/src/backend/js/stdlib/math.js +13 -1
  18. package/src/backend/js/stdlib/path.js +18 -4
  19. package/src/backend/js/stdlib/rand.js +10 -5
  20. package/src/backend/js/stdlib/sha256.js +12 -0
  21. package/src/backend/js/stdlib/slices.js +52 -26
  22. package/src/backend/js/stdlib/strconv.js +21 -5
  23. package/src/backend/js/stdlib/strings.js +148 -15
  24. package/src/backend/js/stdlib/sync.js +13 -0
  25. package/src/backend/js/stdlib/time.js +90 -19
  26. package/src/backend/js/stdlib/unicode.js +2 -2
  27. package/src/backend/wasm/boundary.js +1207 -41
  28. package/src/backend/wasm/const-fold.js +205 -0
  29. package/src/backend/wasm/emit-builtins.js +203 -76
  30. package/src/backend/wasm/emit-exprs.js +464 -276
  31. package/src/backend/wasm/emit-maps.js +144 -8
  32. package/src/backend/wasm/emit-stdlib.js +12 -3005
  33. package/src/backend/wasm/emit-stmts.js +264 -270
  34. package/src/backend/wasm/emit.js +115 -87
  35. package/src/backend/wasm/encode.js +55 -29
  36. package/src/backend/wasm/index.js +1001 -32
  37. package/src/backend/wasm/optimize.js +13 -4
  38. package/src/backend/wasm/stdlib/bytes.js +5039 -0
  39. package/src/backend/wasm/stdlib/core.js +8903 -0
  40. package/src/backend/wasm/stdlib/index.js +40 -0
  41. package/src/backend/wasm/stdlib/io.js +2161 -0
  42. package/src/backend/wasm/stdlib/packages.js +2400 -0
  43. package/src/backend/wasm/stdlib/pure.js +720 -0
  44. package/src/backend/wasm/stdlib/time.js +1200 -0
  45. package/src/backend/wasm/types.js +153 -1
  46. package/src/cli-core.js +1 -1
  47. package/src/compiler.js +18 -4
  48. package/src/dts-parser.js +79 -126
  49. package/src/index.js +2 -6
  50. package/src/lexer.js +80 -29
  51. package/src/lower/boxing.js +1 -2
  52. package/src/lower/captures.js +2 -6
  53. package/src/lower/ownership.js +30 -9
  54. package/src/minifier.js +20 -68
  55. package/src/parser/declarations.js +3 -6
  56. package/src/parser/expressions.js +3 -8
  57. package/src/parser/index.js +18 -3
  58. package/src/parser/statements.js +25 -38
  59. package/src/parser/types.js +1 -2
  60. package/src/resolver.js +2 -27
  61. package/src/templ-lexer.js +29 -63
  62. package/src/templ-parser.js +22 -45
  63. package/src/typechecker/assignability.js +81 -13
  64. package/src/typechecker/expressions.js +102 -2
  65. package/src/typechecker/resolve.js +36 -6
  66. package/src/typechecker/stdlib/core.js +280 -89
  67. package/src/typechecker/stdlib/extended.js +453 -1
  68. package/src/typechecker/types.js +344 -6
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 ──────────────────────────────────────
@@ -10,8 +10,7 @@ export function isReferenceType(t) {
10
10
  base?.kind === "struct" ||
11
11
  base?.kind === "slice" ||
12
12
  base?.kind === "map" ||
13
- base?.kind === "func" ||
14
- base?.kind === "interface"
13
+ base?.kind === "func"
15
14
  );
16
15
  }
17
16
 
@@ -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
  }
@@ -89,19 +89,42 @@ export function collectMutatedVars(
89
89
 
90
90
  export function fnMutates(body, name, addrOnly = false, cache = null) {
91
91
  if (!body) return true;
92
- const key = `${addrOnly ? "&" : ""}${name}`;
93
92
  if (cache) {
94
- if (!cache.has(key)) {
95
- cache.set(key, nodeMutatesVar(body, name, addrOnly));
93
+ const setKey = addrOnly ? "&*" : "*";
94
+ let set = cache.get(setKey);
95
+ if (!set) {
96
+ set = collectMutatedVars(body, new Set(), addrOnly);
97
+ cache.set(setKey, set);
96
98
  }
97
- return cache.get(key);
99
+ return set.has(name);
98
100
  }
99
101
  return nodeMutatesVar(body, name, addrOnly);
100
102
  }
101
103
 
104
+ function collectClosureMutatedVars(node, mutated = new Set()) {
105
+ if (!node || typeof node !== "object") return mutated;
106
+ if (Array.isArray(node)) {
107
+ for (const item of node) collectClosureMutatedVars(item, mutated);
108
+ return mutated;
109
+ }
110
+ if (node.kind === "FuncLit") {
111
+ collectMutatedVars(node.body, mutated, false);
112
+ return mutated;
113
+ }
114
+ forEachChild(node, (child) => collectClosureMutatedVars(child, mutated));
115
+ return mutated;
116
+ }
117
+
102
118
  export function closureMutates(body, name, cache = null) {
103
- const key = `λ${name}`;
104
- if (cache?.has(key)) return cache.get(key);
119
+ if (!body) return false;
120
+ if (cache) {
121
+ let set = cache.get("λ*");
122
+ if (!set) {
123
+ set = collectClosureMutatedVars(body, new Set());
124
+ cache.set("λ*", set);
125
+ }
126
+ return set.has(name);
127
+ }
105
128
 
106
129
  const walk = (node) => {
107
130
  if (!node || typeof node !== "object") return false;
@@ -109,9 +132,7 @@ export function closureMutates(body, name, cache = null) {
109
132
  if (node.kind === "FuncLit") return nodeMutatesVar(node.body, name);
110
133
  return someChild(node, walk);
111
134
  };
112
- const res = walk(body);
113
- if (cache) cache.set(key, res);
114
- return res;
135
+ return walk(body);
115
136
  }
116
137
 
117
138
  export class OwnershipContext {
package/src/minifier.js CHANGED
@@ -121,7 +121,7 @@ const REGEX_CONTEXT_VALUES = new Set([
121
121
  const DIVISION_AFTER_OPS = new Set([")", "]", "++", "--"]);
122
122
 
123
123
  function _isRegexContext(tokens) {
124
- const prev = lastNonWhitespaceToken(tokens);
124
+ const prev = findPrevNonWs(tokens);
125
125
  if (!prev) return true;
126
126
  if (prev.type === "op") return !DIVISION_AFTER_OPS.has(prev.value);
127
127
  return REGEX_CONTEXT_VALUES.has(prev.value);
@@ -323,14 +323,6 @@ function tokenize(code) {
323
323
  return tokens;
324
324
  }
325
325
 
326
- function lastNonWhitespaceToken(tokens) {
327
- for (let i = tokens.length - 1; i >= 0; i--) {
328
- if (tokens[i].type !== "ws" && tokens[i].type !== "comment")
329
- return tokens[i];
330
- }
331
- return null;
332
- }
333
-
334
326
  // ── Stage 1 & 2: Compress ────────────────────────────────────
335
327
 
336
328
  function isIdentLike(type) {
@@ -348,15 +340,6 @@ const SPACE_AFTER_KW_RIGHT_TYPES = new Set([
348
340
  ]);
349
341
  // Operator values that do NOT need a space after a keyword
350
342
  const NO_SPACE_AFTER_KW_OPS = new Set(["(", "[", "!", "-", "+"]);
351
- // Adjacent op pairs that would accidentally merge into a different operator
352
- const _INCREMENT_MERGE_PAIRS = new Set([
353
- "+|+",
354
- "-|-",
355
- "+|++",
356
- "-|--",
357
- "++|+",
358
- "--|−-",
359
- ]);
360
343
 
361
344
  function _wouldMergeUnaryOps(left, right) {
362
345
  return (
@@ -436,11 +419,7 @@ function compressTokens(tokens) {
436
419
  return out;
437
420
  }
438
421
 
439
- function compress(code) {
440
- return compressTokens(tokenize(code));
441
- }
442
-
443
- function findPrevNonWs(tokens, index) {
422
+ function findPrevNonWs(tokens, index = tokens.length) {
444
423
  for (let i = index - 1; i >= 0; i--) {
445
424
  if (tokens[i].type !== "ws" && tokens[i].type !== "comment")
446
425
  return tokens[i];
@@ -477,7 +456,7 @@ function _matchingParen(tokens, openIdx) {
477
456
  // A `(` opens a parameter list only when its `)` is followed by a body `{` or `=>`;
478
457
  // otherwise `name(...)` is a call and its arguments may be free globals.
479
458
  function _collectParamDecls(tokens, i, declaredLocals) {
480
- const prev = findPrevNonWsToken(tokens, i);
459
+ const prev = findPrevNonWs(tokens, i);
481
460
  if (
482
461
  !prev ||
483
462
  (prev.value !== "function" && prev.type !== "ident" && prev.value !== ")")
@@ -583,22 +562,7 @@ function _filterRenameCandidates(declaredLocals) {
583
562
  }
584
563
  }
585
564
 
586
- function _applyRenaming(tokens, renameMap) {
587
- let out = "";
588
- for (let i = 0; i < tokens.length; i++) {
589
- const tok = tokens[i];
590
- if (tok.type === "ident" && renameMap.has(tok.value)) {
591
- const prev = findPrevNonWsToken(tokens, i);
592
- out += prev && prev.value === "." ? tok.value : renameMap.get(tok.value);
593
- } else {
594
- out += tok.value;
595
- }
596
- }
597
- return out;
598
- }
599
-
600
- function mangle(code) {
601
- const tokens = tokenize(code);
565
+ function mangleTokens(tokens) {
602
566
  const { declaredLocals } = _collectLocalDecls(tokens);
603
567
  _filterRenameCandidates(declaredLocals);
604
568
 
@@ -612,7 +576,15 @@ function mangle(code) {
612
576
  const nameGen = shortNameGeneratorFn(occupied);
613
577
  const renameMap = new Map();
614
578
  for (const name of declaredLocals) renameMap.set(name, nameGen());
615
- return _applyRenaming(tokens, renameMap);
579
+ return tokens.map((tok, i) => {
580
+ if (tok.type === "ident" && renameMap.has(tok.value)) {
581
+ const prev = findPrevNonWs(tokens, i);
582
+ if (prev?.value !== ".") {
583
+ return { type: "ident", value: renameMap.get(tok.value) };
584
+ }
585
+ }
586
+ return tok;
587
+ });
616
588
  }
617
589
 
618
590
  function nextNonWs(tokens, index) {
@@ -624,14 +596,6 @@ function nextNonWs(tokens, index) {
624
596
  return null;
625
597
  }
626
598
 
627
- function findPrevNonWsToken(tokens, index) {
628
- for (let i = index - 1; i >= 0; i--) {
629
- if (tokens[i].type !== "ws" && tokens[i].type !== "comment")
630
- return tokens[i];
631
- }
632
- return null;
633
- }
634
-
635
599
  function collectDestructured(tokens, startIdx, locals) {
636
600
  const open = tokens[startIdx].value;
637
601
  const close = open === "[" ? "]" : "}";
@@ -647,7 +611,7 @@ function collectDestructured(tokens, startIdx, locals) {
647
611
  }
648
612
 
649
613
  function _isParamStart(tokens, i) {
650
- const prev = findPrevNonWsToken(tokens, i);
614
+ const prev = findPrevNonWs(tokens, i);
651
615
  return (
652
616
  !prev ||
653
617
  prev.value === "(" ||
@@ -839,28 +803,16 @@ function foldTokens(tokens) {
839
803
  return current;
840
804
  }
841
805
 
842
- function foldLiterals(code) {
843
- const tokens = tokenize(code);
844
- return compressTokens(foldTokens(tokens));
845
- }
846
-
847
806
  // ── Public API ───────────────────────────────────────────────
848
807
 
849
808
  export function minify(code, options = {}) {
850
809
  const { mangle: doMangle = false } = options;
851
-
852
- // Stage 1 + 2: compress (strips comments, whitespace, compresses tokens)
853
- let result = compress(code);
854
-
855
- // Stage 4: literal folding
856
- result = foldLiterals(result);
857
-
858
- // Stage 3: mangling (optional)
810
+ let tokens = tokenize(code).filter(
811
+ (t) => t.type !== "ws" && t.type !== "comment",
812
+ );
813
+ tokens = foldTokens(tokens);
859
814
  if (doMangle) {
860
- result = mangle(result);
861
- // Re-compress after mangling to clean up any leftover whitespace
862
- result = compress(result);
815
+ tokens = mangleTokens(tokens);
863
816
  }
864
-
865
- return result;
817
+ return compressTokens(tokens);
866
818
  }
@@ -261,8 +261,7 @@ export const declarationParseMethods = {
261
261
  },
262
262
 
263
263
  parseVarSpec() {
264
- const names = [this.expect(T.IDENT).value];
265
- while (this.match(T.COMMA)) names.push(this.expect(T.IDENT).value);
264
+ const names = this.parseIdentList();
266
265
  let type = null;
267
266
  let value = null;
268
267
  if (
@@ -284,8 +283,7 @@ export const declarationParseMethods = {
284
283
  let prevRawExprs = null; // raw AST (with iota Idents) from the previous spec
285
284
  let prevType = null;
286
285
  while (!this.check(T.RPAREN) && !this.check(T.EOF)) {
287
- const names = [this.expect(T.IDENT).value];
288
- while (this.match(T.COMMA)) names.push(this.expect(T.IDENT).value);
286
+ const names = this.parseIdentList();
289
287
  let type = null;
290
288
  let rawExprs; // before iota substitution
291
289
  if (this.check(T.ASSIGN)) {
@@ -315,8 +313,7 @@ export const declarationParseMethods = {
315
313
  }
316
314
  this.expect(T.RPAREN);
317
315
  } else {
318
- const names = [this.expect(T.IDENT).value];
319
- while (this.match(T.COMMA)) names.push(this.expect(T.IDENT).value);
316
+ const names = this.parseIdentList();
320
317
  let type = null;
321
318
  if (!this.check(T.ASSIGN)) type = this.parseType();
322
319
  this.expect(T.ASSIGN);
@@ -25,6 +25,8 @@ const PREC = {
25
25
  [T.AND_NOT]: 5,
26
26
  };
27
27
 
28
+ const UNARY_OPS = new Set([T.NOT, T.MINUS, T.PLUS, T.CARET, T.AMP, T.STAR]);
29
+
28
30
  const TYPE_KEYWORDS = new Set([
29
31
  "int",
30
32
  "int8",
@@ -128,14 +130,7 @@ export const expressionParserMethods = {
128
130
 
129
131
  parseUnary(noCompLit = false) {
130
132
  const t = this.peek();
131
- if (
132
- t.type === T.NOT ||
133
- t.type === T.MINUS ||
134
- t.type === T.PLUS ||
135
- t.type === T.CARET ||
136
- t.type === T.AMP ||
137
- t.type === T.STAR
138
- ) {
133
+ if (UNARY_OPS.has(t.type)) {
139
134
  const op = this.advance().value;
140
135
  return { kind: "UnaryExpr", op, operand: this.parseUnary(noCompLit) };
141
136
  }
@@ -73,9 +73,18 @@ export class Parser {
73
73
  return this.peek2().type === type;
74
74
  }
75
75
 
76
- match(...types) {
77
- for (const type of types) {
78
- if (this.check(type)) {
76
+ match(t1, t2, t3, ...rest) {
77
+ const cur = this.peek().type;
78
+ if (
79
+ cur === t1 ||
80
+ (t2 !== undefined && cur === t2) ||
81
+ (t3 !== undefined && cur === t3)
82
+ ) {
83
+ this.advance();
84
+ return true;
85
+ }
86
+ for (let i = 0; i < rest.length; i++) {
87
+ if (cur === rest[i]) {
79
88
  this.advance();
80
89
  return true;
81
90
  }
@@ -83,6 +92,12 @@ export class Parser {
83
92
  return false;
84
93
  }
85
94
 
95
+ parseIdentList() {
96
+ const names = [this.expect(T.IDENT).value];
97
+ while (this.match(T.COMMA)) names.push(this.expect(T.IDENT).value);
98
+ return names;
99
+ }
100
+
86
101
  expect(type) {
87
102
  if (!this.check(type))
88
103
  throw new ParseError(`expected '${type}'`, this.peek());
@@ -15,6 +15,20 @@ const PARSE_STMT_DELEGATE = {
15
15
  [T.LBRACE]: "parseBlock",
16
16
  };
17
17
 
18
+ const ASSIGN_OPS = new Set([
19
+ T.ASSIGN,
20
+ T.PLUS_ASSIGN,
21
+ T.MINUS_ASSIGN,
22
+ T.STAR_ASSIGN,
23
+ T.SLASH_ASSIGN,
24
+ T.PERCENT_ASSIGN,
25
+ T.AMP_ASSIGN,
26
+ T.PIPE_ASSIGN,
27
+ T.CARET_ASSIGN,
28
+ T.LSHIFT_ASSIGN,
29
+ T.RSHIFT_ASSIGN,
30
+ ]);
31
+
18
32
  export const statementParserMethods = {
19
33
  parseBlock() {
20
34
  this.expect(T.LBRACE);
@@ -206,22 +220,13 @@ export const statementParserMethods = {
206
220
  this.expect(T.LBRACE);
207
221
  const cases = [];
208
222
  while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
209
- if (this.check(T.CASE)) {
210
- this.advance();
211
- const list = this.parseExprList();
212
- this.expect(T.COLON);
213
- cases.push({ kind: "CaseClause", list, stmts: this._parseCaseBody() });
214
- } else if (this.check(T.DEFAULT)) {
215
- this.advance();
216
- this.expect(T.COLON);
217
- cases.push({
218
- kind: "CaseClause",
219
- list: null,
220
- stmts: this._parseCaseBody(),
221
- });
222
- } else {
223
+ const isCase = this.match(T.CASE);
224
+ if (!isCase && !this.match(T.DEFAULT)) {
223
225
  this.err("Expected case or default");
224
226
  }
227
+ const list = isCase ? this.parseExprList() : null;
228
+ this.expect(T.COLON);
229
+ cases.push({ kind: "CaseClause", list, stmts: this._parseCaseBody() });
225
230
  }
226
231
  this.expect(T.RBRACE);
227
232
  return { kind: "SwitchStmt", init, tag, cases };
@@ -244,18 +249,13 @@ export const statementParserMethods = {
244
249
  this.expect(T.LBRACE);
245
250
  const cases = [];
246
251
  while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
247
- if (this.check(T.CASE)) {
248
- this.advance();
249
- const types = this._parseTypeSwitchTypeList();
250
- this.expect(T.COLON);
251
- cases.push({ types, stmts: this._parseCaseBody() });
252
- } else if (this.check(T.DEFAULT)) {
253
- this.advance();
254
- this.expect(T.COLON);
255
- cases.push({ types: null, stmts: this._parseCaseBody() });
256
- } else {
252
+ const isCase = this.match(T.CASE);
253
+ if (!isCase && !this.match(T.DEFAULT)) {
257
254
  this.err("Expected case or default");
258
255
  }
256
+ const types = isCase ? this._parseTypeSwitchTypeList() : null;
257
+ this.expect(T.COLON);
258
+ cases.push({ types, stmts: this._parseCaseBody() });
259
259
  }
260
260
  this.expect(T.RBRACE);
261
261
  return { kind: "TypeSwitchStmt", assign, expr, cases };
@@ -305,20 +305,7 @@ export const statementParserMethods = {
305
305
  }
306
306
 
307
307
  // x = expr or x += expr etc.
308
- const assignOps = [
309
- T.ASSIGN,
310
- T.PLUS_ASSIGN,
311
- T.MINUS_ASSIGN,
312
- T.STAR_ASSIGN,
313
- T.SLASH_ASSIGN,
314
- T.PERCENT_ASSIGN,
315
- T.AMP_ASSIGN,
316
- T.PIPE_ASSIGN,
317
- T.CARET_ASSIGN,
318
- T.LSHIFT_ASSIGN,
319
- T.RSHIFT_ASSIGN,
320
- ];
321
- if (assignOps.includes(t.type)) {
308
+ if (ASSIGN_OPS.has(t.type)) {
322
309
  const op = this.advance().value;
323
310
  const rhs = this.parseExprList();
324
311
  return { kind: "AssignStmt", lhs: [first], op, rhs };
@@ -175,8 +175,7 @@ export const typeParserMethods = {
175
175
  this.expect(T.LBRACE);
176
176
  const fields = [];
177
177
  while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
178
- const names = [this.expect(T.IDENT).value];
179
- while (this.match(T.COMMA)) names.push(this.expect(T.IDENT).value);
178
+ const names = this.parseIdentList();
180
179
  // Check if this is an embedded field (just a type name)
181
180
  // e.g., type T struct { Base }
182
181
  if (this.check(T.SEMICOLON) || this.check(T.RBRACE)) {
package/src/resolver.js CHANGED
@@ -25,32 +25,7 @@ function findNodeModules(startDir) {
25
25
  }
26
26
  }
27
27
 
28
- // Built-in stdlib package paths that are always available as globals — no resolution needed.
29
- const BUILTIN_PACKAGES = new Set([
30
- "fmt",
31
- "strings",
32
- "bytes",
33
- "strconv",
34
- "sort",
35
- "math",
36
- "math/bits",
37
- "math/rand",
38
- "errors",
39
- "time",
40
- "unicode",
41
- "unicode/utf8",
42
- "os",
43
- "slices",
44
- "html",
45
- "io",
46
- "gom",
47
- "maps",
48
- "regexp",
49
- "path",
50
- "path/filepath",
51
- "testing",
52
- "gofront/shared",
53
- ]);
28
+ import { STDLIB_PACKAGES } from "./typechecker/types.js";
54
29
 
55
30
  // Returns true for local relative paths (start with ./ or ../)
56
31
  export function isLocalPath(importPath) {
@@ -58,7 +33,7 @@ export function isLocalPath(importPath) {
58
33
  }
59
34
 
60
35
  export function isBuiltinPackage(importPath) {
61
- return BUILTIN_PACKAGES.has(importPath);
36
+ return STDLIB_PACKAGES.has(importPath);
62
37
  }
63
38
 
64
39
  // Resolve a local relative import path to an absolute directory of .go files.