gofront 0.0.2 → 0.0.3
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 +175 -51
- package/package.json +7 -3
- package/src/codegen/expressions.js +804 -0
- package/src/codegen/source-map.js +45 -0
- package/src/codegen/statements.js +406 -0
- package/src/codegen.js +34 -990
- package/src/compiler.js +36 -22
- package/src/index.js +3 -3
- package/src/lexer.js +94 -6
- package/src/parser/expressions.js +389 -0
- package/src/parser/statements.js +355 -0
- package/src/parser/types.js +140 -0
- package/src/parser.js +40 -853
- package/src/typechecker/expressions.js +359 -0
- package/src/typechecker/statements.js +250 -0
- package/src/typechecker/types.js +226 -0
- package/src/typechecker.js +352 -703
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
// Parser statement methods — installed as a mixin on Parser.prototype.
|
|
2
|
+
|
|
3
|
+
import { T } from "../lexer.js";
|
|
4
|
+
|
|
5
|
+
export const statementParserMethods = {
|
|
6
|
+
parseBlock() {
|
|
7
|
+
this.expect(T.LBRACE);
|
|
8
|
+
const stmts = [];
|
|
9
|
+
while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
|
|
10
|
+
const result = this.parseStmt();
|
|
11
|
+
if (Array.isArray(result)) stmts.push(...result);
|
|
12
|
+
else stmts.push(result);
|
|
13
|
+
this.semi();
|
|
14
|
+
}
|
|
15
|
+
this.expect(T.RBRACE);
|
|
16
|
+
return { kind: "Block", stmts };
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
parseStmt() {
|
|
20
|
+
const t = this.peek();
|
|
21
|
+
const stmt = this._parseStmt(t);
|
|
22
|
+
if (Array.isArray(stmt)) {
|
|
23
|
+
for (const s of stmt) s._line = t.line;
|
|
24
|
+
return stmt;
|
|
25
|
+
}
|
|
26
|
+
stmt._line = t.line;
|
|
27
|
+
return stmt;
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
_parseStmt(t) {
|
|
31
|
+
switch (t.type) {
|
|
32
|
+
case T.VAR:
|
|
33
|
+
return this.parseVarDecl();
|
|
34
|
+
case T.CONST:
|
|
35
|
+
return this.parseConstDecl();
|
|
36
|
+
case T.TYPE:
|
|
37
|
+
return this.parseTypeDecl();
|
|
38
|
+
case T.RETURN:
|
|
39
|
+
return this.parseReturn();
|
|
40
|
+
case T.DEFER:
|
|
41
|
+
return this.parseDefer();
|
|
42
|
+
case T.BREAK:
|
|
43
|
+
this.advance();
|
|
44
|
+
if (this.check(T.IDENT)) {
|
|
45
|
+
const label = this.advance().value;
|
|
46
|
+
this.semi();
|
|
47
|
+
return { kind: "BranchStmt", keyword: "break", label };
|
|
48
|
+
}
|
|
49
|
+
this.semi();
|
|
50
|
+
return { kind: "BranchStmt", keyword: "break" };
|
|
51
|
+
case T.CONTINUE:
|
|
52
|
+
this.advance();
|
|
53
|
+
if (this.check(T.IDENT)) {
|
|
54
|
+
const label = this.advance().value;
|
|
55
|
+
this.semi();
|
|
56
|
+
return { kind: "BranchStmt", keyword: "continue", label };
|
|
57
|
+
}
|
|
58
|
+
this.semi();
|
|
59
|
+
return { kind: "BranchStmt", keyword: "continue" };
|
|
60
|
+
case T.FALLTHROUGH:
|
|
61
|
+
this.advance();
|
|
62
|
+
this.semi();
|
|
63
|
+
return { kind: "BranchStmt", keyword: "fallthrough" };
|
|
64
|
+
case T.IF:
|
|
65
|
+
return this.parseIf();
|
|
66
|
+
case T.FOR:
|
|
67
|
+
return this.parseFor();
|
|
68
|
+
case T.SWITCH:
|
|
69
|
+
return this.parseSwitch();
|
|
70
|
+
case T.GO:
|
|
71
|
+
this.err("goroutines are not supported in GoFront");
|
|
72
|
+
break;
|
|
73
|
+
case T.SELECT:
|
|
74
|
+
this.err("select statement is not supported in GoFront (no channels)");
|
|
75
|
+
break;
|
|
76
|
+
case T.LBRACE:
|
|
77
|
+
return this.parseBlock();
|
|
78
|
+
case T.IDENT:
|
|
79
|
+
if (this.check2(T.COLON)) {
|
|
80
|
+
const label = this.advance().value; // consume IDENT
|
|
81
|
+
this.advance(); // consume COLON
|
|
82
|
+
const body = this.parseStmt();
|
|
83
|
+
return { kind: "LabeledStmt", label, body };
|
|
84
|
+
}
|
|
85
|
+
return this.parseSimpleStmt();
|
|
86
|
+
default:
|
|
87
|
+
return this.parseSimpleStmt();
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
parseDefer() {
|
|
92
|
+
this.expect(T.DEFER);
|
|
93
|
+
const call = this.parseExpr();
|
|
94
|
+
return { kind: "DeferStmt", call };
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
parseReturn() {
|
|
98
|
+
this.expect(T.RETURN);
|
|
99
|
+
if (this.check(T.SEMICOLON) || this.check(T.RBRACE)) {
|
|
100
|
+
return { kind: "ReturnStmt", values: [] };
|
|
101
|
+
}
|
|
102
|
+
const values = this.parseExprList();
|
|
103
|
+
return { kind: "ReturnStmt", values };
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
parseIf() {
|
|
107
|
+
this.expect(T.IF);
|
|
108
|
+
let init = null;
|
|
109
|
+
let cond;
|
|
110
|
+
// if init; cond { } — use noCompLit to avoid "{" being parsed as composite literal
|
|
111
|
+
const first = this.parseSimpleStmtRaw(true);
|
|
112
|
+
// Only treat as "init; cond" if semicolon is followed by a non-brace token
|
|
113
|
+
if (this.check(T.SEMICOLON) && this.peek2().type !== T.LBRACE) {
|
|
114
|
+
this.advance(); // consume ;
|
|
115
|
+
init = first;
|
|
116
|
+
cond = this.parseExpr(0, true);
|
|
117
|
+
} else {
|
|
118
|
+
this.match(T.SEMICOLON); // consume any auto-inserted ;
|
|
119
|
+
cond = this.stmtToExpr(first);
|
|
120
|
+
}
|
|
121
|
+
const body = this.parseBlock();
|
|
122
|
+
// Consume auto-inserted ; before else (Go allows "}\nelse")
|
|
123
|
+
this.match(T.SEMICOLON);
|
|
124
|
+
let elseBody = null;
|
|
125
|
+
if (this.match(T.ELSE)) {
|
|
126
|
+
elseBody = this.check(T.IF) ? this.parseIf() : this.parseBlock();
|
|
127
|
+
}
|
|
128
|
+
return { kind: "IfStmt", init, cond, body, elseBody };
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
parseFor() {
|
|
132
|
+
this.expect(T.FOR);
|
|
133
|
+
|
|
134
|
+
// for { } — infinite loop
|
|
135
|
+
if (this.check(T.LBRACE)) {
|
|
136
|
+
return {
|
|
137
|
+
kind: "ForStmt",
|
|
138
|
+
init: null,
|
|
139
|
+
cond: null,
|
|
140
|
+
post: null,
|
|
141
|
+
body: this.parseBlock(),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const first = this.parseSimpleStmtRaw(true);
|
|
146
|
+
|
|
147
|
+
if (this.check(T.LBRACE)) {
|
|
148
|
+
// for k, v := range expr { } — range stmt ends right before {
|
|
149
|
+
if (this.isRangeStmt(first)) {
|
|
150
|
+
return {
|
|
151
|
+
kind: "ForStmt",
|
|
152
|
+
init: first,
|
|
153
|
+
cond: null,
|
|
154
|
+
post: null,
|
|
155
|
+
body: this.parseBlock(),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
// for cond { }
|
|
159
|
+
return {
|
|
160
|
+
kind: "ForStmt",
|
|
161
|
+
init: null,
|
|
162
|
+
cond: this.stmtToExpr(first),
|
|
163
|
+
post: null,
|
|
164
|
+
body: this.parseBlock(),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
this.expect(T.SEMICOLON);
|
|
169
|
+
|
|
170
|
+
// for init; cond; post { }
|
|
171
|
+
let cond = null;
|
|
172
|
+
if (!this.check(T.SEMICOLON)) cond = this.parseExpr(0, true);
|
|
173
|
+
this.expect(T.SEMICOLON);
|
|
174
|
+
let post = null;
|
|
175
|
+
if (!this.check(T.LBRACE)) post = this.parseSimpleStmtRaw();
|
|
176
|
+
const body = this.parseBlock();
|
|
177
|
+
return { kind: "ForStmt", init: first, cond, post, body };
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
isRangeStmt(stmt) {
|
|
181
|
+
if (!stmt) return false;
|
|
182
|
+
if (stmt.kind !== "DefineStmt" && stmt.kind !== "AssignStmt") return false;
|
|
183
|
+
return stmt.rhs?.[0]?.kind === "RangeExpr";
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
parseSwitch() {
|
|
187
|
+
this.expect(T.SWITCH);
|
|
188
|
+
let init = null;
|
|
189
|
+
let tag = null;
|
|
190
|
+
if (!this.check(T.LBRACE)) {
|
|
191
|
+
const first = this.parseSimpleStmtRaw(true);
|
|
192
|
+
if (this.match(T.SEMICOLON)) {
|
|
193
|
+
init = first;
|
|
194
|
+
if (!this.check(T.LBRACE))
|
|
195
|
+
tag = this.stmtToExpr(this.parseSimpleStmtRaw(true));
|
|
196
|
+
} else {
|
|
197
|
+
tag = this.stmtToExpr(first);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Detect type switch: switch x.(type) or switch v := x.(type)
|
|
202
|
+
if (tag?.kind === "TypeSwitchExpr") {
|
|
203
|
+
return this._parseTypeSwitchBody(null, tag.expr);
|
|
204
|
+
}
|
|
205
|
+
if (tag?.kind === "DefineStmt" && tag.rhs[0]?.kind === "TypeSwitchExpr") {
|
|
206
|
+
return this._parseTypeSwitchBody(
|
|
207
|
+
tag.lhs[0]?.name ?? null,
|
|
208
|
+
tag.rhs[0].expr,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
this.expect(T.LBRACE);
|
|
213
|
+
const cases = [];
|
|
214
|
+
while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
|
|
215
|
+
if (this.check(T.CASE)) {
|
|
216
|
+
this.advance();
|
|
217
|
+
const list = this.parseExprList();
|
|
218
|
+
this.expect(T.COLON);
|
|
219
|
+
cases.push({ kind: "CaseClause", list, stmts: this._parseCaseBody() });
|
|
220
|
+
} else if (this.check(T.DEFAULT)) {
|
|
221
|
+
this.advance();
|
|
222
|
+
this.expect(T.COLON);
|
|
223
|
+
cases.push({
|
|
224
|
+
kind: "CaseClause",
|
|
225
|
+
list: null,
|
|
226
|
+
stmts: this._parseCaseBody(),
|
|
227
|
+
});
|
|
228
|
+
} else {
|
|
229
|
+
this.err("Expected case or default");
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
this.expect(T.RBRACE);
|
|
233
|
+
return { kind: "SwitchStmt", init, tag, cases };
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
_parseCaseBody() {
|
|
237
|
+
const stmts = [];
|
|
238
|
+
while (
|
|
239
|
+
!this.check(T.CASE) &&
|
|
240
|
+
!this.check(T.DEFAULT) &&
|
|
241
|
+
!this.check(T.RBRACE)
|
|
242
|
+
) {
|
|
243
|
+
stmts.push(this.parseStmt());
|
|
244
|
+
this.semi();
|
|
245
|
+
}
|
|
246
|
+
return stmts;
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
_parseTypeSwitchBody(assign, expr) {
|
|
250
|
+
this.expect(T.LBRACE);
|
|
251
|
+
const cases = [];
|
|
252
|
+
while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
|
|
253
|
+
if (this.check(T.CASE)) {
|
|
254
|
+
this.advance();
|
|
255
|
+
const types = this._parseTypeSwitchTypeList();
|
|
256
|
+
this.expect(T.COLON);
|
|
257
|
+
cases.push({ types, stmts: this._parseCaseBody() });
|
|
258
|
+
} else if (this.check(T.DEFAULT)) {
|
|
259
|
+
this.advance();
|
|
260
|
+
this.expect(T.COLON);
|
|
261
|
+
cases.push({ types: null, stmts: this._parseCaseBody() });
|
|
262
|
+
} else {
|
|
263
|
+
this.err("Expected case or default");
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
this.expect(T.RBRACE);
|
|
267
|
+
return { kind: "TypeSwitchStmt", assign, expr, cases };
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
_parseTypeSwitchTypeList() {
|
|
271
|
+
const parseOne = () => {
|
|
272
|
+
// nil is a keyword, not a type name, so handle it explicitly
|
|
273
|
+
if (this.check(T.NIL)) {
|
|
274
|
+
this.advance();
|
|
275
|
+
return { kind: "TypeName", name: "nil" };
|
|
276
|
+
}
|
|
277
|
+
return this.parseType();
|
|
278
|
+
};
|
|
279
|
+
const types = [parseOne()];
|
|
280
|
+
while (this.match(T.COMMA)) types.push(parseOne());
|
|
281
|
+
return types;
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
// Parses a "simple statement": assignment, define, inc/dec, or expression.
|
|
285
|
+
// Returns a statement node.
|
|
286
|
+
parseSimpleStmt() {
|
|
287
|
+
const s = this.parseSimpleStmtRaw();
|
|
288
|
+
return s;
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
parseSimpleStmtRaw(noCompLit = false) {
|
|
292
|
+
// Parse left-hand side (could be expr list for multi-assign)
|
|
293
|
+
const first = this.parseExpr(0, noCompLit);
|
|
294
|
+
const t = this.peek();
|
|
295
|
+
|
|
296
|
+
// a, b, c := ... or x, y = ...
|
|
297
|
+
if (t.type === T.COMMA) {
|
|
298
|
+
const lhs = [first];
|
|
299
|
+
while (this.match(T.COMMA)) lhs.push(this.parseExpr());
|
|
300
|
+
const op = this.advance(); // := or =
|
|
301
|
+
const rhs = this.parseExprList();
|
|
302
|
+
if (op.type === T.DEFINE) return { kind: "DefineStmt", lhs, rhs };
|
|
303
|
+
return { kind: "AssignStmt", lhs, op: "=", rhs };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// x := expr
|
|
307
|
+
if (t.type === T.DEFINE) {
|
|
308
|
+
this.advance();
|
|
309
|
+
const rhs = this.parseExprList();
|
|
310
|
+
return { kind: "DefineStmt", lhs: [first], rhs };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// x = expr or x += expr etc.
|
|
314
|
+
const assignOps = [
|
|
315
|
+
T.ASSIGN,
|
|
316
|
+
T.PLUS_ASSIGN,
|
|
317
|
+
T.MINUS_ASSIGN,
|
|
318
|
+
T.STAR_ASSIGN,
|
|
319
|
+
T.SLASH_ASSIGN,
|
|
320
|
+
T.PERCENT_ASSIGN,
|
|
321
|
+
T.AMP_ASSIGN,
|
|
322
|
+
T.PIPE_ASSIGN,
|
|
323
|
+
T.CARET_ASSIGN,
|
|
324
|
+
T.LSHIFT_ASSIGN,
|
|
325
|
+
T.RSHIFT_ASSIGN,
|
|
326
|
+
];
|
|
327
|
+
if (assignOps.includes(t.type)) {
|
|
328
|
+
const op = this.advance().value;
|
|
329
|
+
const rhs = this.parseExprList();
|
|
330
|
+
return { kind: "AssignStmt", lhs: [first], op, rhs };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// x++ x--
|
|
334
|
+
if (t.type === T.INC || t.type === T.DEC) {
|
|
335
|
+
const op = this.advance().value;
|
|
336
|
+
return { kind: "IncDecStmt", expr: first, op };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// for range detection: "for k, v := range expr"
|
|
340
|
+
// range keyword after :=
|
|
341
|
+
if (first.kind === "Ident" && first.name === "range") {
|
|
342
|
+
const expr = this.parseExpr();
|
|
343
|
+
return { kind: "RangeExpr", expr };
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return { kind: "ExprStmt", expr: first };
|
|
347
|
+
},
|
|
348
|
+
|
|
349
|
+
// Converts an ExprStmt back to its expression
|
|
350
|
+
stmtToExpr(stmt) {
|
|
351
|
+
if (!stmt) return null;
|
|
352
|
+
if (stmt.kind === "ExprStmt") return stmt.expr;
|
|
353
|
+
return stmt;
|
|
354
|
+
},
|
|
355
|
+
};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// Parser type-expression methods — installed as a mixin on Parser.prototype.
|
|
2
|
+
|
|
3
|
+
import { T } from "../lexer.js";
|
|
4
|
+
|
|
5
|
+
export const typeParserMethods = {
|
|
6
|
+
parseType() {
|
|
7
|
+
const t = this.peek();
|
|
8
|
+
switch (t.type) {
|
|
9
|
+
case T.LBRACKET:
|
|
10
|
+
return this.parseSliceOrArrayType();
|
|
11
|
+
case T.MAP:
|
|
12
|
+
return this.parseMapType();
|
|
13
|
+
case T.FUNC:
|
|
14
|
+
return this.parseFuncType();
|
|
15
|
+
case T.STRUCT:
|
|
16
|
+
return this.parseStructType();
|
|
17
|
+
case T.INTERFACE:
|
|
18
|
+
return this.parseInterfaceType();
|
|
19
|
+
case T.STAR:
|
|
20
|
+
this.advance();
|
|
21
|
+
return { kind: "PointerType", base: this.parseType() };
|
|
22
|
+
case T.CHAN:
|
|
23
|
+
this.err("channels are not supported in GoFront");
|
|
24
|
+
break;
|
|
25
|
+
case T.IDENT:
|
|
26
|
+
case "int":
|
|
27
|
+
case "float64":
|
|
28
|
+
case "string":
|
|
29
|
+
case "bool":
|
|
30
|
+
case "any":
|
|
31
|
+
case "byte":
|
|
32
|
+
case "rune":
|
|
33
|
+
case "error":
|
|
34
|
+
return this.parseTypeName();
|
|
35
|
+
default:
|
|
36
|
+
this.err(`Expected type, got '${t.value}'`);
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
parseTypeName() {
|
|
41
|
+
const t = this.advance();
|
|
42
|
+
let name = t.value;
|
|
43
|
+
// qualified: pkg.Type
|
|
44
|
+
if (this.check(T.DOT)) {
|
|
45
|
+
this.advance();
|
|
46
|
+
name += `.${this.expect(T.IDENT).value}`;
|
|
47
|
+
}
|
|
48
|
+
return { kind: "TypeName", name };
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
parseSliceOrArrayType() {
|
|
52
|
+
this.expect(T.LBRACKET);
|
|
53
|
+
if (this.check(T.RBRACKET)) {
|
|
54
|
+
// slice type: []T
|
|
55
|
+
this.advance();
|
|
56
|
+
return { kind: "SliceType", elem: this.parseType() };
|
|
57
|
+
}
|
|
58
|
+
if (this.match(T.ELLIPSIS)) {
|
|
59
|
+
// [...]T — array with inferred length from composite literal
|
|
60
|
+
this.expect(T.RBRACKET);
|
|
61
|
+
return { kind: "ArrayType", inferLen: true, elem: this.parseType() };
|
|
62
|
+
}
|
|
63
|
+
// fixed array: [n]T — treated as slice for JS purposes
|
|
64
|
+
const size = this.parseExpr();
|
|
65
|
+
this.expect(T.RBRACKET);
|
|
66
|
+
return { kind: "ArrayType", size, elem: this.parseType() };
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
parseMapType() {
|
|
70
|
+
this.expect(T.MAP);
|
|
71
|
+
this.expect(T.LBRACKET);
|
|
72
|
+
const key = this.parseType();
|
|
73
|
+
this.expect(T.RBRACKET);
|
|
74
|
+
const value = this.parseType();
|
|
75
|
+
return { kind: "MapType", key, value };
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
parseFuncType() {
|
|
79
|
+
this.expect(T.FUNC);
|
|
80
|
+
const params = this.parseParamList();
|
|
81
|
+
const returnType = this.parseReturnType(true);
|
|
82
|
+
return { kind: "FuncType", params, returnType };
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
parseStructType() {
|
|
86
|
+
this.expect(T.STRUCT);
|
|
87
|
+
this.expect(T.LBRACE);
|
|
88
|
+
const fields = [];
|
|
89
|
+
while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
|
|
90
|
+
const names = [this.expect(T.IDENT).value];
|
|
91
|
+
while (this.match(T.COMMA)) names.push(this.expect(T.IDENT).value);
|
|
92
|
+
// Check if this is an embedded field (just a type name)
|
|
93
|
+
// e.g., type T struct { Base }
|
|
94
|
+
if (this.check(T.SEMICOLON) || this.check(T.RBRACE)) {
|
|
95
|
+
const typeName = names[0]; // assuming no comma list for embedded
|
|
96
|
+
fields.push({
|
|
97
|
+
names: [],
|
|
98
|
+
type: { kind: "TypeName", name: typeName },
|
|
99
|
+
embedded: true,
|
|
100
|
+
});
|
|
101
|
+
this.semi();
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const type = this.parseType();
|
|
105
|
+
// Consume optional struct tag (backtick string), e.g. `json:"name"`
|
|
106
|
+
if (this.check(T.STRING)) this.advance();
|
|
107
|
+
fields.push({ names, type });
|
|
108
|
+
this.semi();
|
|
109
|
+
}
|
|
110
|
+
this.expect(T.RBRACE);
|
|
111
|
+
return { kind: "StructType", fields };
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
parseInterfaceType() {
|
|
115
|
+
this.expect(T.INTERFACE);
|
|
116
|
+
this.expect(T.LBRACE);
|
|
117
|
+
const methods = [];
|
|
118
|
+
const embeds = [];
|
|
119
|
+
while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
|
|
120
|
+
const name = this.expect(T.IDENT).value;
|
|
121
|
+
if (this.check(T.LPAREN)) {
|
|
122
|
+
// Method signature: Name(params) returnType
|
|
123
|
+
const params = this.parseParamList();
|
|
124
|
+
const returnType = this.parseReturnType();
|
|
125
|
+
methods.push({ name, params, returnType });
|
|
126
|
+
} else {
|
|
127
|
+
// Embedded interface: TypeName or pkg.TypeName
|
|
128
|
+
let typeName = name;
|
|
129
|
+
if (this.check(T.DOT)) {
|
|
130
|
+
this.advance();
|
|
131
|
+
typeName += `.${this.expect(T.IDENT).value}`;
|
|
132
|
+
}
|
|
133
|
+
embeds.push({ kind: "TypeName", name: typeName });
|
|
134
|
+
}
|
|
135
|
+
this.semi();
|
|
136
|
+
}
|
|
137
|
+
this.expect(T.RBRACE);
|
|
138
|
+
return { kind: "InterfaceType", methods, embeds };
|
|
139
|
+
},
|
|
140
|
+
};
|