gofront 0.0.1 → 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.
@@ -0,0 +1,389 @@
1
+ // Parser expression methods — installed as a mixin on Parser.prototype.
2
+
3
+ import { T } from "../lexer.js";
4
+
5
+ // Binary operator precedence (higher = binds tighter, matching Go spec)
6
+ const PREC = {
7
+ [T.OR]: 1,
8
+ [T.AND]: 2,
9
+ [T.EQ]: 3,
10
+ [T.NEQ]: 3,
11
+ [T.LT]: 3,
12
+ [T.GT]: 3,
13
+ [T.LTE]: 3,
14
+ [T.GTE]: 3,
15
+ [T.PLUS]: 4,
16
+ [T.MINUS]: 4,
17
+ [T.PIPE]: 4,
18
+ [T.CARET]: 4,
19
+ [T.STAR]: 5,
20
+ [T.SLASH]: 5,
21
+ [T.PERCENT]: 5,
22
+ [T.LSHIFT]: 5,
23
+ [T.RSHIFT]: 5,
24
+ [T.AMP]: 5,
25
+ [T.AND_NOT]: 5,
26
+ };
27
+
28
+ const TYPE_KEYWORDS = new Set([
29
+ "int",
30
+ "float64",
31
+ "string",
32
+ "bool",
33
+ "any",
34
+ "byte",
35
+ "rune",
36
+ "error",
37
+ ]);
38
+
39
+ const BUILTIN_KEYWORDS = new Set([
40
+ "new",
41
+ "make",
42
+ "len",
43
+ "cap",
44
+ "append",
45
+ "delete",
46
+ "copy",
47
+ "print",
48
+ "println",
49
+ "panic",
50
+ "error",
51
+ ]);
52
+
53
+ export const expressionParserMethods = {
54
+ parseExprList() {
55
+ const exprs = [this.parseExpr()];
56
+ while (this.match(T.COMMA)) exprs.push(this.parseExpr());
57
+ return exprs;
58
+ },
59
+
60
+ // noCompLit=true disables composite literal parsing in if/for/switch conditions
61
+ // to resolve the ambiguity between "if x {" and "if Type{ } {"
62
+ parseExpr(minPrec = 0, noCompLit = false) {
63
+ let left = this.parseUnary(noCompLit);
64
+ while (true) {
65
+ const prec = PREC[this.peek().type];
66
+ if (!prec || prec <= minPrec) break;
67
+ const op = this.advance().value;
68
+ const right = this.parseExpr(prec, noCompLit);
69
+ left = { kind: "BinaryExpr", left, op, right };
70
+ }
71
+ return left;
72
+ },
73
+
74
+ parseUnary(noCompLit = false) {
75
+ const t = this.peek();
76
+ if (
77
+ t.type === T.NOT ||
78
+ t.type === T.MINUS ||
79
+ t.type === T.PLUS ||
80
+ t.type === T.CARET ||
81
+ t.type === T.AMP ||
82
+ t.type === T.STAR
83
+ ) {
84
+ const op = this.advance().value;
85
+ return { kind: "UnaryExpr", op, operand: this.parseUnary(noCompLit) };
86
+ }
87
+ if (t.type === T.AWAIT) {
88
+ this.advance();
89
+ return { kind: "AwaitExpr", expr: this.parseUnary(noCompLit) };
90
+ }
91
+ return this.parsePostfix(noCompLit);
92
+ },
93
+
94
+ parsePostfix(noCompLit = false) {
95
+ let expr = this.parsePrimary();
96
+ while (true) {
97
+ const t = this.peek();
98
+ if (t.type === T.DOT) {
99
+ this.advance();
100
+ // type assertion: expr.(Type)
101
+ if (this.check(T.LPAREN)) {
102
+ this.advance();
103
+ // x.(type) — type switch guard expression
104
+ if (this.check(T.TYPE)) {
105
+ this.advance();
106
+ this.expect(T.RPAREN);
107
+ expr = { kind: "TypeSwitchExpr", expr };
108
+ } else {
109
+ const type = this.parseType();
110
+ this.expect(T.RPAREN);
111
+ expr = { kind: "TypeAssertExpr", expr, type };
112
+ }
113
+ } else {
114
+ const field = this.advance().value;
115
+ expr = { kind: "SelectorExpr", expr, field };
116
+ }
117
+ } else if (t.type === T.LBRACKET) {
118
+ this.advance();
119
+ const low = this.check(T.COLON) ? null : this.parseExpr();
120
+ if (this.match(T.COLON)) {
121
+ const high =
122
+ this.check(T.RBRACKET) || this.check(T.COLON)
123
+ ? null
124
+ : this.parseExpr();
125
+ let max = null;
126
+ if (this.match(T.COLON)) {
127
+ max = this.parseExpr();
128
+ }
129
+ this.expect(T.RBRACKET);
130
+ expr = { kind: "SliceExpr", expr, low, high, max };
131
+ } else {
132
+ this.expect(T.RBRACKET);
133
+ expr = { kind: "IndexExpr", expr, index: low };
134
+ }
135
+ } else if (t.type === T.LPAREN) {
136
+ expr = this.parseCall(expr);
137
+ } else if (
138
+ t.type === T.LBRACE &&
139
+ !noCompLit &&
140
+ this.isCompositeLitContext(expr)
141
+ ) {
142
+ expr = this.parseCompositeLit(expr);
143
+ } else {
144
+ break;
145
+ }
146
+ }
147
+ return expr;
148
+ },
149
+
150
+ // Composite literals are only allowed after a type name (not arbitrary exprs)
151
+ isCompositeLitContext(expr) {
152
+ return (
153
+ expr.kind === "Ident" ||
154
+ expr.kind === "SelectorExpr" ||
155
+ expr.kind === "TypeExpr" ||
156
+ expr.kind === "StructType"
157
+ );
158
+ },
159
+
160
+ parseCall(fn) {
161
+ this.expect(T.LPAREN);
162
+ const args = [];
163
+ if (!this.check(T.RPAREN)) {
164
+ do {
165
+ if (this.check(T.RPAREN)) break;
166
+ // map[K]V and []T as call args must be parsed as types, not expressions,
167
+ // when they are NOT followed by { (which would make them composite literals).
168
+ // Needed for make(map[K]V), make([]T, n), new(T), etc.
169
+ const t = this.peek();
170
+ const isSliceOrArrayType =
171
+ t.type === T.LBRACKET &&
172
+ (this.peek2()?.type === T.RBRACKET || // []T
173
+ this.peek2()?.type === T.INT); // [n]T
174
+ if (
175
+ (t.type === T.MAP || isSliceOrArrayType) &&
176
+ !this.looksLikeCompositeLitArg()
177
+ ) {
178
+ const typeNode = this.parseType();
179
+ // []byte(s) / []rune(s) used as a call argument — it's a type conversion
180
+ if (this.check(T.LPAREN)) {
181
+ this.advance();
182
+ const convExpr = this.parseExpr();
183
+ this.expect(T.RPAREN);
184
+ args.push({
185
+ kind: "TypeConversion",
186
+ targetType: typeNode,
187
+ expr: convExpr,
188
+ });
189
+ } else {
190
+ args.push({ kind: "TypeExpr", type: typeNode });
191
+ }
192
+ } else {
193
+ const arg = this.parseExpr();
194
+ if (this.match(T.ELLIPSIS)) arg._spread = true;
195
+ args.push(arg);
196
+ }
197
+ } while (this.match(T.COMMA));
198
+ }
199
+ this.expect(T.RPAREN);
200
+ return { kind: "CallExpr", func: fn, args };
201
+ },
202
+
203
+ // Returns true if the current position looks like a composite literal argument,
204
+ // e.g. []int{1,2,3} or map[string]int{"a":1}. We scan ahead past the type
205
+ // tokens to check whether a { follows, without advancing this.pos permanently.
206
+ looksLikeCompositeLitArg() {
207
+ let i = this.pos;
208
+ const src = this.tokens;
209
+ // Skip over the type tokens: [, ], map, [, K, ], V, ident, etc.
210
+ // Simple approach: skip until we hit something that can't be part of a type
211
+ let depth = 0;
212
+ while (i < src.length) {
213
+ const tt = src[i].type;
214
+ if (tt === T.LBRACKET) {
215
+ depth++;
216
+ i++;
217
+ continue;
218
+ }
219
+ if (tt === T.RBRACKET) {
220
+ depth--;
221
+ i++;
222
+ continue;
223
+ }
224
+ if (depth > 0) {
225
+ i++;
226
+ continue;
227
+ }
228
+ if (
229
+ tt === T.MAP ||
230
+ tt === T.IDENT ||
231
+ tt === T.STAR ||
232
+ tt === T.INT ||
233
+ tt === T.FLOAT ||
234
+ tt === T.STRING
235
+ ) {
236
+ i++;
237
+ continue;
238
+ }
239
+ break;
240
+ }
241
+ // Skip whitespace tokens (shouldn't exist post-lex, but be safe)
242
+ while (i < src.length && src[i].type === T.SEMICOLON) i++;
243
+ return i < src.length && src[i].type === T.LBRACE;
244
+ },
245
+
246
+ parseCompositeLit(typeExpr) {
247
+ this.expect(T.LBRACE);
248
+ const elems = [];
249
+ while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
250
+ // Implicit composite literal: {X: 1} inside []Point{{X:1}} or map[string]Point{"a": {X:1}}
251
+ if (this.check(T.LBRACE)) {
252
+ elems.push(this.parseCompositeLit(null)); // null typeExpr = inferred
253
+ } else {
254
+ const first = this.parseExpr();
255
+ if (this.match(T.COLON)) {
256
+ // Map key: value — value may also be an implicit composite lit
257
+ const val = this.check(T.LBRACE)
258
+ ? this.parseCompositeLit(null)
259
+ : this.parseExpr();
260
+ elems.push({ kind: "KeyValueExpr", key: first, value: val });
261
+ } else {
262
+ elems.push(first);
263
+ }
264
+ }
265
+ this.match(T.COMMA);
266
+ }
267
+ this.expect(T.RBRACE);
268
+ return { kind: "CompositeLit", typeExpr, elems };
269
+ },
270
+
271
+ parsePrimary() {
272
+ const line = this.peek().line;
273
+ const expr = this._parsePrimary();
274
+ if (!expr._line) expr._line = line;
275
+ return expr;
276
+ },
277
+
278
+ _parsePrimary() {
279
+ const t = this.peek();
280
+
281
+ // Literals
282
+ if (t.type === T.INT) {
283
+ this.advance();
284
+ return { kind: "BasicLit", litKind: "INT", value: t.value };
285
+ }
286
+ if (t.type === T.FLOAT) {
287
+ this.advance();
288
+ return { kind: "BasicLit", litKind: "FLOAT", value: t.value };
289
+ }
290
+ if (t.type === T.STRING) {
291
+ this.advance();
292
+ return { kind: "BasicLit", litKind: "STRING", value: t.value };
293
+ }
294
+ if (t.type === T.TRUE) {
295
+ this.advance();
296
+ return { kind: "BasicLit", litKind: "BOOL", value: "true" };
297
+ }
298
+ if (t.type === T.FALSE) {
299
+ this.advance();
300
+ return { kind: "BasicLit", litKind: "BOOL", value: "false" };
301
+ }
302
+ if (t.type === T.NIL) {
303
+ this.advance();
304
+ return { kind: "BasicLit", litKind: "NIL", value: "null" };
305
+ }
306
+
307
+ // Parenthesised expression
308
+ if (t.type === T.LPAREN) {
309
+ this.advance();
310
+ const expr = this.parseExpr();
311
+ this.expect(T.RPAREN);
312
+ return expr;
313
+ }
314
+
315
+ // Function literal (sync or async)
316
+ if (t.type === T.FUNC || t.type === T.ASYNC) {
317
+ const isAsync = t.type === T.ASYNC;
318
+ this.advance();
319
+ if (isAsync) this.expect(T.FUNC);
320
+ const params = this.parseParamList();
321
+ const returnType = this.parseReturnType();
322
+ const body = this.parseBlock();
323
+ return { kind: "FuncLit", params, returnType, body, async: isAsync };
324
+ }
325
+
326
+ // range expression (only valid inside a for statement)
327
+ if (t.type === T.RANGE) {
328
+ this.advance();
329
+ // Always use noCompLit here: "range slice {" — { is the for body, not a composite lit
330
+ return { kind: "RangeExpr", expr: this.parseExpr(0, true) };
331
+ }
332
+
333
+ // Builtin / identifier
334
+ if (
335
+ t.type === T.IDENT ||
336
+ this.isTypeKeyword(t) ||
337
+ this.isBuiltinKeyword(t)
338
+ ) {
339
+ this.advance();
340
+ // Type conversion or composite lit: Type(expr) or Type{...}
341
+ if (this.isTypeKeyword(t) && this.check(T.LPAREN)) {
342
+ // type conversion: int(x), string(x), etc.
343
+ this.advance();
344
+ const expr = this.parseExpr();
345
+ this.expect(T.RPAREN);
346
+ return {
347
+ kind: "TypeConversion",
348
+ targetType: { kind: "TypeName", name: t.value },
349
+ expr,
350
+ };
351
+ }
352
+ return { kind: "Ident", name: t.value };
353
+ }
354
+
355
+ // Slice/array/map composite literals or type conversions:
356
+ // []int{1,2} → CompositeLit
357
+ // []byte(s), []rune(s) → TypeConversion
358
+ if (t.type === T.LBRACKET || t.type === T.MAP) {
359
+ const typeExpr = this.parseType();
360
+ if (this.check(T.LPAREN)) {
361
+ this.advance();
362
+ const expr = this.parseExpr();
363
+ this.expect(T.RPAREN);
364
+ return { kind: "TypeConversion", targetType: typeExpr, expr };
365
+ }
366
+ return this.parseCompositeLit(typeExpr);
367
+ }
368
+
369
+ // Anonymous struct composite literal: struct{Name string}{Name: "Alice"}
370
+ if (t.type === T.STRUCT) {
371
+ const typeExpr = this.parseStructType();
372
+ return this.parseCompositeLit(typeExpr);
373
+ }
374
+
375
+ if (t.type === T.CHAN) {
376
+ this.err("channels are not supported in GoFront");
377
+ }
378
+
379
+ this.err(`Unexpected token in expression: '${t.value}'`);
380
+ },
381
+
382
+ isTypeKeyword(t) {
383
+ return TYPE_KEYWORDS.has(t.value);
384
+ },
385
+
386
+ isBuiltinKeyword(t) {
387
+ return BUILTIN_KEYWORDS.has(t.value);
388
+ },
389
+ };