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.
- package/README.md +357 -33
- 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 +50 -851
- package/src/compiler.js +44 -23
- package/src/dts-parser.js +0 -4
- package/src/index.js +11 -20
- package/src/lexer.js +170 -12
- 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 +57 -745
- package/src/resolver.js +1 -1
- 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 +390 -647
|
@@ -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
|
+
};
|