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.
- package/README.md +36 -3
- package/package.json +7 -7
- package/src/asset-manager.js +125 -0
- package/src/cli-core.js +74 -0
- package/src/codegen/expressions.js +3 -0
- package/src/codegen/statements.js +3 -0
- package/src/codegen/stdlib/builder.js +3 -0
- package/src/codegen/stdlib/bytes.js +3 -0
- package/src/codegen/stdlib/errors.js +3 -0
- package/src/codegen/stdlib/fmt.js +3 -0
- package/src/codegen/stdlib/gom.js +3 -0
- package/src/codegen/stdlib/html.js +3 -0
- package/src/codegen/stdlib/index.js +3 -0
- package/src/codegen/stdlib/io.js +3 -0
- package/src/codegen/stdlib/maps.js +3 -0
- package/src/codegen/stdlib/math.js +3 -0
- package/src/codegen/stdlib/os.js +3 -0
- package/src/codegen/stdlib/path.js +3 -0
- package/src/codegen/stdlib/rand.js +3 -0
- package/src/codegen/stdlib/regexp.js +3 -0
- package/src/codegen/stdlib/slices.js +3 -0
- package/src/codegen/stdlib/sort.js +3 -0
- package/src/codegen/stdlib/strconv.js +3 -0
- package/src/codegen/stdlib/strings.js +3 -0
- package/src/codegen/stdlib/time.js +3 -0
- package/src/codegen/stdlib/unicode.js +3 -0
- package/src/codegen/stdlib/utf8.js +3 -0
- package/src/codegen/templ.js +3 -0
- package/src/compiler.js +63 -4
- package/src/dev-server.js +96 -33
- package/src/index.js +80 -123
- package/src/parser/declarations.js +347 -0
- package/src/parser/index.js +3 -346
- package/src/typechecker/assignability.js +3 -0
- package/src/typechecker/expressions.js +9 -0
- package/src/typechecker/index.js +2 -1
- package/src/typechecker/resolve.js +9 -2
- package/src/typechecker/statements.js +3 -0
- package/src/typechecker/termination.js +3 -0
- package/src/typechecker/types.js +1 -0
- 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
|
+
};
|
package/src/parser/index.js
CHANGED
|
@@ -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);
|