gofront 0.0.9 → 1.0.1
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 +5 -5
- package/package.json +7 -4
- package/src/cli-core.js +65 -0
- package/src/codegen/expressions.js +523 -1717
- package/src/codegen/index.js +576 -0
- package/src/codegen/runtime.js +151 -0
- package/src/codegen/statements.js +308 -296
- package/src/codegen/stdlib/builder.js +39 -0
- package/src/codegen/stdlib/bytes.js +65 -0
- package/src/codegen/stdlib/errors.js +20 -0
- package/src/codegen/stdlib/fmt.js +104 -0
- package/src/codegen/stdlib/gom.js +155 -0
- package/src/codegen/stdlib/html.js +20 -0
- package/src/codegen/stdlib/index.js +98 -0
- package/src/codegen/stdlib/io.js +36 -0
- package/src/codegen/stdlib/maps.js +45 -0
- package/src/codegen/stdlib/math.js +56 -0
- package/src/codegen/stdlib/os.js +13 -0
- package/src/codegen/stdlib/path.js +34 -0
- package/src/codegen/stdlib/rand.js +27 -0
- package/src/codegen/stdlib/regexp.js +54 -0
- package/src/codegen/stdlib/slices.js +51 -0
- package/src/codegen/stdlib/sort.js +31 -0
- package/src/codegen/stdlib/strconv.js +33 -0
- package/src/codegen/stdlib/strings.js +86 -0
- package/src/codegen/stdlib/time.js +57 -0
- package/src/codegen/stdlib/unicode.js +25 -0
- package/src/codegen/stdlib/utf8.js +26 -0
- package/src/codegen/templ.js +284 -0
- package/src/compiler.js +142 -77
- package/src/dts-parser.js +355 -371
- package/src/index.js +32 -162
- package/src/lexer.js +186 -272
- package/src/minifier.js +305 -318
- package/src/{parser.js → parser/declarations.js} +18 -175
- package/src/parser/expressions.js +51 -34
- package/src/parser/index.js +161 -0
- package/src/parser/statements.js +24 -34
- package/src/parser/types.js +31 -29
- package/src/templ-parser.js +2 -2
- package/src/tokens.js +109 -0
- package/src/typechecker/assignability.js +261 -0
- package/src/typechecker/expressions.js +568 -549
- package/src/typechecker/index.js +556 -0
- package/src/typechecker/resolve.js +428 -0
- package/src/typechecker/statements.js +270 -280
- package/src/typechecker/stdlib/core.js +592 -0
- package/src/typechecker/stdlib/extended.js +332 -0
- package/src/typechecker/stdlib.js +9 -1060
- package/src/typechecker/termination.js +86 -0
- package/src/typechecker/types.js +1 -0
- package/src/codegen.js +0 -882
- package/src/typechecker.js +0 -1299
|
@@ -1,159 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
// Split into sub-modules under parser/:
|
|
4
|
-
// types.js — type expression parsing (slice, map, struct, interface, etc.)
|
|
5
|
-
// statements.js — block, control flow, simple statements
|
|
6
|
-
// expressions.js — operator precedence, unary, postfix, primary, literals
|
|
7
|
-
//
|
|
8
|
-
// Node kinds used throughout the compiler:
|
|
9
|
-
// Program, PackageDecl, ImportDecl
|
|
10
|
-
// FuncDecl, MethodDecl, VarDecl, ConstDecl, TypeDecl
|
|
11
|
-
// StructType, InterfaceType, SliceType, MapType, FuncType, PointerType
|
|
12
|
-
// Block, IfStmt, ForStmt, RangeStmt, ReturnStmt, BranchStmt
|
|
13
|
-
// AssignStmt, DefineStmt, IncDecStmt, ExprStmt
|
|
14
|
-
// SwitchStmt, CaseClause
|
|
15
|
-
// Ident, BasicLit, UnaryExpr, BinaryExpr
|
|
16
|
-
// CallExpr, SelectorExpr, IndexExpr, SliceExpr
|
|
17
|
-
// CompositeLit, KeyValueExpr, FuncLit, AwaitExpr
|
|
18
|
-
|
|
19
|
-
import { T } from "./lexer.js";
|
|
20
|
-
import { expressionParserMethods } from "./parser/expressions.js";
|
|
21
|
-
import { statementParserMethods } from "./parser/statements.js";
|
|
22
|
-
import { typeParserMethods } from "./parser/types.js";
|
|
23
|
-
|
|
24
|
-
export class ParseError extends Error {
|
|
25
|
-
constructor(msg, token, filename, sourceCode) {
|
|
26
|
-
const loc = filename
|
|
27
|
-
? `${filename}:${token.line}:${token.col}`
|
|
28
|
-
: `${token.line}:${token.col}`;
|
|
29
|
-
let lineContext = "";
|
|
30
|
-
if (token.line && sourceCode) {
|
|
31
|
-
const lines = sourceCode.split("\n");
|
|
32
|
-
const lineStr = lines[token.line - 1];
|
|
33
|
-
if (lineStr !== undefined) {
|
|
34
|
-
lineContext = `\n ${token.line} | ${lineStr}`;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
super(
|
|
38
|
-
`Parse error at ${loc}: ${msg} (got ${token.type} "${token.value}")${lineContext}`,
|
|
39
|
-
);
|
|
40
|
-
this.token = token;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export class Parser {
|
|
45
|
-
constructor(tokens, filename = null, source = null) {
|
|
46
|
-
this.tokens = tokens;
|
|
47
|
-
this.pos = 0;
|
|
48
|
-
this.filename = filename;
|
|
49
|
-
this.source = source;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// ── Primitives ───────────────────────────────────────────────
|
|
53
|
-
|
|
54
|
-
peek() {
|
|
55
|
-
return this.tokens[this.pos];
|
|
56
|
-
}
|
|
57
|
-
peek2() {
|
|
58
|
-
return this.tokens[this.pos + 1];
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
advance() {
|
|
62
|
-
const t = this.tokens[this.pos];
|
|
63
|
-
if (t.type !== T.EOF) this.pos++;
|
|
64
|
-
return t;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
check(type) {
|
|
68
|
-
return this.peek().type === type;
|
|
69
|
-
}
|
|
70
|
-
check2(type) {
|
|
71
|
-
return this.peek2().type === type;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
match(...types) {
|
|
75
|
-
for (const type of types) {
|
|
76
|
-
if (this.check(type)) {
|
|
77
|
-
this.advance();
|
|
78
|
-
return true;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
expect(type) {
|
|
85
|
-
if (!this.check(type))
|
|
86
|
-
throw new ParseError(`expected '${type}'`, this.peek());
|
|
87
|
-
return this.advance();
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
semi() {
|
|
91
|
-
if (this.check(T.SEMICOLON)) this.advance();
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
err(msg) {
|
|
95
|
-
throw new ParseError(msg, this.peek(), this.filename, this.source);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// ── Entry point ──────────────────────────────────────────────
|
|
99
|
-
|
|
100
|
-
parse() {
|
|
101
|
-
const pkg = this.parsePackage();
|
|
102
|
-
const imports = [];
|
|
103
|
-
while (this.check(T.IMPORT)) imports.push(this.parseImport());
|
|
104
|
-
const decls = [];
|
|
105
|
-
while (!this.check(T.EOF)) {
|
|
106
|
-
const result = this.parseTopDecl();
|
|
107
|
-
if (Array.isArray(result)) decls.push(...result);
|
|
108
|
-
else decls.push(result);
|
|
109
|
-
}
|
|
110
|
-
return {
|
|
111
|
-
kind: "Program",
|
|
112
|
-
pkg,
|
|
113
|
-
imports,
|
|
114
|
-
decls,
|
|
115
|
-
_filename: this.filename,
|
|
116
|
-
_source: this.source,
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
parsePackage() {
|
|
121
|
-
this.expect(T.PACKAGE);
|
|
122
|
-
const name = this.expect(T.IDENT).value;
|
|
123
|
-
this.semi();
|
|
124
|
-
return { kind: "PackageDecl", name };
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
parseImport() {
|
|
128
|
-
this.expect(T.IMPORT);
|
|
129
|
-
const imports = [];
|
|
130
|
-
const parseOne = () => {
|
|
131
|
-
const _line = this.peek().line;
|
|
132
|
-
let alias = null;
|
|
133
|
-
if (this.check(T.IDENT)) {
|
|
134
|
-
alias = this.advance().value;
|
|
135
|
-
} else if (this.check(T.DOT)) {
|
|
136
|
-
alias = ".";
|
|
137
|
-
this.advance();
|
|
138
|
-
}
|
|
139
|
-
const path = this.expect(T.STRING).value;
|
|
140
|
-
return { path, alias, _line };
|
|
141
|
-
};
|
|
142
|
-
if (this.match(T.LPAREN)) {
|
|
143
|
-
while (!this.check(T.RPAREN) && !this.check(T.EOF)) {
|
|
144
|
-
imports.push(parseOne());
|
|
145
|
-
this.semi();
|
|
146
|
-
}
|
|
147
|
-
this.expect(T.RPAREN);
|
|
148
|
-
} else {
|
|
149
|
-
imports.push(parseOne());
|
|
150
|
-
}
|
|
151
|
-
this.semi();
|
|
152
|
-
return { kind: "ImportDecl", imports };
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// ── Top-level declarations ───────────────────────────────────
|
|
1
|
+
import { T } from "../tokens.js";
|
|
156
2
|
|
|
3
|
+
export const declarationParseMethods = {
|
|
157
4
|
parseTopDecl() {
|
|
158
5
|
const t = this.peek();
|
|
159
6
|
switch (t.type) {
|
|
@@ -170,7 +17,7 @@ export class Parser {
|
|
|
170
17
|
default:
|
|
171
18
|
this.err("Expected top-level declaration");
|
|
172
19
|
}
|
|
173
|
-
}
|
|
20
|
+
},
|
|
174
21
|
|
|
175
22
|
parseFuncOrMethod() {
|
|
176
23
|
const _line = this.peek().line;
|
|
@@ -219,14 +66,14 @@ export class Parser {
|
|
|
219
66
|
body,
|
|
220
67
|
_line,
|
|
221
68
|
};
|
|
222
|
-
}
|
|
69
|
+
},
|
|
223
70
|
|
|
224
71
|
parseAsyncFuncOrMethod() {
|
|
225
72
|
this.expect(T.ASYNC);
|
|
226
73
|
const decl = this.parseFuncOrMethod();
|
|
227
74
|
decl.async = true;
|
|
228
75
|
return decl;
|
|
229
|
-
}
|
|
76
|
+
},
|
|
230
77
|
|
|
231
78
|
// Returns true if tok is a receiver-list terminator (i.e. not a type name)
|
|
232
79
|
isReceiverTerminator(tok) {
|
|
@@ -237,7 +84,7 @@ export class Parser {
|
|
|
237
84
|
tok.type === T.SEMICOLON ||
|
|
238
85
|
tok.type === T.EOF)
|
|
239
86
|
);
|
|
240
|
-
}
|
|
87
|
+
},
|
|
241
88
|
|
|
242
89
|
// Lookahead: does the [ ... ] after a func name or type name look like a type param list?
|
|
243
90
|
// Type param lists contain IDENT followed by a constraint (another IDENT or keyword),
|
|
@@ -255,13 +102,13 @@ export class Parser {
|
|
|
255
102
|
if (second.type === T.TILDE) return true; // T ~int
|
|
256
103
|
if (second.type === T.INTERFACE) return true; // T interface{...}
|
|
257
104
|
return false;
|
|
258
|
-
}
|
|
105
|
+
},
|
|
259
106
|
|
|
260
107
|
parseSignature() {
|
|
261
108
|
const params = this.parseParamList();
|
|
262
109
|
const returnType = this.parseReturnType();
|
|
263
110
|
return { params, returnType };
|
|
264
|
-
}
|
|
111
|
+
},
|
|
265
112
|
|
|
266
113
|
// Parses (a, b int, c string) style param list
|
|
267
114
|
parseParamList() {
|
|
@@ -296,7 +143,7 @@ export class Parser {
|
|
|
296
143
|
}
|
|
297
144
|
this.expect(T.RPAREN);
|
|
298
145
|
return params;
|
|
299
|
-
}
|
|
146
|
+
},
|
|
300
147
|
|
|
301
148
|
// Lookahead: determine if current IDENT starts a named param (name type)
|
|
302
149
|
// vs an unnamed param (just a type). Scans IDENT (COMMA IDENT)* and checks
|
|
@@ -313,7 +160,7 @@ export class Parser {
|
|
|
313
160
|
const next = this.tokens[pos];
|
|
314
161
|
if (!next) return false;
|
|
315
162
|
return this.looksLikeType(next) || next.type === T.ELLIPSIS;
|
|
316
|
-
}
|
|
163
|
+
},
|
|
317
164
|
|
|
318
165
|
looksLikeType(tok) {
|
|
319
166
|
return (
|
|
@@ -325,7 +172,7 @@ export class Parser {
|
|
|
325
172
|
tok.type === T.STRUCT ||
|
|
326
173
|
(tok.type === T.IDENT && tok.value !== "_")
|
|
327
174
|
);
|
|
328
|
-
}
|
|
175
|
+
},
|
|
329
176
|
|
|
330
177
|
parseReturnType(inTypeExpr = false) {
|
|
331
178
|
// No return type
|
|
@@ -367,7 +214,7 @@ export class Parser {
|
|
|
367
214
|
return typeNode;
|
|
368
215
|
}
|
|
369
216
|
return this.parseType();
|
|
370
|
-
}
|
|
217
|
+
},
|
|
371
218
|
|
|
372
219
|
parseTypeDecl() {
|
|
373
220
|
this.expect(T.TYPE);
|
|
@@ -394,7 +241,7 @@ export class Parser {
|
|
|
394
241
|
const type = this.parseType();
|
|
395
242
|
this.semi();
|
|
396
243
|
return { kind: "TypeDecl", name, typeParams, type, isAlias };
|
|
397
|
-
}
|
|
244
|
+
},
|
|
398
245
|
|
|
399
246
|
parseVarDecl() {
|
|
400
247
|
this.expect(T.VAR);
|
|
@@ -410,7 +257,7 @@ export class Parser {
|
|
|
410
257
|
}
|
|
411
258
|
this.semi();
|
|
412
259
|
return { kind: "VarDecl", decls };
|
|
413
|
-
}
|
|
260
|
+
},
|
|
414
261
|
|
|
415
262
|
parseVarSpec() {
|
|
416
263
|
const names = [this.expect(T.IDENT).value];
|
|
@@ -426,7 +273,7 @@ export class Parser {
|
|
|
426
273
|
}
|
|
427
274
|
if (this.match(T.ASSIGN)) value = this.parseExprList();
|
|
428
275
|
return { names, type, value };
|
|
429
|
-
}
|
|
276
|
+
},
|
|
430
277
|
|
|
431
278
|
parseConstDecl() {
|
|
432
279
|
this.expect(T.CONST);
|
|
@@ -477,7 +324,7 @@ export class Parser {
|
|
|
477
324
|
}
|
|
478
325
|
this.semi();
|
|
479
326
|
return { kind: "ConstDecl", decls };
|
|
480
|
-
}
|
|
327
|
+
},
|
|
481
328
|
|
|
482
329
|
// Deep-clone an expression AST, replacing `iota` identifiers with the given integer.
|
|
483
330
|
_substituteIota(node, val) {
|
|
@@ -496,9 +343,5 @@ export class Parser {
|
|
|
496
343
|
}
|
|
497
344
|
}
|
|
498
345
|
return clone;
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
Object.assign(Parser.prototype, typeParserMethods);
|
|
503
|
-
Object.assign(Parser.prototype, statementParserMethods);
|
|
504
|
-
Object.assign(Parser.prototype, expressionParserMethods);
|
|
346
|
+
},
|
|
347
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Parser expression methods — installed as a mixin on Parser.prototype.
|
|
2
2
|
|
|
3
|
-
import { T } from "../
|
|
3
|
+
import { T } from "../tokens.js";
|
|
4
4
|
|
|
5
5
|
// Binary operator precedence (higher = binds tighter, matching Go spec)
|
|
6
6
|
const PREC = {
|
|
@@ -55,6 +55,45 @@ const BUILTIN_KEYWORDS = new Set([
|
|
|
55
55
|
"imag",
|
|
56
56
|
]);
|
|
57
57
|
|
|
58
|
+
// Tokens that cannot legally appear inside a type argument list.
|
|
59
|
+
// Each || in an inline condition counts as CC; extracting here keeps looksLikeTypeArgList lean.
|
|
60
|
+
const DISALLOWED_IN_TYPE_ARGS = new Set([
|
|
61
|
+
T.PLUS,
|
|
62
|
+
T.MINUS,
|
|
63
|
+
T.PERCENT,
|
|
64
|
+
T.SLASH,
|
|
65
|
+
T.AND,
|
|
66
|
+
T.OR,
|
|
67
|
+
T.CARET,
|
|
68
|
+
T.NOT,
|
|
69
|
+
T.LT,
|
|
70
|
+
T.GT,
|
|
71
|
+
T.LTE,
|
|
72
|
+
T.GTE,
|
|
73
|
+
T.EQ,
|
|
74
|
+
T.NEQ,
|
|
75
|
+
T.LSHIFT,
|
|
76
|
+
T.RSHIFT,
|
|
77
|
+
T.AND_NOT,
|
|
78
|
+
T.COLON,
|
|
79
|
+
T.DEFINE,
|
|
80
|
+
T.ASSIGN,
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
function _isDisallowedInTypeArgs(tt) {
|
|
84
|
+
return DISALLOWED_IN_TYPE_ARGS.has(tt);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function _isTypeIntroducerToken(tt) {
|
|
88
|
+
return (
|
|
89
|
+
tt === T.STAR ||
|
|
90
|
+
tt === T.MAP ||
|
|
91
|
+
tt === T.FUNC ||
|
|
92
|
+
tt === T.INTERFACE ||
|
|
93
|
+
tt === T.STRUCT
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
58
97
|
export const expressionParserMethods = {
|
|
59
98
|
parseExprList() {
|
|
60
99
|
const exprs = [this.parseExpr()];
|
|
@@ -185,6 +224,7 @@ export const expressionParserMethods = {
|
|
|
185
224
|
let depth = 1;
|
|
186
225
|
let hasComma = false;
|
|
187
226
|
let hasTypeKeyword = false;
|
|
227
|
+
let hasUpperIdent = false;
|
|
188
228
|
while (i < src.length && depth > 0) {
|
|
189
229
|
const tt = src[i].type;
|
|
190
230
|
if (tt === T.LBRACKET) {
|
|
@@ -201,42 +241,19 @@ export const expressionParserMethods = {
|
|
|
201
241
|
// Reject literals — it's an index expression
|
|
202
242
|
if (tt === T.INT || tt === T.FLOAT || tt === T.STRING) return false;
|
|
203
243
|
// Reject binary operators that wouldn't appear in type args
|
|
204
|
-
if (
|
|
205
|
-
tt === T.PLUS ||
|
|
206
|
-
tt === T.MINUS ||
|
|
207
|
-
tt === T.PERCENT ||
|
|
208
|
-
tt === T.SLASH ||
|
|
209
|
-
tt === T.AND ||
|
|
210
|
-
tt === T.OR ||
|
|
211
|
-
tt === T.CARET ||
|
|
212
|
-
tt === T.NOT ||
|
|
213
|
-
tt === T.LT ||
|
|
214
|
-
tt === T.GT ||
|
|
215
|
-
tt === T.LTE ||
|
|
216
|
-
tt === T.GTE ||
|
|
217
|
-
tt === T.EQ ||
|
|
218
|
-
tt === T.NEQ ||
|
|
219
|
-
tt === T.LSHIFT ||
|
|
220
|
-
tt === T.RSHIFT ||
|
|
221
|
-
tt === T.AND_NOT ||
|
|
222
|
-
tt === T.COLON ||
|
|
223
|
-
tt === T.DEFINE ||
|
|
224
|
-
tt === T.ASSIGN
|
|
225
|
-
)
|
|
226
|
-
return false;
|
|
244
|
+
if (_isDisallowedInTypeArgs(tt)) return false;
|
|
227
245
|
if (depth === 1) {
|
|
228
246
|
if (tt === T.COMMA) hasComma = true;
|
|
229
|
-
if (
|
|
230
|
-
tt === T.STAR ||
|
|
231
|
-
tt === T.MAP ||
|
|
232
|
-
tt === T.FUNC ||
|
|
233
|
-
tt === T.INTERFACE ||
|
|
234
|
-
tt === T.STRUCT
|
|
235
|
-
)
|
|
236
|
-
hasTypeKeyword = true;
|
|
237
|
-
// Check if it's a builtin type name
|
|
247
|
+
if (_isTypeIntroducerToken(tt)) hasTypeKeyword = true;
|
|
238
248
|
if (tt === T.IDENT && TYPE_KEYWORDS.has(src[i].value))
|
|
239
249
|
hasTypeKeyword = true;
|
|
250
|
+
// User-defined types start with uppercase (GoFront convention)
|
|
251
|
+
if (
|
|
252
|
+
tt === T.IDENT &&
|
|
253
|
+
/^[A-Z]/.test(src[i].value) &&
|
|
254
|
+
!TYPE_KEYWORDS.has(src[i].value)
|
|
255
|
+
)
|
|
256
|
+
hasUpperIdent = true;
|
|
240
257
|
}
|
|
241
258
|
i++;
|
|
242
259
|
}
|
|
@@ -248,7 +265,7 @@ export const expressionParserMethods = {
|
|
|
248
265
|
if (after.type === T.LPAREN || after.type === T.LBRACE) return true;
|
|
249
266
|
// Function value in argument list: Identity[int], 99) — only if clearly a type
|
|
250
267
|
if (after.type === T.COMMA || after.type === T.RPAREN) {
|
|
251
|
-
return hasComma || hasTypeKeyword;
|
|
268
|
+
return hasComma || hasTypeKeyword || hasUpperIdent;
|
|
252
269
|
}
|
|
253
270
|
return false;
|
|
254
271
|
},
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// Recursive-descent parser. Produces an AST from a GoFront token stream.
|
|
2
|
+
//
|
|
3
|
+
// Split into sub-modules under parser/:
|
|
4
|
+
// declarations.js — top-level declarations (func, method, type, var, const)
|
|
5
|
+
// types.js — type expression parsing (slice, map, struct, interface, etc.)
|
|
6
|
+
// statements.js — block, control flow, simple statements
|
|
7
|
+
// expressions.js — operator precedence, unary, postfix, primary, literals
|
|
8
|
+
//
|
|
9
|
+
// Node kinds used throughout the compiler:
|
|
10
|
+
// Program, PackageDecl, ImportDecl
|
|
11
|
+
// FuncDecl, MethodDecl, VarDecl, ConstDecl, TypeDecl
|
|
12
|
+
// StructType, InterfaceType, SliceType, MapType, FuncType, PointerType
|
|
13
|
+
// Block, IfStmt, ForStmt, RangeStmt, ReturnStmt, BranchStmt
|
|
14
|
+
// AssignStmt, DefineStmt, IncDecStmt, ExprStmt
|
|
15
|
+
// SwitchStmt, CaseClause
|
|
16
|
+
// Ident, BasicLit, UnaryExpr, BinaryExpr
|
|
17
|
+
// CallExpr, SelectorExpr, IndexExpr, SliceExpr
|
|
18
|
+
// CompositeLit, KeyValueExpr, FuncLit, AwaitExpr
|
|
19
|
+
|
|
20
|
+
import { T } from "../tokens.js";
|
|
21
|
+
import { declarationParseMethods } from "./declarations.js";
|
|
22
|
+
import { expressionParserMethods } from "./expressions.js";
|
|
23
|
+
import { statementParserMethods } from "./statements.js";
|
|
24
|
+
import { typeParserMethods } from "./types.js";
|
|
25
|
+
|
|
26
|
+
export class ParseError extends Error {
|
|
27
|
+
constructor(msg, token, filename, sourceCode) {
|
|
28
|
+
const loc = filename
|
|
29
|
+
? `${filename}:${token.line}:${token.col}`
|
|
30
|
+
: `${token.line}:${token.col}`;
|
|
31
|
+
let lineContext = "";
|
|
32
|
+
if (token.line && sourceCode) {
|
|
33
|
+
const lines = sourceCode.split("\n");
|
|
34
|
+
const lineStr = lines[token.line - 1];
|
|
35
|
+
if (lineStr !== undefined) {
|
|
36
|
+
lineContext = `\n ${token.line} | ${lineStr}`;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
super(
|
|
40
|
+
`Parse error at ${loc}: ${msg} (got ${token.type} "${token.value}")${lineContext}`,
|
|
41
|
+
);
|
|
42
|
+
this.token = token;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class Parser {
|
|
47
|
+
constructor(tokens, filename = null, source = null) {
|
|
48
|
+
this.tokens = tokens;
|
|
49
|
+
this.pos = 0;
|
|
50
|
+
this.filename = filename;
|
|
51
|
+
this.source = source;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ── Primitives ───────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
peek() {
|
|
57
|
+
return this.tokens[this.pos];
|
|
58
|
+
}
|
|
59
|
+
peek2() {
|
|
60
|
+
return this.tokens[this.pos + 1];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
advance() {
|
|
64
|
+
const t = this.tokens[this.pos];
|
|
65
|
+
if (t.type !== T.EOF) this.pos++;
|
|
66
|
+
return t;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
check(type) {
|
|
70
|
+
return this.peek().type === type;
|
|
71
|
+
}
|
|
72
|
+
check2(type) {
|
|
73
|
+
return this.peek2().type === type;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
match(...types) {
|
|
77
|
+
for (const type of types) {
|
|
78
|
+
if (this.check(type)) {
|
|
79
|
+
this.advance();
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
expect(type) {
|
|
87
|
+
if (!this.check(type))
|
|
88
|
+
throw new ParseError(`expected '${type}'`, this.peek());
|
|
89
|
+
return this.advance();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
semi() {
|
|
93
|
+
if (this.check(T.SEMICOLON)) this.advance();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
err(msg) {
|
|
97
|
+
throw new ParseError(msg, this.peek(), this.filename, this.source);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ── Entry point ──────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
parse() {
|
|
103
|
+
const pkg = this.parsePackage();
|
|
104
|
+
const imports = [];
|
|
105
|
+
while (this.check(T.IMPORT)) imports.push(this.parseImport());
|
|
106
|
+
const decls = [];
|
|
107
|
+
while (!this.check(T.EOF)) {
|
|
108
|
+
const result = this.parseTopDecl();
|
|
109
|
+
if (Array.isArray(result)) decls.push(...result);
|
|
110
|
+
else decls.push(result);
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
kind: "Program",
|
|
114
|
+
pkg,
|
|
115
|
+
imports,
|
|
116
|
+
decls,
|
|
117
|
+
_filename: this.filename,
|
|
118
|
+
_source: this.source,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
parsePackage() {
|
|
123
|
+
this.expect(T.PACKAGE);
|
|
124
|
+
const name = this.expect(T.IDENT).value;
|
|
125
|
+
this.semi();
|
|
126
|
+
return { kind: "PackageDecl", name };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
parseImport() {
|
|
130
|
+
this.expect(T.IMPORT);
|
|
131
|
+
const imports = [];
|
|
132
|
+
const parseOne = () => {
|
|
133
|
+
const _line = this.peek().line;
|
|
134
|
+
let alias = null;
|
|
135
|
+
if (this.check(T.IDENT)) {
|
|
136
|
+
alias = this.advance().value;
|
|
137
|
+
} else if (this.check(T.DOT)) {
|
|
138
|
+
alias = ".";
|
|
139
|
+
this.advance();
|
|
140
|
+
}
|
|
141
|
+
const path = this.expect(T.STRING).value;
|
|
142
|
+
return { path, alias, _line };
|
|
143
|
+
};
|
|
144
|
+
if (this.match(T.LPAREN)) {
|
|
145
|
+
while (!this.check(T.RPAREN) && !this.check(T.EOF)) {
|
|
146
|
+
imports.push(parseOne());
|
|
147
|
+
this.semi();
|
|
148
|
+
}
|
|
149
|
+
this.expect(T.RPAREN);
|
|
150
|
+
} else {
|
|
151
|
+
imports.push(parseOne());
|
|
152
|
+
}
|
|
153
|
+
this.semi();
|
|
154
|
+
return { kind: "ImportDecl", imports };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
Object.assign(Parser.prototype, typeParserMethods);
|
|
159
|
+
Object.assign(Parser.prototype, statementParserMethods);
|
|
160
|
+
Object.assign(Parser.prototype, expressionParserMethods);
|
|
161
|
+
Object.assign(Parser.prototype, declarationParseMethods);
|
package/src/parser/statements.js
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
// Parser statement methods — installed as a mixin on Parser.prototype.
|
|
2
2
|
|
|
3
|
-
import { T } from "../
|
|
3
|
+
import { T } from "../tokens.js";
|
|
4
|
+
|
|
5
|
+
// Method-name dispatch for pure-delegation cases in _parseStmt.
|
|
6
|
+
const PARSE_STMT_DELEGATE = {
|
|
7
|
+
[T.VAR]: "parseVarDecl",
|
|
8
|
+
[T.CONST]: "parseConstDecl",
|
|
9
|
+
[T.TYPE]: "parseTypeDecl",
|
|
10
|
+
[T.RETURN]: "parseReturn",
|
|
11
|
+
[T.DEFER]: "parseDefer",
|
|
12
|
+
[T.IF]: "parseIf",
|
|
13
|
+
[T.FOR]: "parseFor",
|
|
14
|
+
[T.SWITCH]: "parseSwitch",
|
|
15
|
+
[T.LBRACE]: "parseBlock",
|
|
16
|
+
};
|
|
4
17
|
|
|
5
18
|
export const statementParserMethods = {
|
|
6
19
|
parseBlock() {
|
|
@@ -32,59 +45,36 @@ export const statementParserMethods = {
|
|
|
32
45
|
},
|
|
33
46
|
|
|
34
47
|
_parseStmt(t) {
|
|
48
|
+
const delegate = PARSE_STMT_DELEGATE[t.type];
|
|
49
|
+
if (delegate) return this[delegate]();
|
|
50
|
+
|
|
35
51
|
switch (t.type) {
|
|
36
|
-
case T.VAR:
|
|
37
|
-
return this.parseVarDecl();
|
|
38
|
-
case T.CONST:
|
|
39
|
-
return this.parseConstDecl();
|
|
40
|
-
case T.TYPE:
|
|
41
|
-
return this.parseTypeDecl();
|
|
42
|
-
case T.RETURN:
|
|
43
|
-
return this.parseReturn();
|
|
44
|
-
case T.DEFER:
|
|
45
|
-
return this.parseDefer();
|
|
46
52
|
case T.BREAK:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const label = this.advance().value;
|
|
50
|
-
this.semi();
|
|
51
|
-
return { kind: "BranchStmt", keyword: "break", label };
|
|
52
|
-
}
|
|
53
|
-
this.semi();
|
|
54
|
-
return { kind: "BranchStmt", keyword: "break" };
|
|
55
|
-
case T.CONTINUE:
|
|
56
|
-
this.advance();
|
|
53
|
+
case T.CONTINUE: {
|
|
54
|
+
const keyword = this.advance().value;
|
|
57
55
|
if (this.check(T.IDENT)) {
|
|
58
56
|
const label = this.advance().value;
|
|
59
57
|
this.semi();
|
|
60
|
-
return { kind: "BranchStmt", keyword
|
|
58
|
+
return { kind: "BranchStmt", keyword, label };
|
|
61
59
|
}
|
|
62
60
|
this.semi();
|
|
63
|
-
return { kind: "BranchStmt", keyword
|
|
61
|
+
return { kind: "BranchStmt", keyword };
|
|
62
|
+
}
|
|
64
63
|
case T.FALLTHROUGH:
|
|
65
64
|
this.advance();
|
|
66
65
|
this.semi();
|
|
67
66
|
return { kind: "BranchStmt", keyword: "fallthrough" };
|
|
68
|
-
case T.IF:
|
|
69
|
-
return this.parseIf();
|
|
70
|
-
case T.FOR:
|
|
71
|
-
return this.parseFor();
|
|
72
|
-
case T.SWITCH:
|
|
73
|
-
return this.parseSwitch();
|
|
74
67
|
case T.GO:
|
|
75
68
|
return this.err("goroutines are not supported in GoFront");
|
|
76
69
|
case T.SELECT:
|
|
77
70
|
return this.err(
|
|
78
71
|
"select statement is not supported in GoFront (no channels)",
|
|
79
72
|
);
|
|
80
|
-
case T.LBRACE:
|
|
81
|
-
return this.parseBlock();
|
|
82
73
|
case T.IDENT:
|
|
83
74
|
if (this.check2(T.COLON)) {
|
|
84
|
-
const label = this.advance().value;
|
|
75
|
+
const label = this.advance().value;
|
|
85
76
|
this.advance(); // consume COLON
|
|
86
|
-
|
|
87
|
-
return { kind: "LabeledStmt", label, body };
|
|
77
|
+
return { kind: "LabeledStmt", label, body: this.parseStmt() };
|
|
88
78
|
}
|
|
89
79
|
return this.parseSimpleStmt();
|
|
90
80
|
default:
|