gofront 0.0.2 → 0.0.4

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.
@@ -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
+ };