gofront 1.0.0 → 1.1.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 (41) hide show
  1. package/README.md +36 -3
  2. package/package.json +7 -7
  3. package/src/asset-manager.js +125 -0
  4. package/src/cli-core.js +74 -0
  5. package/src/codegen/expressions.js +3 -0
  6. package/src/codegen/statements.js +3 -0
  7. package/src/codegen/stdlib/builder.js +3 -0
  8. package/src/codegen/stdlib/bytes.js +3 -0
  9. package/src/codegen/stdlib/errors.js +3 -0
  10. package/src/codegen/stdlib/fmt.js +3 -0
  11. package/src/codegen/stdlib/gom.js +3 -0
  12. package/src/codegen/stdlib/html.js +3 -0
  13. package/src/codegen/stdlib/index.js +3 -0
  14. package/src/codegen/stdlib/io.js +3 -0
  15. package/src/codegen/stdlib/maps.js +3 -0
  16. package/src/codegen/stdlib/math.js +3 -0
  17. package/src/codegen/stdlib/os.js +3 -0
  18. package/src/codegen/stdlib/path.js +3 -0
  19. package/src/codegen/stdlib/rand.js +3 -0
  20. package/src/codegen/stdlib/regexp.js +3 -0
  21. package/src/codegen/stdlib/slices.js +3 -0
  22. package/src/codegen/stdlib/sort.js +3 -0
  23. package/src/codegen/stdlib/strconv.js +3 -0
  24. package/src/codegen/stdlib/strings.js +3 -0
  25. package/src/codegen/stdlib/time.js +3 -0
  26. package/src/codegen/stdlib/unicode.js +3 -0
  27. package/src/codegen/stdlib/utf8.js +3 -0
  28. package/src/codegen/templ.js +3 -0
  29. package/src/compiler.js +63 -4
  30. package/src/dev-server.js +96 -33
  31. package/src/index.js +80 -123
  32. package/src/parser/declarations.js +347 -0
  33. package/src/parser/index.js +3 -346
  34. package/src/typechecker/assignability.js +3 -0
  35. package/src/typechecker/expressions.js +9 -0
  36. package/src/typechecker/index.js +2 -1
  37. package/src/typechecker/resolve.js +9 -2
  38. package/src/typechecker/statements.js +3 -0
  39. package/src/typechecker/termination.js +3 -0
  40. package/src/typechecker/types.js +1 -0
  41. package/src/vendor.js +228 -0
@@ -0,0 +1,347 @@
1
+ import { T } from "../tokens.js";
2
+
3
+ export const declarationParseMethods = {
4
+ parseTopDecl() {
5
+ const t = this.peek();
6
+ switch (t.type) {
7
+ case T.ASYNC:
8
+ return this.parseAsyncFuncOrMethod();
9
+ case T.FUNC:
10
+ return this.parseFuncOrMethod();
11
+ case T.TYPE:
12
+ return this.parseTypeDecl();
13
+ case T.VAR:
14
+ return this.parseVarDecl();
15
+ case T.CONST:
16
+ return this.parseConstDecl();
17
+ default:
18
+ this.err("Expected top-level declaration");
19
+ }
20
+ },
21
+
22
+ parseFuncOrMethod() {
23
+ const _line = this.peek().line;
24
+ this.expect(T.FUNC);
25
+ // method: func (recv Type) Name(...)
26
+ if (this.check(T.LPAREN)) {
27
+ this.expect(T.LPAREN);
28
+ const recvName =
29
+ this.check(T.IDENT) && !this.isReceiverTerminator(this.peek2())
30
+ ? this.advance().value
31
+ : "_";
32
+ this.match(T.STAR); // pointer receiver: (c *Counter) — strip *, treat same as value receiver
33
+ const recvType = this.parseTypeName();
34
+ this.expect(T.RPAREN);
35
+ const name = this.expect(T.IDENT).value;
36
+ const { params, returnType } = this.parseSignature();
37
+ const body = this.parseBlock();
38
+ this.semi();
39
+ return {
40
+ kind: "MethodDecl",
41
+ recvName,
42
+ recvType,
43
+ name,
44
+ params,
45
+ returnType,
46
+ body,
47
+ _line,
48
+ };
49
+ }
50
+ // plain function
51
+ const name = this.expect(T.IDENT).value;
52
+ // generic function: func Name[T any, U Stringer](...) ...
53
+ let typeParams = null;
54
+ if (this.check(T.LBRACKET) && this._looksLikeTypeParamList()) {
55
+ typeParams = this.parseTypeParamList();
56
+ }
57
+ const { params, returnType } = this.parseSignature();
58
+ const body = this.parseBlock();
59
+ this.semi();
60
+ return {
61
+ kind: "FuncDecl",
62
+ name,
63
+ typeParams,
64
+ params,
65
+ returnType,
66
+ body,
67
+ _line,
68
+ };
69
+ },
70
+
71
+ parseAsyncFuncOrMethod() {
72
+ this.expect(T.ASYNC);
73
+ const decl = this.parseFuncOrMethod();
74
+ decl.async = true;
75
+ return decl;
76
+ },
77
+
78
+ // Returns true if tok is a receiver-list terminator (i.e. not a type name)
79
+ isReceiverTerminator(tok) {
80
+ return (
81
+ tok &&
82
+ (tok.type === T.RPAREN ||
83
+ tok.type === T.COMMA ||
84
+ tok.type === T.SEMICOLON ||
85
+ tok.type === T.EOF)
86
+ );
87
+ },
88
+
89
+ // Lookahead: does the [ ... ] after a func name or type name look like a type param list?
90
+ // Type param lists contain IDENT followed by a constraint (another IDENT or keyword),
91
+ // not expressions like integers or operators.
92
+ _looksLikeTypeParamList() {
93
+ const i = this.pos + 1; // skip [
94
+ const src = this.tokens;
95
+ // First token must be an IDENT (the type param name)
96
+ if (!src[i] || src[i].type !== T.IDENT) return false;
97
+ // Second token should be a constraint (IDENT like 'any', 'comparable', another type name)
98
+ // or TILDE for union constraints
99
+ const second = src[i + 1];
100
+ if (!second) return false;
101
+ if (second.type === T.IDENT) return true; // T any, T Stringer
102
+ if (second.type === T.TILDE) return true; // T ~int
103
+ if (second.type === T.INTERFACE) return true; // T interface{...}
104
+ return false;
105
+ },
106
+
107
+ parseSignature() {
108
+ const params = this.parseParamList();
109
+ const returnType = this.parseReturnType();
110
+ return { params, returnType };
111
+ },
112
+
113
+ // Parses (a, b int, c string) style param list
114
+ parseParamList() {
115
+ this.expect(T.LPAREN);
116
+ const params = [];
117
+ if (!this.check(T.RPAREN)) {
118
+ do {
119
+ if (this.check(T.RPAREN)) break;
120
+ // Collect names, then type
121
+ const names = [];
122
+ if (this.check(T.IDENT) && this._isNamedParam()) {
123
+ // named: a, b int or ns ...int
124
+ names.push(this.advance().value);
125
+ while (
126
+ this.match(T.COMMA) &&
127
+ this.check(T.IDENT) &&
128
+ this._isNamedParam()
129
+ ) {
130
+ names.push(this.advance().value);
131
+ }
132
+ // variadic: name ...T
133
+ const variadic = this.match(T.ELLIPSIS);
134
+ const type = this.parseType();
135
+ for (const n of names) params.push({ name: n, type, variadic });
136
+ } else {
137
+ // unnamed: just a type (possibly variadic: ...T)
138
+ const variadic = this.match(T.ELLIPSIS);
139
+ const type = this.parseType();
140
+ params.push({ name: "_", type, variadic });
141
+ }
142
+ } while (this.match(T.COMMA));
143
+ }
144
+ this.expect(T.RPAREN);
145
+ return params;
146
+ },
147
+
148
+ // Lookahead: determine if current IDENT starts a named param (name type)
149
+ // vs an unnamed param (just a type). Scans IDENT (COMMA IDENT)* and checks
150
+ // if a type-start token follows — if so, the IDENTs are names; if ) or ,
151
+ // follows, the IDENTs are themselves types.
152
+ _isNamedParam() {
153
+ let pos = this.pos + 1; // skip current IDENT
154
+ while (
155
+ this.tokens[pos]?.type === T.COMMA &&
156
+ this.tokens[pos + 1]?.type === T.IDENT
157
+ ) {
158
+ pos += 2;
159
+ }
160
+ const next = this.tokens[pos];
161
+ if (!next) return false;
162
+ return this.looksLikeType(next) || next.type === T.ELLIPSIS;
163
+ },
164
+
165
+ looksLikeType(tok) {
166
+ return (
167
+ tok.type === T.LBRACKET ||
168
+ tok.type === T.STAR ||
169
+ tok.type === T.MAP ||
170
+ tok.type === T.FUNC ||
171
+ tok.type === T.INTERFACE ||
172
+ tok.type === T.STRUCT ||
173
+ (tok.type === T.IDENT && tok.value !== "_")
174
+ );
175
+ },
176
+
177
+ parseReturnType(inTypeExpr = false) {
178
+ // No return type
179
+ if (
180
+ this.check(T.LBRACE) ||
181
+ this.check(T.RBRACE) ||
182
+ this.check(T.SEMICOLON) ||
183
+ this.check(T.EOF)
184
+ )
185
+ return null;
186
+ // Inside a type expression (e.g. func type in a tuple), `,` and `)` also end the type
187
+ if (inTypeExpr && (this.check(T.COMMA) || this.check(T.RPAREN)))
188
+ return null;
189
+ // Multiple return values: (int, string) or named (result int, ok bool)
190
+ if (this.check(T.LPAREN)) {
191
+ this.advance();
192
+ const types = [];
193
+ const names = []; // non-null if named returns
194
+ let isNamed = false;
195
+ do {
196
+ // Named returns: only treat IDENT as a name if peek2 is an actual type token
197
+ if (this.check(T.IDENT) && this.looksLikeType(this.peek2())) {
198
+ names.push(this.advance().value);
199
+ isNamed = true;
200
+ } else {
201
+ names.push(null);
202
+ }
203
+ types.push(this.parseType());
204
+ } while (this.match(T.COMMA));
205
+ this.expect(T.RPAREN);
206
+ const typeNode =
207
+ types.length === 1 ? types[0] : { kind: "TupleType", types };
208
+ // Attach named return info so typechecker can inject them into scope
209
+ if (isNamed)
210
+ typeNode._namedReturns = names.map((n, i) => ({
211
+ name: n,
212
+ type: types[i],
213
+ }));
214
+ return typeNode;
215
+ }
216
+ return this.parseType();
217
+ },
218
+
219
+ parseTypeDecl() {
220
+ this.expect(T.TYPE);
221
+ if (this.match(T.LPAREN)) {
222
+ const decls = [];
223
+ while (!this.check(T.RPAREN) && !this.check(T.EOF)) {
224
+ const name = this.expect(T.IDENT).value;
225
+ const isAlias = this.match(T.ASSIGN);
226
+ const type = this.parseType();
227
+ decls.push({ kind: "TypeDecl", name, type, isAlias });
228
+ this.semi();
229
+ }
230
+ this.expect(T.RPAREN);
231
+ this.semi();
232
+ return decls;
233
+ }
234
+ const name = this.expect(T.IDENT).value;
235
+ // generic type: type Name[T any] struct { ... }
236
+ let typeParams = null;
237
+ if (this.check(T.LBRACKET) && this._looksLikeTypeParamList()) {
238
+ typeParams = this.parseTypeParamList();
239
+ }
240
+ const isAlias = this.match(T.ASSIGN);
241
+ const type = this.parseType();
242
+ this.semi();
243
+ return { kind: "TypeDecl", name, typeParams, type, isAlias };
244
+ },
245
+
246
+ parseVarDecl() {
247
+ this.expect(T.VAR);
248
+ const decls = [];
249
+ if (this.match(T.LPAREN)) {
250
+ while (!this.check(T.RPAREN) && !this.check(T.EOF)) {
251
+ decls.push(this.parseVarSpec());
252
+ this.semi();
253
+ }
254
+ this.expect(T.RPAREN);
255
+ } else {
256
+ decls.push(this.parseVarSpec());
257
+ }
258
+ this.semi();
259
+ return { kind: "VarDecl", decls };
260
+ },
261
+
262
+ parseVarSpec() {
263
+ const names = [this.expect(T.IDENT).value];
264
+ while (this.match(T.COMMA)) names.push(this.expect(T.IDENT).value);
265
+ let type = null;
266
+ let value = null;
267
+ if (
268
+ !this.check(T.ASSIGN) &&
269
+ !this.check(T.SEMICOLON) &&
270
+ !this.check(T.RPAREN)
271
+ ) {
272
+ type = this.parseType();
273
+ }
274
+ if (this.match(T.ASSIGN)) value = this.parseExprList();
275
+ return { names, type, value };
276
+ },
277
+
278
+ parseConstDecl() {
279
+ this.expect(T.CONST);
280
+ const decls = [];
281
+ if (this.match(T.LPAREN)) {
282
+ let iotaVal = 0;
283
+ let prevRawExprs = null; // raw AST (with iota Idents) from the previous spec
284
+ let prevType = null;
285
+ while (!this.check(T.RPAREN) && !this.check(T.EOF)) {
286
+ const names = [this.expect(T.IDENT).value];
287
+ while (this.match(T.COMMA)) names.push(this.expect(T.IDENT).value);
288
+ let type = null;
289
+ let rawExprs; // before iota substitution
290
+ if (this.check(T.ASSIGN)) {
291
+ this.advance();
292
+ rawExprs = this.parseExprList();
293
+ prevRawExprs = rawExprs;
294
+ prevType = null;
295
+ } else if (!this.check(T.SEMICOLON) && !this.check(T.RPAREN)) {
296
+ // optional type annotation before =
297
+ type = this.parseType();
298
+ prevType = type;
299
+ this.expect(T.ASSIGN);
300
+ rawExprs = this.parseExprList();
301
+ prevRawExprs = rawExprs;
302
+ } else {
303
+ // Omitted expression — repeat previous expression with updated iota
304
+ rawExprs = prevRawExprs ?? [
305
+ { kind: "BasicLit", litKind: "INT", value: String(iotaVal) },
306
+ ];
307
+ type = prevType ?? null;
308
+ }
309
+ // Substitute iota deeply in a cloned expression AST
310
+ const value = rawExprs.map((v) => this._substituteIota(v, iotaVal));
311
+ decls.push({ names, type, value });
312
+ iotaVal++;
313
+ this.semi();
314
+ }
315
+ this.expect(T.RPAREN);
316
+ } else {
317
+ const names = [this.expect(T.IDENT).value];
318
+ while (this.match(T.COMMA)) names.push(this.expect(T.IDENT).value);
319
+ let type = null;
320
+ if (!this.check(T.ASSIGN)) type = this.parseType();
321
+ this.expect(T.ASSIGN);
322
+ const value = this.parseExprList();
323
+ decls.push({ names, type, value });
324
+ }
325
+ this.semi();
326
+ return { kind: "ConstDecl", decls };
327
+ },
328
+
329
+ // Deep-clone an expression AST, replacing `iota` identifiers with the given integer.
330
+ _substituteIota(node, val) {
331
+ if (!node || typeof node !== "object") return node;
332
+ if (node.kind === "Ident" && node.name === "iota") {
333
+ return { kind: "BasicLit", litKind: "INT", value: String(val) };
334
+ }
335
+ // Shallow-clone the node, then recursively substitute in all child properties
336
+ const clone = { ...node };
337
+ for (const key of Object.keys(clone)) {
338
+ const v = clone[key];
339
+ if (Array.isArray(v)) {
340
+ clone[key] = v.map((item) => this._substituteIota(item, val));
341
+ } else if (v && typeof v === "object" && v.kind) {
342
+ clone[key] = this._substituteIota(v, val);
343
+ }
344
+ }
345
+ return clone;
346
+ },
347
+ };
@@ -1,6 +1,7 @@
1
1
  // Recursive-descent parser. Produces an AST from a GoFront token stream.
2
2
  //
3
3
  // Split into sub-modules under parser/:
4
+ // declarations.js — top-level declarations (func, method, type, var, const)
4
5
  // types.js — type expression parsing (slice, map, struct, interface, etc.)
5
6
  // statements.js — block, control flow, simple statements
6
7
  // expressions.js — operator precedence, unary, postfix, primary, literals
@@ -17,6 +18,7 @@
17
18
  // CompositeLit, KeyValueExpr, FuncLit, AwaitExpr
18
19
 
19
20
  import { T } from "../tokens.js";
21
+ import { declarationParseMethods } from "./declarations.js";
20
22
  import { expressionParserMethods } from "./expressions.js";
21
23
  import { statementParserMethods } from "./statements.js";
22
24
  import { typeParserMethods } from "./types.js";
@@ -151,354 +153,9 @@ export class Parser {
151
153
  this.semi();
152
154
  return { kind: "ImportDecl", imports };
153
155
  }
154
-
155
- // ── Top-level declarations ───────────────────────────────────
156
-
157
- parseTopDecl() {
158
- const t = this.peek();
159
- switch (t.type) {
160
- case T.ASYNC:
161
- return this.parseAsyncFuncOrMethod();
162
- case T.FUNC:
163
- return this.parseFuncOrMethod();
164
- case T.TYPE:
165
- return this.parseTypeDecl();
166
- case T.VAR:
167
- return this.parseVarDecl();
168
- case T.CONST:
169
- return this.parseConstDecl();
170
- default:
171
- this.err("Expected top-level declaration");
172
- }
173
- }
174
-
175
- parseFuncOrMethod() {
176
- const _line = this.peek().line;
177
- this.expect(T.FUNC);
178
- // method: func (recv Type) Name(...)
179
- if (this.check(T.LPAREN)) {
180
- this.expect(T.LPAREN);
181
- const recvName =
182
- this.check(T.IDENT) && !this.isReceiverTerminator(this.peek2())
183
- ? this.advance().value
184
- : "_";
185
- this.match(T.STAR); // pointer receiver: (c *Counter) — strip *, treat same as value receiver
186
- const recvType = this.parseTypeName();
187
- this.expect(T.RPAREN);
188
- const name = this.expect(T.IDENT).value;
189
- const { params, returnType } = this.parseSignature();
190
- const body = this.parseBlock();
191
- this.semi();
192
- return {
193
- kind: "MethodDecl",
194
- recvName,
195
- recvType,
196
- name,
197
- params,
198
- returnType,
199
- body,
200
- _line,
201
- };
202
- }
203
- // plain function
204
- const name = this.expect(T.IDENT).value;
205
- // generic function: func Name[T any, U Stringer](...) ...
206
- let typeParams = null;
207
- if (this.check(T.LBRACKET) && this._looksLikeTypeParamList()) {
208
- typeParams = this.parseTypeParamList();
209
- }
210
- const { params, returnType } = this.parseSignature();
211
- const body = this.parseBlock();
212
- this.semi();
213
- return {
214
- kind: "FuncDecl",
215
- name,
216
- typeParams,
217
- params,
218
- returnType,
219
- body,
220
- _line,
221
- };
222
- }
223
-
224
- parseAsyncFuncOrMethod() {
225
- this.expect(T.ASYNC);
226
- const decl = this.parseFuncOrMethod();
227
- decl.async = true;
228
- return decl;
229
- }
230
-
231
- // Returns true if tok is a receiver-list terminator (i.e. not a type name)
232
- isReceiverTerminator(tok) {
233
- return (
234
- tok &&
235
- (tok.type === T.RPAREN ||
236
- tok.type === T.COMMA ||
237
- tok.type === T.SEMICOLON ||
238
- tok.type === T.EOF)
239
- );
240
- }
241
-
242
- // Lookahead: does the [ ... ] after a func name or type name look like a type param list?
243
- // Type param lists contain IDENT followed by a constraint (another IDENT or keyword),
244
- // not expressions like integers or operators.
245
- _looksLikeTypeParamList() {
246
- const i = this.pos + 1; // skip [
247
- const src = this.tokens;
248
- // First token must be an IDENT (the type param name)
249
- if (!src[i] || src[i].type !== T.IDENT) return false;
250
- // Second token should be a constraint (IDENT like 'any', 'comparable', another type name)
251
- // or TILDE for union constraints
252
- const second = src[i + 1];
253
- if (!second) return false;
254
- if (second.type === T.IDENT) return true; // T any, T Stringer
255
- if (second.type === T.TILDE) return true; // T ~int
256
- if (second.type === T.INTERFACE) return true; // T interface{...}
257
- return false;
258
- }
259
-
260
- parseSignature() {
261
- const params = this.parseParamList();
262
- const returnType = this.parseReturnType();
263
- return { params, returnType };
264
- }
265
-
266
- // Parses (a, b int, c string) style param list
267
- parseParamList() {
268
- this.expect(T.LPAREN);
269
- const params = [];
270
- if (!this.check(T.RPAREN)) {
271
- do {
272
- if (this.check(T.RPAREN)) break;
273
- // Collect names, then type
274
- const names = [];
275
- if (this.check(T.IDENT) && this._isNamedParam()) {
276
- // named: a, b int or ns ...int
277
- names.push(this.advance().value);
278
- while (
279
- this.match(T.COMMA) &&
280
- this.check(T.IDENT) &&
281
- this._isNamedParam()
282
- ) {
283
- names.push(this.advance().value);
284
- }
285
- // variadic: name ...T
286
- const variadic = this.match(T.ELLIPSIS);
287
- const type = this.parseType();
288
- for (const n of names) params.push({ name: n, type, variadic });
289
- } else {
290
- // unnamed: just a type (possibly variadic: ...T)
291
- const variadic = this.match(T.ELLIPSIS);
292
- const type = this.parseType();
293
- params.push({ name: "_", type, variadic });
294
- }
295
- } while (this.match(T.COMMA));
296
- }
297
- this.expect(T.RPAREN);
298
- return params;
299
- }
300
-
301
- // Lookahead: determine if current IDENT starts a named param (name type)
302
- // vs an unnamed param (just a type). Scans IDENT (COMMA IDENT)* and checks
303
- // if a type-start token follows — if so, the IDENTs are names; if ) or ,
304
- // follows, the IDENTs are themselves types.
305
- _isNamedParam() {
306
- let pos = this.pos + 1; // skip current IDENT
307
- while (
308
- this.tokens[pos]?.type === T.COMMA &&
309
- this.tokens[pos + 1]?.type === T.IDENT
310
- ) {
311
- pos += 2;
312
- }
313
- const next = this.tokens[pos];
314
- if (!next) return false;
315
- return this.looksLikeType(next) || next.type === T.ELLIPSIS;
316
- }
317
-
318
- looksLikeType(tok) {
319
- return (
320
- tok.type === T.LBRACKET ||
321
- tok.type === T.STAR ||
322
- tok.type === T.MAP ||
323
- tok.type === T.FUNC ||
324
- tok.type === T.INTERFACE ||
325
- tok.type === T.STRUCT ||
326
- (tok.type === T.IDENT && tok.value !== "_")
327
- );
328
- }
329
-
330
- parseReturnType(inTypeExpr = false) {
331
- // No return type
332
- if (
333
- this.check(T.LBRACE) ||
334
- this.check(T.RBRACE) ||
335
- this.check(T.SEMICOLON) ||
336
- this.check(T.EOF)
337
- )
338
- return null;
339
- // Inside a type expression (e.g. func type in a tuple), `,` and `)` also end the type
340
- if (inTypeExpr && (this.check(T.COMMA) || this.check(T.RPAREN)))
341
- return null;
342
- // Multiple return values: (int, string) or named (result int, ok bool)
343
- if (this.check(T.LPAREN)) {
344
- this.advance();
345
- const types = [];
346
- const names = []; // non-null if named returns
347
- let isNamed = false;
348
- do {
349
- // Named returns: only treat IDENT as a name if peek2 is an actual type token
350
- if (this.check(T.IDENT) && this.looksLikeType(this.peek2())) {
351
- names.push(this.advance().value);
352
- isNamed = true;
353
- } else {
354
- names.push(null);
355
- }
356
- types.push(this.parseType());
357
- } while (this.match(T.COMMA));
358
- this.expect(T.RPAREN);
359
- const typeNode =
360
- types.length === 1 ? types[0] : { kind: "TupleType", types };
361
- // Attach named return info so typechecker can inject them into scope
362
- if (isNamed)
363
- typeNode._namedReturns = names.map((n, i) => ({
364
- name: n,
365
- type: types[i],
366
- }));
367
- return typeNode;
368
- }
369
- return this.parseType();
370
- }
371
-
372
- parseTypeDecl() {
373
- this.expect(T.TYPE);
374
- if (this.match(T.LPAREN)) {
375
- const decls = [];
376
- while (!this.check(T.RPAREN) && !this.check(T.EOF)) {
377
- const name = this.expect(T.IDENT).value;
378
- const isAlias = this.match(T.ASSIGN);
379
- const type = this.parseType();
380
- decls.push({ kind: "TypeDecl", name, type, isAlias });
381
- this.semi();
382
- }
383
- this.expect(T.RPAREN);
384
- this.semi();
385
- return decls;
386
- }
387
- const name = this.expect(T.IDENT).value;
388
- // generic type: type Name[T any] struct { ... }
389
- let typeParams = null;
390
- if (this.check(T.LBRACKET) && this._looksLikeTypeParamList()) {
391
- typeParams = this.parseTypeParamList();
392
- }
393
- const isAlias = this.match(T.ASSIGN);
394
- const type = this.parseType();
395
- this.semi();
396
- return { kind: "TypeDecl", name, typeParams, type, isAlias };
397
- }
398
-
399
- parseVarDecl() {
400
- this.expect(T.VAR);
401
- const decls = [];
402
- if (this.match(T.LPAREN)) {
403
- while (!this.check(T.RPAREN) && !this.check(T.EOF)) {
404
- decls.push(this.parseVarSpec());
405
- this.semi();
406
- }
407
- this.expect(T.RPAREN);
408
- } else {
409
- decls.push(this.parseVarSpec());
410
- }
411
- this.semi();
412
- return { kind: "VarDecl", decls };
413
- }
414
-
415
- parseVarSpec() {
416
- const names = [this.expect(T.IDENT).value];
417
- while (this.match(T.COMMA)) names.push(this.expect(T.IDENT).value);
418
- let type = null;
419
- let value = null;
420
- if (
421
- !this.check(T.ASSIGN) &&
422
- !this.check(T.SEMICOLON) &&
423
- !this.check(T.RPAREN)
424
- ) {
425
- type = this.parseType();
426
- }
427
- if (this.match(T.ASSIGN)) value = this.parseExprList();
428
- return { names, type, value };
429
- }
430
-
431
- parseConstDecl() {
432
- this.expect(T.CONST);
433
- const decls = [];
434
- if (this.match(T.LPAREN)) {
435
- let iotaVal = 0;
436
- let prevRawExprs = null; // raw AST (with iota Idents) from the previous spec
437
- let prevType = null;
438
- while (!this.check(T.RPAREN) && !this.check(T.EOF)) {
439
- const names = [this.expect(T.IDENT).value];
440
- while (this.match(T.COMMA)) names.push(this.expect(T.IDENT).value);
441
- let type = null;
442
- let rawExprs; // before iota substitution
443
- if (this.check(T.ASSIGN)) {
444
- this.advance();
445
- rawExprs = this.parseExprList();
446
- prevRawExprs = rawExprs;
447
- prevType = null;
448
- } else if (!this.check(T.SEMICOLON) && !this.check(T.RPAREN)) {
449
- // optional type annotation before =
450
- type = this.parseType();
451
- prevType = type;
452
- this.expect(T.ASSIGN);
453
- rawExprs = this.parseExprList();
454
- prevRawExprs = rawExprs;
455
- } else {
456
- // Omitted expression — repeat previous expression with updated iota
457
- rawExprs = prevRawExprs ?? [
458
- { kind: "BasicLit", litKind: "INT", value: String(iotaVal) },
459
- ];
460
- type = prevType ?? null;
461
- }
462
- // Substitute iota deeply in a cloned expression AST
463
- const value = rawExprs.map((v) => this._substituteIota(v, iotaVal));
464
- decls.push({ names, type, value });
465
- iotaVal++;
466
- this.semi();
467
- }
468
- this.expect(T.RPAREN);
469
- } else {
470
- const names = [this.expect(T.IDENT).value];
471
- while (this.match(T.COMMA)) names.push(this.expect(T.IDENT).value);
472
- let type = null;
473
- if (!this.check(T.ASSIGN)) type = this.parseType();
474
- this.expect(T.ASSIGN);
475
- const value = this.parseExprList();
476
- decls.push({ names, type, value });
477
- }
478
- this.semi();
479
- return { kind: "ConstDecl", decls };
480
- }
481
-
482
- // Deep-clone an expression AST, replacing `iota` identifiers with the given integer.
483
- _substituteIota(node, val) {
484
- if (!node || typeof node !== "object") return node;
485
- if (node.kind === "Ident" && node.name === "iota") {
486
- return { kind: "BasicLit", litKind: "INT", value: String(val) };
487
- }
488
- // Shallow-clone the node, then recursively substitute in all child properties
489
- const clone = { ...node };
490
- for (const key of Object.keys(clone)) {
491
- const v = clone[key];
492
- if (Array.isArray(v)) {
493
- clone[key] = v.map((item) => this._substituteIota(item, val));
494
- } else if (v && typeof v === "object" && v.kind) {
495
- clone[key] = this._substituteIota(v, val);
496
- }
497
- }
498
- return clone;
499
- }
500
156
  }
501
157
 
502
158
  Object.assign(Parser.prototype, typeParserMethods);
503
159
  Object.assign(Parser.prototype, statementParserMethods);
504
160
  Object.assign(Parser.prototype, expressionParserMethods);
161
+ Object.assign(Parser.prototype, declarationParseMethods);