gofront 1.7.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/README.md +13 -13
  2. package/package.json +8 -37
  3. package/src/backend/js/expressions.js +1341 -148
  4. package/src/backend/js/fmt-runtime.js +573 -0
  5. package/src/backend/js/index.js +375 -57
  6. package/src/backend/js/runtime.js +1460 -133
  7. package/src/backend/js/statements.js +900 -86
  8. package/src/backend/js/stdlib/bits.js +87 -14
  9. package/src/backend/js/stdlib/builder.js +41 -6
  10. package/src/backend/js/stdlib/bytes.js +253 -7
  11. package/src/backend/js/stdlib/errors.js +2 -1
  12. package/src/backend/js/stdlib/fmt.js +349 -10
  13. package/src/backend/js/stdlib/index.js +8 -3
  14. package/src/backend/js/stdlib/json.js +247 -52
  15. package/src/backend/js/stdlib/maps.js +0 -8
  16. package/src/backend/js/stdlib/math.js +17 -1
  17. package/src/backend/js/stdlib/simd.js +10 -0
  18. package/src/backend/js/stdlib/slices.js +36 -6
  19. package/src/backend/js/stdlib/strconv.js +10 -5
  20. package/src/backend/js/stdlib/strings.js +83 -4
  21. package/src/backend/js/stdlib/testing.js +7 -0
  22. package/src/backend/js/stdlib/time.js +79 -55
  23. package/src/backend/js/stdlib/unicode.js +1 -1
  24. package/src/backend/js/stdlib/utf8.js +1 -1
  25. package/src/backend/js/templ.js +38 -76
  26. package/src/backend/js/type-info.js +272 -0
  27. package/src/backend/shared/fmt-types.js +188 -0
  28. package/src/backend/wasm/boundary.js +436 -80
  29. package/src/backend/wasm/emit-builtins.js +303 -13
  30. package/src/backend/wasm/emit-exprs.js +1397 -203
  31. package/src/backend/wasm/emit-maps.js +61 -13
  32. package/src/backend/wasm/emit-stmts.js +1359 -164
  33. package/src/backend/wasm/emit.js +512 -77
  34. package/src/backend/wasm/encode.js +43 -13
  35. package/src/backend/wasm/globals.js +452 -0
  36. package/src/backend/wasm/index.js +194 -2489
  37. package/src/backend/wasm/method-expr.js +381 -0
  38. package/src/backend/wasm/module-emitter.js +2240 -0
  39. package/src/backend/wasm/monomorph.js +62 -4
  40. package/src/backend/wasm/prescan.js +397 -0
  41. package/src/backend/wasm/stdlib/bytes.js +1804 -127
  42. package/src/backend/wasm/stdlib/core.js +1574 -199
  43. package/src/backend/wasm/stdlib/index.js +0 -5
  44. package/src/backend/wasm/stdlib/io.js +1567 -150
  45. package/src/backend/wasm/stdlib/packages.js +2 -0
  46. package/src/backend/wasm/stdlib/simd.js +293 -28
  47. package/src/backend/wasm/stdlib/time.js +1041 -12
  48. package/src/backend/wasm/types.js +200 -15
  49. package/src/backend/wasm/variadic.js +93 -0
  50. package/src/backend/wasm/wat.js +47 -20
  51. package/src/{cli-core.js → cli/cli-core.js} +236 -10
  52. package/src/{test-runner.js → cli/test-runner.js} +332 -39
  53. package/src/{vendor.js → cli/vendor.js} +3 -4
  54. package/src/compiler.js +107 -269
  55. package/src/index.js +15 -169
  56. package/src/{lexer.js → lexer/lexer.js} +102 -55
  57. package/src/{tokens.js → lexer/tokens.js} +1 -0
  58. package/src/link/sourcemap.js +97 -0
  59. package/src/link/stdlib.js +515 -0
  60. package/src/link/wasm.js +294 -0
  61. package/src/lower/bounds.js +394 -0
  62. package/src/lower/boxing.js +1 -1
  63. package/src/lower/captures.js +176 -32
  64. package/src/lower/index.js +26 -18
  65. package/src/lower/json-codecs.js +1398 -0
  66. package/src/lower/ownership.js +22 -1
  67. package/src/lower/range.js +86 -1
  68. package/src/lower/reachability.js +132 -0
  69. package/src/parser/declarations.js +58 -13
  70. package/src/parser/expressions.js +29 -7
  71. package/src/parser/index.js +2 -1
  72. package/src/parser/statements.js +37 -22
  73. package/src/{templ-parser.js → parser/templ.js} +138 -15
  74. package/src/parser/types.js +69 -9
  75. package/src/resolver.js +22 -1
  76. package/src/typechecker/assignability.js +176 -17
  77. package/src/typechecker/const-fold.js +356 -0
  78. package/src/typechecker/constants.js +256 -0
  79. package/src/typechecker/expressions.js +931 -122
  80. package/src/typechecker/index.js +459 -67
  81. package/src/typechecker/init-order.js +330 -0
  82. package/src/typechecker/resolve.js +451 -56
  83. package/src/typechecker/statements.js +90 -9
  84. package/src/typechecker/stdlib/core.js +333 -55
  85. package/src/typechecker/stdlib/excluded.js +898 -0
  86. package/src/typechecker/stdlib/extended.js +322 -26
  87. package/src/typechecker/{stdlib.js → stdlib/index.js} +5 -5
  88. package/src/typechecker/stdlib/simd.js +65 -15
  89. package/src/typechecker/termination.js +1 -1
  90. package/src/typechecker/types.js +149 -25
  91. package/stdlib/bytes/iter.go +192 -0
  92. package/stdlib/bytes/search.go +240 -0
  93. package/stdlib/bytes/split.go +218 -0
  94. package/stdlib/cmp/cmp.go +65 -0
  95. package/stdlib/encoding/json/errors.go +31 -0
  96. package/stdlib/encoding/json/indent.go +71 -0
  97. package/stdlib/encoding/json/number.go +12 -0
  98. package/stdlib/encoding/json/stream.go +173 -0
  99. package/stdlib/errors/overlay.go +12 -0
  100. package/stdlib/hash/hash.go +53 -0
  101. package/stdlib/internal/jsonrt/dec.go +738 -0
  102. package/stdlib/internal/jsonrt/enc.go +366 -0
  103. package/stdlib/internal/jsonrt/indent.go +135 -0
  104. package/stdlib/internal/jsonrt/number.go +27 -0
  105. package/stdlib/internal/jsonrt/reader.go +73 -0
  106. package/stdlib/internal/jsonrt/scanner.go +649 -0
  107. package/stdlib/io/io.go +346 -0
  108. package/stdlib/iter/iter.go +18 -0
  109. package/stdlib/maps/iter.go +65 -0
  110. package/stdlib/math/extra.go +270 -0
  111. package/stdlib/slices/extra.go +73 -0
  112. package/stdlib/slices/iter.go +113 -0
  113. package/stdlib/sort/sort.go +561 -0
  114. package/stdlib/strconv/quote.go +341 -0
  115. package/stdlib/strings/extra.go +35 -0
  116. package/stdlib/strings/iter.go +122 -0
  117. package/stdlib/sync/extra.go +105 -0
  118. package/stdlib/unicode/unicode.go +112 -0
  119. package/stdlib/unicode/utf8/bytes.go +239 -0
  120. package/src/backend/wasm/const-fold.js +0 -205
  121. package/src/backend/wasm/emit-stdlib.js +0 -13
  122. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  123. /package/src/{colors.js → cli/colors.js} +0 -0
  124. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  125. /package/src/{minifier.js → cli/minifier.js} +0 -0
  126. /package/src/{project-config.js → cli/project-config.js} +0 -0
  127. /package/src/{pwa.js → cli/pwa.js} +0 -0
  128. /package/src/{templ-lexer.js → lexer/templ-lexer.js} +0 -0
@@ -32,6 +32,22 @@ export function nodeWritesVar(node, name) {
32
32
  }
33
33
  }
34
34
 
35
+ // The variable whose address `node` takes without an explicit `&`, or null:
36
+ // a pointer-receiver method call or method value (`v.M()`, `v.M` with
37
+ // `func (*T) M`) takes `&v`, and slicing an array (`a[:]`) aliases it.
38
+ export function implicitAddrRoot(node) {
39
+ if (node.kind === "SelectorExpr" && node._type?._ptrRecv === true)
40
+ return rootIdentName(node.expr);
41
+ if (node.kind === "SliceExpr" && isArrayTyped(node.expr?._type))
42
+ return rootIdentName(node.expr);
43
+ return null;
44
+ }
45
+
46
+ function isArrayTyped(t) {
47
+ while (t?.kind === "named") t = t.underlying;
48
+ return t?.kind === "array";
49
+ }
50
+
35
51
  // True when `node` may modify (or take the address of) the value held by variable `name`.
36
52
  export function nodeMutatesVar(node, name, addrOnly = false) {
37
53
  if (!node || typeof node !== "object") return false;
@@ -43,11 +59,13 @@ export function nodeMutatesVar(node, name, addrOnly = false) {
43
59
  rootIdentName(node.operand) === name
44
60
  )
45
61
  return true;
62
+ if (addrOnly && implicitAddrRoot(node) === name) return true;
46
63
  if (!addrOnly && nodeWritesVar(node, name)) return true;
47
64
  return someChild(node, (child) => nodeMutatesVar(child, name, addrOnly));
48
65
  }
49
66
 
50
67
  // Single-pass collector of all mutated or address-taken variables in an AST node.
68
+ // With `addrOnly`, only address-taken ones, implicit address-taking included.
51
69
  export function collectMutatedVars(
52
70
  node,
53
71
  mutated = new Set(),
@@ -62,7 +80,10 @@ export function collectMutatedVars(
62
80
  if (node.kind === "UnaryExpr" && node.op === "&") {
63
81
  const root = rootIdentName(node.operand);
64
82
  if (root) mutated.add(root);
65
- } else if (!addrOnly) {
83
+ } else if (addrOnly) {
84
+ const root = implicitAddrRoot(node);
85
+ if (root) mutated.add(root);
86
+ } else {
66
87
  if (node.kind === "AssignStmt") {
67
88
  for (const e of node.lhs) {
68
89
  const root = rootIdentName(e);
@@ -1,9 +1,94 @@
1
1
  // src/lower/range.js
2
2
  // Classifies for-range loop shapes and variable scoping/redelcarations.
3
3
 
4
- import { isNumeric } from "../typechecker/types.js";
4
+ import { isNumeric, iteratorYieldParams } from "../typechecker/types.js";
5
5
  import { someChild } from "./walk.js";
6
6
 
7
+ let iterStateCounter = 0;
8
+
9
+ export function isIteratorRangeFor(stmt) {
10
+ if (stmt?.kind !== "ForStmt") return false;
11
+ const rangeExpr =
12
+ stmt.cond?.kind === "RangeExpr" ? stmt.cond : stmt.init?.rhs?.[0];
13
+ if (rangeExpr?.kind !== "RangeExpr") return false;
14
+ if (rangeExpr._isIterator) return true;
15
+ const iterType = rangeExpr.expr?._type;
16
+ return Boolean(iteratorYieldParams(iterType));
17
+ }
18
+
19
+ function buildYieldParamsAndAssignments(lhs, yieldTypes, isAssign) {
20
+ const params = [];
21
+ const assignStmts = [];
22
+ for (let i = 0; i < yieldTypes.length; i++) {
23
+ const yType = yieldTypes[i] ?? { kind: "basic", name: "any" };
24
+ const lhsElem = lhs[i];
25
+ if (!isAssign && lhsElem?.name && lhsElem.name !== "_") {
26
+ params.push({ name: lhsElem.name, type: yType });
27
+ } else {
28
+ const paramName = `__yield_p${i}`;
29
+ params.push({ name: paramName, type: yType });
30
+ if (isAssign && lhsElem && lhsElem.name !== "_") {
31
+ assignStmts.push({
32
+ kind: "AssignStmt",
33
+ lhs: [lhsElem],
34
+ rhs: [{ kind: "Ident", name: paramName, _type: yType }],
35
+ op: "=",
36
+ });
37
+ }
38
+ }
39
+ }
40
+ return { params, assignStmts };
41
+ }
42
+
43
+ export function getOrCreateIteratorFuncLit(stmt) {
44
+ if (stmt._yieldFuncLit) return stmt._yieldFuncLit;
45
+
46
+ const rangeExpr =
47
+ stmt.cond?.kind === "RangeExpr" ? stmt.cond : stmt.init?.rhs?.[0];
48
+ const iterType = rangeExpr?.expr?._type;
49
+ const info = iteratorYieldParams(iterType);
50
+ const yieldTypes = rangeExpr?._yieldParams ?? info?.yieldParams ?? [];
51
+
52
+ if (!stmt._iterStateName) {
53
+ stmt._iterStateName = `__iterState$${++iterStateCounter}`;
54
+ }
55
+
56
+ const isAssign = stmt.init?.kind === "AssignStmt";
57
+ const lhs = stmt.init?.lhs ?? [];
58
+ const { params, assignStmts } = buildYieldParamsAndAssignments(
59
+ lhs,
60
+ yieldTypes,
61
+ isAssign,
62
+ );
63
+
64
+ const rawStmts = stmt.body?.stmts ?? stmt.body?.list ?? [];
65
+ const bodyStmts =
66
+ assignStmts.length > 0 ? [...assignStmts, ...rawStmts] : rawStmts;
67
+
68
+ const funcLit = {
69
+ kind: "FuncLit",
70
+ params,
71
+ returnType: { kind: "basic", name: "bool" },
72
+ body: {
73
+ kind: "Block",
74
+ stmts: bodyStmts,
75
+ list: bodyStmts,
76
+ },
77
+ _type: {
78
+ kind: "func",
79
+ params: params.map((p) => p.type),
80
+ returns: [{ kind: "basic", name: "bool" }],
81
+ },
82
+ _isYieldClosure: true,
83
+ _iterStateName: stmt._iterStateName,
84
+ _forStmt: stmt,
85
+ _line: stmt._line,
86
+ };
87
+
88
+ stmt._yieldFuncLit = funcLit;
89
+ return funcLit;
90
+ }
91
+
7
92
  export function isRangeFor(stmt) {
8
93
  if (!stmt?.init) return false;
9
94
  const init = stmt.init;
@@ -0,0 +1,132 @@
1
+ // src/lower/reachability.js
2
+ // Reachability for Go-source stdlib packages: only declarations reachable
3
+ // from user code are emitted, so an imported but unused package costs zero
4
+ // bytes (JS output and dev-mode WASM, where Binaryen does not run).
5
+ //
6
+ // Stdlib top-level names are mangled (`Compare$$cmp`) and user references to
7
+ // them are plain identifiers after type-checking, so reachability is a
8
+ // closure over names. A method is reachable when its receiver type is and
9
+ // its name is selected somewhere reachable, or is called implicitly.
10
+
11
+ import { forEachChild } from "./walk.js";
12
+
13
+ // Methods the runtime or emitter-backed stdlib call without a selector in
14
+ // GoFront code (fmt verbs, errors.Is/As/Unwrap, encoding/json).
15
+ const IMPLICIT_METHODS = new Set([
16
+ "Error",
17
+ "String",
18
+ "GoString",
19
+ "Format",
20
+ "Unwrap",
21
+ "Is",
22
+ "As",
23
+ "MarshalJSON",
24
+ "UnmarshalJSON",
25
+ "MarshalText",
26
+ "UnmarshalText",
27
+ ]);
28
+
29
+ function declNames(d) {
30
+ if (d.kind === "FuncDecl" || d.kind === "TypeDecl") return [d.name];
31
+ if (d.kind === "VarDecl" || d.kind === "ConstDecl")
32
+ return (d.decls ?? []).flatMap((spec) => spec.names ?? []);
33
+ return [];
34
+ }
35
+
36
+ // Names (identifiers and type names) and selected fields used under `node`.
37
+ function collectRefs(node, names, fields) {
38
+ if (!node || typeof node !== "object") return;
39
+ if (Array.isArray(node)) {
40
+ for (const n of node) collectRefs(n, names, fields);
41
+ return;
42
+ }
43
+ if (node._stdName) names.add(node._stdName); // `cmp.Compare` in user code
44
+ if (
45
+ node.kind === "Ident" ||
46
+ node.kind === "TypeName" ||
47
+ node.kind === "GenericTypeName"
48
+ )
49
+ names.add(node.name);
50
+ else if (node.kind === "SelectorExpr") fields.add(node.field);
51
+ forEachChild(node, (child) => collectRefs(child, names, fields));
52
+ }
53
+
54
+ function indexStdlib(stdlibPrograms) {
55
+ const byName = new Map(); // name -> decls
56
+ const methods = []; // { decl, recv }
57
+ const inits = new Map(); // program -> init decls
58
+ for (const p of stdlibPrograms) {
59
+ for (const d of p.decls ?? []) {
60
+ if (d.kind === "MethodDecl")
61
+ methods.push({ decl: d, recv: d.recvType?.name });
62
+ else if (d.kind === "FuncDecl" && d.name === "init")
63
+ inits.set(p, [...(inits.get(p) ?? []), d]);
64
+ for (const n of declNames(d)) {
65
+ if (!byName.has(n)) byName.set(n, []);
66
+ byName.get(n).push(d);
67
+ }
68
+ }
69
+ }
70
+ return { byName, methods, inits };
71
+ }
72
+
73
+ const JSON_NAMES = [
74
+ "SyntaxError$$internal$jsonrt",
75
+ "UnmarshalTypeError$$internal$jsonrt",
76
+ "InvalidUnmarshalError$$internal$jsonrt",
77
+ "UnsupportedValueError$$internal$jsonrt",
78
+ "UnsupportedTypeError$$internal$jsonrt",
79
+ "MarshalerError$$internal$jsonrt",
80
+ "CheckValid$$internal$jsonrt",
81
+ "Number$$internal$jsonrt",
82
+ "OptEscapeHTML$$internal$jsonrt",
83
+ "OptUseNumber$$internal$jsonrt",
84
+ "OptDisallowUnknownFields$$internal$jsonrt",
85
+ ];
86
+
87
+ function addExtraRoots(names) {
88
+ if (names.has("strconv")) names.add("NumError$$strconv");
89
+ if (names.has("json")) {
90
+ for (const n of JSON_NAMES) names.add(n);
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Returns the set of declarations of `stdlibPrograms` reachable from
96
+ * `rootPrograms` (all user code that is emitted). A program's `init` is
97
+ * kept when anything else in that program is reached.
98
+ */
99
+ export function reachableStdlibDecls(rootPrograms, stdlibPrograms) {
100
+ const { byName, methods, inits } = indexStdlib(stdlibPrograms);
101
+ const reached = new Set();
102
+ const names = new Set();
103
+ const fields = new Set(IMPLICIT_METHODS);
104
+ collectRefs(rootPrograms, names, fields);
105
+ addExtraRoots(names);
106
+
107
+ const pending = [...names];
108
+ const reach = (d) => {
109
+ if (reached.has(d)) return;
110
+ reached.add(d);
111
+ const local = new Set();
112
+ collectRefs(d, local, fields);
113
+ for (const n of local) {
114
+ if (names.has(n)) continue;
115
+ names.add(n);
116
+ pending.push(n);
117
+ }
118
+ };
119
+ let changed = true;
120
+ while (changed) {
121
+ while (pending.length > 0)
122
+ for (const d of byName.get(pending.pop()) ?? []) reach(d);
123
+ const size = reached.size;
124
+ for (const m of methods)
125
+ if (names.has(m.recv) && fields.has(m.decl.name)) reach(m.decl);
126
+ for (const p of stdlibPrograms)
127
+ if (p.decls.some((d) => reached.has(d)))
128
+ for (const d of inits.get(p) ?? []) reach(d);
129
+ changed = reached.size !== size || pending.length > 0;
130
+ }
131
+ return reached;
132
+ }
@@ -1,4 +1,4 @@
1
- import { T } from "../tokens.js";
1
+ import { T } from "../lexer/tokens.js";
2
2
 
3
3
  export const declarationParseMethods = {
4
4
  parseTopDecl() {
@@ -56,7 +56,11 @@ export const declarationParseMethods = {
56
56
  typeParams = this.parseTypeParamList();
57
57
  }
58
58
  const { params, returnType } = this.parseSignature();
59
- const body = this.parseBlock();
59
+ // `//gofront:intrinsic name` on the line before a body-less func: the
60
+ // backend provides the implementation; every pass sees an empty body.
61
+ const intrinsic = this.tokens?.intrinsics?.get(_line - 1) ?? null;
62
+ const bodyless = intrinsic !== null && !this.check(T.LBRACE);
63
+ const body = bodyless ? { kind: "Block", stmts: [] } : this.parseBlock();
60
64
  this.semi();
61
65
  return {
62
66
  kind: "FuncDecl",
@@ -65,6 +69,7 @@ export const declarationParseMethods = {
65
69
  params,
66
70
  returnType,
67
71
  body,
72
+ ...(intrinsic && { intrinsic, _bodyless: bodyless }),
68
73
  _line,
69
74
  };
70
75
  },
@@ -91,17 +96,40 @@ export const declarationParseMethods = {
91
96
  // Type param lists contain IDENT followed by a constraint (another IDENT or keyword),
92
97
  // not expressions like integers or operators.
93
98
  _looksLikeTypeParamList() {
94
- const i = this.pos + 1; // skip [
99
+ let i = this.pos + 1; // skip [
95
100
  const src = this.tokens;
96
- // First token must be an IDENT (the type param name)
97
101
  if (!src[i] || src[i].type !== T.IDENT) return false;
98
- // Second token should be a constraint (IDENT like 'any', 'comparable', another type name)
99
- // or TILDE for union constraints
100
- const second = src[i + 1];
101
- if (!second) return false;
102
- if (second.type === T.IDENT) return true; // T any, T Stringer
103
- if (second.type === T.TILDE) return true; // T ~int
104
- if (second.type === T.INTERFACE) return true; // T interface{...}
102
+ while (
103
+ src[i] &&
104
+ src[i].type === T.IDENT &&
105
+ src[i + 1] &&
106
+ src[i + 1].type === T.COMMA
107
+ ) {
108
+ i += 2;
109
+ }
110
+ if (!src[i] || src[i].type !== T.IDENT) return false;
111
+ const afterName = src[i + 1];
112
+ if (!afterName) return false;
113
+ if (afterName.type === T.IDENT) return true; // T any, T Stringer
114
+ if (afterName.type === T.TILDE) return true; // T ~int
115
+ if (afterName.type === T.INTERFACE) return true; // T interface{...}
116
+ return false;
117
+ },
118
+
119
+ // `Name[…]` followed by `,` or `)` in a result list is an instantiated
120
+ // generic type; a result name is followed by its type (`a []int`).
121
+ _isGenericResultType() {
122
+ if (this.peek2()?.type !== T.LBRACKET) return false;
123
+ const src = this.tokens;
124
+ let depth = 0;
125
+ for (let i = this.pos + 1; i < src.length; i++) {
126
+ const t = src[i].type;
127
+ if (t === T.LBRACKET) depth++;
128
+ else if (t === T.RBRACKET && --depth === 0) {
129
+ const next = src[i + 1]?.type;
130
+ return next === T.COMMA || next === T.RPAREN;
131
+ } else if (t === T.EOF || t === T.LBRACE) return false;
132
+ }
105
133
  return false;
106
134
  },
107
135
 
@@ -195,7 +223,12 @@ export const declarationParseMethods = {
195
223
  let isNamed = false;
196
224
  do {
197
225
  // Named returns: only treat IDENT as a name if peek2 is an actual type token
198
- if (this.check(T.IDENT) && this.looksLikeType(this.peek2())) {
226
+ // (`Box[int]` is an instantiated type, not a name followed by `[int]`).
227
+ if (
228
+ this.check(T.IDENT) &&
229
+ this.looksLikeType(this.peek2()) &&
230
+ !this._isGenericResultType()
231
+ ) {
199
232
  names.push(this.advance().value);
200
233
  isNamed = true;
201
234
  } else {
@@ -204,6 +237,17 @@ export const declarationParseMethods = {
204
237
  types.push(this.parseType());
205
238
  } while (this.match(T.COMMA));
206
239
  this.expect(T.RPAREN);
240
+ // Grouped names `(r, theta float64)`: a bare identifier before a
241
+ // named entry is a name sharing that entry's type.
242
+ if (isNamed) {
243
+ for (let i = types.length - 2; i >= 0; i--) {
244
+ if (names[i] !== null) continue;
245
+ if (types[i]?.kind !== "TypeName" || types[i].typeArgs?.length)
246
+ continue;
247
+ names[i] = types[i].name;
248
+ types[i] = { ...types[i + 1] };
249
+ }
250
+ }
207
251
  const typeNode =
208
252
  types.length === 1 ? types[0] : { kind: "TupleType", types };
209
253
  // Attach named return info so typechecker can inject them into scope
@@ -261,6 +305,7 @@ export const declarationParseMethods = {
261
305
  },
262
306
 
263
307
  parseVarSpec() {
308
+ const at = this.peek();
264
309
  const names = this.parseIdentList();
265
310
  let type = null;
266
311
  let value = null;
@@ -272,7 +317,7 @@ export const declarationParseMethods = {
272
317
  type = this.parseType();
273
318
  }
274
319
  if (this.match(T.ASSIGN)) value = this.parseExprList();
275
- return { names, type, value };
320
+ return { names, type, value, _line: at.line, _col: at.col };
276
321
  },
277
322
 
278
323
  parseConstDecl() {
@@ -1,6 +1,6 @@
1
1
  // Parser expression methods — installed as a mixin on Parser.prototype.
2
2
 
3
- import { T } from "../tokens.js";
3
+ import { T } from "../lexer/tokens.js";
4
4
 
5
5
  // Binary operator precedence (higher = binds tighter, matching Go spec)
6
6
  const PREC = {
@@ -108,9 +108,9 @@ function _isTypeIntroducerToken(tt) {
108
108
  }
109
109
 
110
110
  export const expressionParserMethods = {
111
- parseExprList() {
112
- const exprs = [this.parseExpr()];
113
- while (this.match(T.COMMA)) exprs.push(this.parseExpr());
111
+ parseExprList(noCompLit = false) {
112
+ const exprs = [this.parseExpr(0, noCompLit)];
113
+ while (this.match(T.COMMA)) exprs.push(this.parseExpr(0, noCompLit));
114
114
  return exprs;
115
115
  },
116
116
 
@@ -132,7 +132,13 @@ export const expressionParserMethods = {
132
132
  const t = this.peek();
133
133
  if (UNARY_OPS.has(t.type)) {
134
134
  const op = this.advance().value;
135
- return { kind: "UnaryExpr", op, operand: this.parseUnary(noCompLit) };
135
+ return {
136
+ kind: "UnaryExpr",
137
+ op,
138
+ operand: this.parseUnary(noCompLit),
139
+ _line: t.line,
140
+ _col: t.col,
141
+ };
136
142
  }
137
143
  if (t.type === T.AWAIT) {
138
144
  this.advance();
@@ -166,7 +172,12 @@ export const expressionParserMethods = {
166
172
  }
167
173
  } else if (t.type === T.LBRACKET) {
168
174
  // Check if this looks like generic type args: Ident[Type](...) or Ident[Type]{...}
169
- if (expr.kind === "Ident" && this.looksLikeTypeArgList()) {
175
+ // `pkg.F[int](x)` / `pkg.T[X]{...}` instantiate through a qualifier.
176
+ if (
177
+ (expr.kind === "Ident" ||
178
+ (expr.kind === "SelectorExpr" && expr.expr.kind === "Ident")) &&
179
+ this.looksLikeTypeArgList()
180
+ ) {
170
181
  this.advance(); // consume [
171
182
  const typeArgs = [this.parseType()];
172
183
  while (this.match(T.COMMA)) typeArgs.push(this.parseType());
@@ -241,6 +252,8 @@ export const expressionParserMethods = {
241
252
  if (tt === T.RBRACKET) {
242
253
  depth--;
243
254
  if (depth === 0) break;
255
+ // `a[i].F` selects from an index result: a value, never a type.
256
+ if (src[i + 1]?.type === T.DOT) return false;
244
257
  i++;
245
258
  continue;
246
259
  }
@@ -369,7 +382,16 @@ export const expressionParserMethods = {
369
382
  while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
370
383
  // Implicit composite literal: {X: 1} inside []Point{{X:1}} or map[string]Point{"a": {X:1}}
371
384
  if (this.check(T.LBRACE)) {
372
- elems.push(this.parseCompositeLit(null)); // null typeExpr = inferred
385
+ const lit = this.parseCompositeLit(null); // null typeExpr = inferred
386
+ if (this.match(T.COLON)) {
387
+ // Elided key type: map[P]int{{1}: 2}
388
+ const val = this.check(T.LBRACE)
389
+ ? this.parseCompositeLit(null)
390
+ : this.parseExpr();
391
+ elems.push({ kind: "KeyValueExpr", key: lit, value: val });
392
+ } else {
393
+ elems.push(lit);
394
+ }
373
395
  } else {
374
396
  const first = this.parseExpr();
375
397
  if (this.match(T.COLON)) {
@@ -17,7 +17,7 @@
17
17
  // CallExpr, SelectorExpr, IndexExpr, SliceExpr
18
18
  // CompositeLit, KeyValueExpr, FuncLit, AwaitExpr
19
19
 
20
- import { T } from "../tokens.js";
20
+ import { T } from "../lexer/tokens.js";
21
21
  import { declarationParseMethods } from "./declarations.js";
22
22
  import { expressionParserMethods } from "./expressions.js";
23
23
  import { statementParserMethods } from "./statements.js";
@@ -131,6 +131,7 @@ export class Parser {
131
131
  decls,
132
132
  target: this.tokens?.target ?? null,
133
133
  _targetDirective: this.tokens?.targetDirective ?? null,
134
+ _embedDirectives: this.tokens?.embedDirectives ?? [],
134
135
  _filename: this.filename,
135
136
  _source: this.source,
136
137
  };
@@ -1,6 +1,6 @@
1
1
  // Parser statement methods — installed as a mixin on Parser.prototype.
2
2
 
3
- import { T } from "../tokens.js";
3
+ import { T } from "../lexer/tokens.js";
4
4
 
5
5
  // Method-name dispatch for pure-delegation cases in _parseStmt.
6
6
  const PARSE_STMT_DELEGATE = {
@@ -78,6 +78,13 @@ export const statementParserMethods = {
78
78
  this.advance();
79
79
  this.semi();
80
80
  return { kind: "BranchStmt", keyword: "fallthrough" };
81
+ case T.GOTO: {
82
+ // Parsed so the checker can reject it with a targeted message.
83
+ this.advance();
84
+ const label = this.expect(T.IDENT).value;
85
+ this.semi();
86
+ return { kind: "BranchStmt", keyword: "goto", label };
87
+ }
81
88
  case T.GO:
82
89
  return this.err("goroutines are not supported in GoFront");
83
90
  case T.SELECT:
@@ -88,7 +95,12 @@ export const statementParserMethods = {
88
95
  if (this.check2(T.COLON)) {
89
96
  const label = this.advance().value;
90
97
  this.advance(); // consume COLON
91
- return { kind: "LabeledStmt", label, body: this.parseStmt() };
98
+ // A label may precede the end of a block: the statement is empty.
99
+ const body =
100
+ this.check(T.RBRACE) || this.check(T.CASE) || this.check(T.DEFAULT)
101
+ ? { kind: "Block", stmts: [] }
102
+ : this.parseStmt();
103
+ return { kind: "LabeledStmt", label, body };
92
104
  }
93
105
  return this.parseSimpleStmt();
94
106
  default:
@@ -150,27 +162,30 @@ export const statementParserMethods = {
150
162
  };
151
163
  }
152
164
 
153
- const first = this.parseSimpleStmtRaw(true);
154
-
155
- if (this.check(T.LBRACE)) {
156
- // for k, v := range expr { } — range stmt ends right before {
157
- if (this.isRangeStmt(first)) {
165
+ let first = null;
166
+ if (!this.check(T.SEMICOLON)) {
167
+ first = this.parseSimpleStmtRaw(true);
168
+
169
+ if (this.check(T.LBRACE)) {
170
+ // for k, v := range expr { } — range stmt ends right before {
171
+ if (this.isRangeStmt(first)) {
172
+ return {
173
+ kind: "ForStmt",
174
+ init: first,
175
+ cond: null,
176
+ post: null,
177
+ body: this.parseBlock(),
178
+ };
179
+ }
180
+ // for cond { }
158
181
  return {
159
182
  kind: "ForStmt",
160
- init: first,
161
- cond: null,
183
+ init: null,
184
+ cond: this.stmtToExpr(first),
162
185
  post: null,
163
186
  body: this.parseBlock(),
164
187
  };
165
188
  }
166
- // for cond { }
167
- return {
168
- kind: "ForStmt",
169
- init: null,
170
- cond: this.stmtToExpr(first),
171
- post: null,
172
- body: this.parseBlock(),
173
- };
174
189
  }
175
190
 
176
191
  this.expect(T.SEMICOLON);
@@ -180,7 +195,7 @@ export const statementParserMethods = {
180
195
  if (!this.check(T.SEMICOLON)) cond = this.parseExpr(0, true);
181
196
  this.expect(T.SEMICOLON);
182
197
  let post = null;
183
- if (!this.check(T.LBRACE)) post = this.parseSimpleStmtRaw();
198
+ if (!this.check(T.LBRACE)) post = this.parseSimpleStmtRaw(true);
184
199
  const body = this.parseBlock();
185
200
  return { kind: "ForStmt", init: first, cond, post, body };
186
201
  },
@@ -290,9 +305,9 @@ export const statementParserMethods = {
290
305
  // a, b, c := ... or x, y = ...
291
306
  if (t.type === T.COMMA) {
292
307
  const lhs = [first];
293
- while (this.match(T.COMMA)) lhs.push(this.parseExpr());
308
+ while (this.match(T.COMMA)) lhs.push(this.parseExpr(0, noCompLit));
294
309
  const op = this.advance(); // := or =
295
- const rhs = this.parseExprList();
310
+ const rhs = this.parseExprList(noCompLit);
296
311
  if (op.type === T.DEFINE) return { kind: "DefineStmt", lhs, rhs };
297
312
  return { kind: "AssignStmt", lhs, op: "=", rhs };
298
313
  }
@@ -300,14 +315,14 @@ export const statementParserMethods = {
300
315
  // x := expr
301
316
  if (t.type === T.DEFINE) {
302
317
  this.advance();
303
- const rhs = this.parseExprList();
318
+ const rhs = this.parseExprList(noCompLit);
304
319
  return { kind: "DefineStmt", lhs: [first], rhs };
305
320
  }
306
321
 
307
322
  // x = expr or x += expr etc.
308
323
  if (ASSIGN_OPS.has(t.type)) {
309
324
  const op = this.advance().value;
310
- const rhs = this.parseExprList();
325
+ const rhs = this.parseExprList(noCompLit);
311
326
  return { kind: "AssignStmt", lhs: [first], op, rhs };
312
327
  }
313
328