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
@@ -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 };
@@ -0,0 +1,144 @@
1
+ // src/parser/struct-tag.js
2
+ // Parse Go struct field tags matching Go's reflect.StructTag specification.
3
+
4
+ function unquoteTagValue(s) {
5
+ if (s.length < 2 || s[0] !== '"' || s[s.length - 1] !== '"') return s;
6
+ const inner = s.slice(1, -1);
7
+ let res = "";
8
+ for (let i = 0; i < inner.length; i++) {
9
+ if (inner[i] === "\\" && i + 1 < inner.length) {
10
+ i++;
11
+ const esc = inner[i];
12
+ switch (esc) {
13
+ case "n":
14
+ res += "\n";
15
+ break;
16
+ case "t":
17
+ res += "\t";
18
+ break;
19
+ case "r":
20
+ res += "\r";
21
+ break;
22
+ case '"':
23
+ res += '"';
24
+ break;
25
+ case "\\":
26
+ res += "\\";
27
+ break;
28
+ case "a":
29
+ res += "\x07";
30
+ break;
31
+ case "b":
32
+ res += "\b";
33
+ break;
34
+ case "f":
35
+ res += "\f";
36
+ break;
37
+ case "v":
38
+ res += "\v";
39
+ break;
40
+ case "0":
41
+ res += "\0";
42
+ break;
43
+ default:
44
+ res += `\\${esc}`;
45
+ break;
46
+ }
47
+ } else {
48
+ res += inner[i];
49
+ }
50
+ }
51
+ return res;
52
+ }
53
+
54
+ /**
55
+ * Parses a raw struct tag string into a Map of key -> unquoted string value,
56
+ * adhering to Go reflect.StructTag convention.
57
+ *
58
+ * @param {string | null | undefined} tag
59
+ * @returns {Map<string, string>}
60
+ */
61
+ export function parseStructTag(tag) {
62
+ const map = new Map();
63
+ if (!tag || typeof tag !== "string") return map;
64
+
65
+ let str = tag;
66
+ while (str.length > 0) {
67
+ // Skip leading spaces
68
+ let i = 0;
69
+ while (i < str.length && str.charCodeAt(i) === 32) i++;
70
+ str = str.slice(i);
71
+ if (str.length === 0) break;
72
+
73
+ // Scan to colon: key must not contain space, quote, colon, or control characters
74
+ i = 0;
75
+ while (
76
+ i < str.length &&
77
+ str.charCodeAt(i) > 32 &&
78
+ str[i] !== ":" &&
79
+ str[i] !== '"' &&
80
+ str.charCodeAt(i) !== 127
81
+ ) {
82
+ i++;
83
+ }
84
+ if (
85
+ i === 0 ||
86
+ i + 1 >= str.length ||
87
+ str[i] !== ":" ||
88
+ str[i + 1] !== '"'
89
+ ) {
90
+ break;
91
+ }
92
+ const key = str.slice(0, i);
93
+ str = str.slice(i + 1); // skip colon, now points to opening '"'
94
+
95
+ // Scan quoted string to find closing '"'
96
+ i = 1;
97
+ while (i < str.length && str[i] !== '"') {
98
+ if (str[i] === "\\") i++; // skip escaped char
99
+ i++;
100
+ }
101
+ if (i >= str.length) break;
102
+
103
+ const qval = str.slice(0, i + 1);
104
+ str = str.slice(i + 1);
105
+
106
+ if (!map.has(key)) {
107
+ map.set(key, unquoteTagValue(qval));
108
+ }
109
+ }
110
+ return map;
111
+ }
112
+
113
+ /**
114
+ * Parses the "json" key from a struct tag string or parsed Map according to encoding/json rules.
115
+ *
116
+ * @param {string | Map<string, string> | null | undefined} tag
117
+ * @returns {{ name: string | null, omitempty: boolean, ignored: boolean, options: string[] }}
118
+ */
119
+ export function parseJsonTag(tag) {
120
+ if (!tag) {
121
+ return { name: null, omitempty: false, ignored: false, options: [] };
122
+ }
123
+ const map = typeof tag === "string" ? parseStructTag(tag) : tag;
124
+ if (!map.has("json")) {
125
+ return { name: null, omitempty: false, ignored: false, options: [] };
126
+ }
127
+ const raw = map.get("json");
128
+ const parts = raw.split(",");
129
+ const rawName = parts[0];
130
+ const options = parts.slice(1);
131
+ const omitempty = options.includes("omitempty");
132
+
133
+ if (rawName === "-" && parts.length === 1) {
134
+ return { name: "-", omitempty: false, ignored: true, options };
135
+ }
136
+
137
+ const name = rawName === "" ? null : rawName;
138
+ return {
139
+ name,
140
+ omitempty,
141
+ ignored: false,
142
+ options,
143
+ };
144
+ }
@@ -175,24 +175,80 @@ 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);
180
- // Check if this is an embedded field (just a type name)
181
- // e.g., type T struct { Base }
182
- if (this.check(T.SEMICOLON) || this.check(T.RBRACE)) {
183
- const typeName = names[0]; // assuming no comma list for embedded
178
+ // Embedded pointer field: *Base or *pkg.Base
179
+ if (this.check(T.STAR)) {
180
+ this.advance();
181
+ let typeName = this.expect(T.IDENT).value;
182
+ if (this.check(T.DOT)) {
183
+ this.advance();
184
+ typeName += `.${this.expect(T.IDENT).value}`;
185
+ }
186
+ const type = {
187
+ kind: "PointerType",
188
+ base: { kind: "TypeName", name: typeName },
189
+ };
190
+ let tag = null;
191
+ if (this.check(T.STRING)) {
192
+ tag = this.advance().value;
193
+ }
194
+ fields.push({
195
+ names: [],
196
+ type,
197
+ embedded: true,
198
+ tag,
199
+ });
200
+ this.semi();
201
+ continue;
202
+ }
203
+
204
+ const names = this.parseIdentList();
205
+ // Check if this is a qualified embedded field: pkg.Base
206
+ if (names.length === 1 && this.check(T.DOT)) {
207
+ this.advance();
208
+ const typeName = `${names[0]}.${this.expect(T.IDENT).value}`;
209
+ let tag = null;
210
+ if (this.check(T.STRING)) {
211
+ tag = this.advance().value;
212
+ }
184
213
  fields.push({
185
214
  names: [],
186
215
  type: { kind: "TypeName", name: typeName },
187
216
  embedded: true,
217
+ tag,
188
218
  });
189
219
  this.semi();
190
220
  continue;
191
221
  }
222
+
223
+ // Embedded field (just a type name, e.g. Base), optionally tagged
224
+ if (
225
+ names.length === 1 &&
226
+ (this.check(T.SEMICOLON) ||
227
+ this.check(T.RBRACE) ||
228
+ this.check(T.STRING))
229
+ ) {
230
+ const typeName = names[0];
231
+ let tag = null;
232
+ if (this.check(T.STRING)) {
233
+ tag = this.advance().value;
234
+ }
235
+ fields.push({
236
+ names: [],
237
+ type: { kind: "TypeName", name: typeName },
238
+ embedded: true,
239
+ tag,
240
+ });
241
+ this.semi();
242
+ continue;
243
+ }
244
+
192
245
  const type = this.parseType();
193
246
  // Consume optional struct tag (backtick string), e.g. `json:"name"`
194
- if (this.check(T.STRING)) this.advance();
195
- fields.push({ names, type });
247
+ let tag = null;
248
+ if (this.check(T.STRING)) {
249
+ tag = this.advance().value;
250
+ }
251
+ fields.push({ names, type, tag });
196
252
  this.semi();
197
253
  }
198
254
  this.expect(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.