gofront 0.0.4 → 0.0.5
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 +104 -126
- package/package.json +2 -3
- package/src/codegen/expressions.js +448 -256
- package/src/codegen/statements.js +193 -6
- package/src/codegen.js +89 -3
- package/src/dts-parser.js +1 -1
- package/src/index.js +13 -12
- package/src/lexer.js +24 -1
- package/src/minifier.js +698 -0
- package/src/parser/expressions.js +115 -15
- package/src/parser/statements.js +4 -4
- package/src/parser/types.js +108 -3
- package/src/parser.js +57 -4
- package/src/typechecker/expressions.js +279 -10
- package/src/typechecker/statements.js +64 -3
- package/src/typechecker/types.js +96 -10
- package/src/typechecker.js +482 -16
|
@@ -34,6 +34,8 @@ const TYPE_KEYWORDS = new Set([
|
|
|
34
34
|
"byte",
|
|
35
35
|
"rune",
|
|
36
36
|
"error",
|
|
37
|
+
"complex64",
|
|
38
|
+
"complex128",
|
|
37
39
|
]);
|
|
38
40
|
|
|
39
41
|
const BUILTIN_KEYWORDS = new Set([
|
|
@@ -48,6 +50,9 @@ const BUILTIN_KEYWORDS = new Set([
|
|
|
48
50
|
"println",
|
|
49
51
|
"panic",
|
|
50
52
|
"error",
|
|
53
|
+
"complex",
|
|
54
|
+
"real",
|
|
55
|
+
"imag",
|
|
51
56
|
]);
|
|
52
57
|
|
|
53
58
|
export const expressionParserMethods = {
|
|
@@ -115,22 +120,36 @@ export const expressionParserMethods = {
|
|
|
115
120
|
expr = { kind: "SelectorExpr", expr, field };
|
|
116
121
|
}
|
|
117
122
|
} else if (t.type === T.LBRACKET) {
|
|
118
|
-
this
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
? null
|
|
124
|
-
: this.parseExpr();
|
|
125
|
-
let max = null;
|
|
126
|
-
if (this.match(T.COLON)) {
|
|
127
|
-
max = this.parseExpr();
|
|
128
|
-
}
|
|
123
|
+
// Check if this looks like generic type args: Ident[Type](...) or Ident[Type]{...}
|
|
124
|
+
if (expr.kind === "Ident" && this.looksLikeTypeArgList()) {
|
|
125
|
+
this.advance(); // consume [
|
|
126
|
+
const typeArgs = [this.parseType()];
|
|
127
|
+
while (this.match(T.COMMA)) typeArgs.push(this.parseType());
|
|
129
128
|
this.expect(T.RBRACKET);
|
|
130
|
-
expr = {
|
|
129
|
+
expr = {
|
|
130
|
+
kind: "InstantiationExpr",
|
|
131
|
+
expr,
|
|
132
|
+
typeArgs,
|
|
133
|
+
_line: expr._line,
|
|
134
|
+
};
|
|
131
135
|
} else {
|
|
132
|
-
this.
|
|
133
|
-
|
|
136
|
+
this.advance();
|
|
137
|
+
const low = this.check(T.COLON) ? null : this.parseExpr();
|
|
138
|
+
if (this.match(T.COLON)) {
|
|
139
|
+
const high =
|
|
140
|
+
this.check(T.RBRACKET) || this.check(T.COLON)
|
|
141
|
+
? null
|
|
142
|
+
: this.parseExpr();
|
|
143
|
+
let max = null;
|
|
144
|
+
if (this.match(T.COLON)) {
|
|
145
|
+
max = this.parseExpr();
|
|
146
|
+
}
|
|
147
|
+
this.expect(T.RBRACKET);
|
|
148
|
+
expr = { kind: "SliceExpr", expr, low, high, max };
|
|
149
|
+
} else {
|
|
150
|
+
this.expect(T.RBRACKET);
|
|
151
|
+
expr = { kind: "IndexExpr", expr, index: low };
|
|
152
|
+
}
|
|
134
153
|
}
|
|
135
154
|
} else if (t.type === T.LPAREN) {
|
|
136
155
|
expr = this.parseCall(expr);
|
|
@@ -153,10 +172,87 @@ export const expressionParserMethods = {
|
|
|
153
172
|
expr.kind === "Ident" ||
|
|
154
173
|
expr.kind === "SelectorExpr" ||
|
|
155
174
|
expr.kind === "TypeExpr" ||
|
|
156
|
-
expr.kind === "StructType"
|
|
175
|
+
expr.kind === "StructType" ||
|
|
176
|
+
expr.kind === "InstantiationExpr"
|
|
157
177
|
);
|
|
158
178
|
},
|
|
159
179
|
|
|
180
|
+
// Lookahead: does the [ ... ] after an Ident look like generic type args?
|
|
181
|
+
// Returns true if: only type-like tokens inside brackets, AND followed by a valid continuation
|
|
182
|
+
looksLikeTypeArgList() {
|
|
183
|
+
let i = this.pos + 1; // skip [
|
|
184
|
+
const src = this.tokens;
|
|
185
|
+
let depth = 1;
|
|
186
|
+
let hasComma = false;
|
|
187
|
+
let hasTypeKeyword = false;
|
|
188
|
+
while (i < src.length && depth > 0) {
|
|
189
|
+
const tt = src[i].type;
|
|
190
|
+
if (tt === T.LBRACKET) {
|
|
191
|
+
depth++;
|
|
192
|
+
i++;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (tt === T.RBRACKET) {
|
|
196
|
+
depth--;
|
|
197
|
+
if (depth === 0) break;
|
|
198
|
+
i++;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
// Reject literals — it's an index expression
|
|
202
|
+
if (tt === T.INT || tt === T.FLOAT || tt === T.STRING) return false;
|
|
203
|
+
// 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;
|
|
227
|
+
if (depth === 1) {
|
|
228
|
+
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
|
|
238
|
+
if (tt === T.IDENT && TYPE_KEYWORDS.has(src[i].value))
|
|
239
|
+
hasTypeKeyword = true;
|
|
240
|
+
}
|
|
241
|
+
i++;
|
|
242
|
+
}
|
|
243
|
+
if (depth !== 0) return false;
|
|
244
|
+
// Check what follows ]
|
|
245
|
+
const after = src[i + 1];
|
|
246
|
+
if (!after) return false;
|
|
247
|
+
// Call or composite lit — always valid as type args
|
|
248
|
+
if (after.type === T.LPAREN || after.type === T.LBRACE) return true;
|
|
249
|
+
// Function value in argument list: Identity[int], 99) — only if clearly a type
|
|
250
|
+
if (after.type === T.COMMA || after.type === T.RPAREN) {
|
|
251
|
+
return hasComma || hasTypeKeyword;
|
|
252
|
+
}
|
|
253
|
+
return false;
|
|
254
|
+
},
|
|
255
|
+
|
|
160
256
|
parseCall(fn) {
|
|
161
257
|
this.expect(T.LPAREN);
|
|
162
258
|
const args = [];
|
|
@@ -291,6 +387,10 @@ export const expressionParserMethods = {
|
|
|
291
387
|
this.advance();
|
|
292
388
|
return { kind: "BasicLit", litKind: "STRING", value: t.value };
|
|
293
389
|
}
|
|
390
|
+
if (t.type === T.IMAG) {
|
|
391
|
+
this.advance();
|
|
392
|
+
return { kind: "ImagLit", value: t.value };
|
|
393
|
+
}
|
|
294
394
|
if (t.type === T.TRUE) {
|
|
295
395
|
this.advance();
|
|
296
396
|
return { kind: "BasicLit", litKind: "BOOL", value: "true" };
|
package/src/parser/statements.js
CHANGED
|
@@ -68,11 +68,11 @@ export const statementParserMethods = {
|
|
|
68
68
|
case T.SWITCH:
|
|
69
69
|
return this.parseSwitch();
|
|
70
70
|
case T.GO:
|
|
71
|
-
this.err("goroutines are not supported in GoFront");
|
|
72
|
-
break;
|
|
71
|
+
return this.err("goroutines are not supported in GoFront");
|
|
73
72
|
case T.SELECT:
|
|
74
|
-
this.err(
|
|
75
|
-
|
|
73
|
+
return this.err(
|
|
74
|
+
"select statement is not supported in GoFront (no channels)",
|
|
75
|
+
);
|
|
76
76
|
case T.LBRACE:
|
|
77
77
|
return this.parseBlock();
|
|
78
78
|
case T.IDENT:
|
package/src/parser/types.js
CHANGED
|
@@ -20,8 +20,7 @@ export const typeParserMethods = {
|
|
|
20
20
|
this.advance();
|
|
21
21
|
return { kind: "PointerType", base: this.parseType() };
|
|
22
22
|
case T.CHAN:
|
|
23
|
-
this.err("channels are not supported in GoFront");
|
|
24
|
-
break;
|
|
23
|
+
return this.err("channels are not supported in GoFront");
|
|
25
24
|
case T.IDENT:
|
|
26
25
|
case "int":
|
|
27
26
|
case "float64":
|
|
@@ -45,9 +44,96 @@ export const typeParserMethods = {
|
|
|
45
44
|
this.advance();
|
|
46
45
|
name += `.${this.expect(T.IDENT).value}`;
|
|
47
46
|
}
|
|
47
|
+
// Generic type instantiation: Type[int, string]
|
|
48
|
+
if (this.check(T.LBRACKET) && this._looksLikeTypeArgListInType()) {
|
|
49
|
+
this.expect(T.LBRACKET);
|
|
50
|
+
const typeArgs = [this.parseType()];
|
|
51
|
+
while (this.match(T.COMMA)) typeArgs.push(this.parseType());
|
|
52
|
+
this.expect(T.RBRACKET);
|
|
53
|
+
return { kind: "GenericTypeName", name, typeArgs };
|
|
54
|
+
}
|
|
48
55
|
return { kind: "TypeName", name };
|
|
49
56
|
},
|
|
50
57
|
|
|
58
|
+
// Lookahead to distinguish Type[int] (generic) from [n]T (array) in type context.
|
|
59
|
+
// Scans from [ to matching ] looking for only type-like tokens.
|
|
60
|
+
_looksLikeTypeArgListInType() {
|
|
61
|
+
let i = this.pos + 1; // skip [
|
|
62
|
+
const src = this.tokens;
|
|
63
|
+
let depth = 1;
|
|
64
|
+
while (i < src.length && depth > 0) {
|
|
65
|
+
const tt = src[i].type;
|
|
66
|
+
if (tt === T.LBRACKET) {
|
|
67
|
+
depth++;
|
|
68
|
+
i++;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (tt === T.RBRACKET) {
|
|
72
|
+
depth--;
|
|
73
|
+
if (depth === 0) break;
|
|
74
|
+
i++;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
// Reject literals — it's an index/array expression
|
|
78
|
+
if (tt === T.INT || tt === T.FLOAT || tt === T.STRING) return false;
|
|
79
|
+
i++;
|
|
80
|
+
}
|
|
81
|
+
return depth === 0;
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
parseTypeParamList() {
|
|
85
|
+
this.expect(T.LBRACKET);
|
|
86
|
+
const params = [];
|
|
87
|
+
do {
|
|
88
|
+
const name = this.expect(T.IDENT).value;
|
|
89
|
+
const constraint = this.parseConstraint();
|
|
90
|
+
params.push({ kind: "TypeParam", name, constraint });
|
|
91
|
+
} while (this.match(T.COMMA));
|
|
92
|
+
this.expect(T.RBRACKET);
|
|
93
|
+
return params;
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
parseConstraint() {
|
|
97
|
+
// Union constraint: ~int | ~string or int | string
|
|
98
|
+
if (
|
|
99
|
+
this.check(T.TILDE) ||
|
|
100
|
+
(this.check(T.IDENT) && this._looksLikeUnionConstraint())
|
|
101
|
+
) {
|
|
102
|
+
return this._parseUnionConstraint();
|
|
103
|
+
}
|
|
104
|
+
// Single constraint: any, comparable, or named type
|
|
105
|
+
return this.parseType();
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
_looksLikeUnionConstraint() {
|
|
109
|
+
// Check if after the identifier there's a | before the next , or ]
|
|
110
|
+
let i = this.pos + 1;
|
|
111
|
+
const src = this.tokens;
|
|
112
|
+
while (i < src.length) {
|
|
113
|
+
const tt = src[i].type;
|
|
114
|
+
if (tt === T.PIPE) return true;
|
|
115
|
+
if (
|
|
116
|
+
tt === T.COMMA ||
|
|
117
|
+
tt === T.RBRACKET ||
|
|
118
|
+
tt === T.SEMICOLON ||
|
|
119
|
+
tt === T.EOF
|
|
120
|
+
)
|
|
121
|
+
return false;
|
|
122
|
+
i++;
|
|
123
|
+
}
|
|
124
|
+
return false;
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
_parseUnionConstraint() {
|
|
128
|
+
const terms = [];
|
|
129
|
+
do {
|
|
130
|
+
const approx = this.match(T.TILDE);
|
|
131
|
+
const type = this.parseType();
|
|
132
|
+
terms.push({ approx, type });
|
|
133
|
+
} while (this.match(T.PIPE));
|
|
134
|
+
return { kind: "UnionConstraint", terms };
|
|
135
|
+
},
|
|
136
|
+
|
|
51
137
|
parseSliceOrArrayType() {
|
|
52
138
|
this.expect(T.LBRACKET);
|
|
53
139
|
if (this.check(T.RBRACKET)) {
|
|
@@ -116,13 +202,30 @@ export const typeParserMethods = {
|
|
|
116
202
|
this.expect(T.LBRACE);
|
|
117
203
|
const methods = [];
|
|
118
204
|
const embeds = [];
|
|
205
|
+
let unionConstraint = null;
|
|
119
206
|
while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
|
|
207
|
+
// Union constraint element: ~int | ~string
|
|
208
|
+
if (this.check(T.TILDE)) {
|
|
209
|
+
unionConstraint = this._parseUnionConstraint();
|
|
210
|
+
this.semi();
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
120
213
|
const name = this.expect(T.IDENT).value;
|
|
121
214
|
if (this.check(T.LPAREN)) {
|
|
122
215
|
// Method signature: Name(params) returnType
|
|
123
216
|
const params = this.parseParamList();
|
|
124
217
|
const returnType = this.parseReturnType();
|
|
125
218
|
methods.push({ name, params, returnType });
|
|
219
|
+
} else if (this.check(T.PIPE) || this.check(T.TILDE)) {
|
|
220
|
+
// Union constraint starting with a type name: int | string
|
|
221
|
+
// We already consumed the first ident, build the union
|
|
222
|
+
const terms = [{ approx: false, type: { kind: "TypeName", name } }];
|
|
223
|
+
while (this.match(T.PIPE)) {
|
|
224
|
+
const approx = this.match(T.TILDE);
|
|
225
|
+
const type = this.parseType();
|
|
226
|
+
terms.push({ approx, type });
|
|
227
|
+
}
|
|
228
|
+
unionConstraint = { kind: "UnionConstraint", terms };
|
|
126
229
|
} else {
|
|
127
230
|
// Embedded interface: TypeName or pkg.TypeName
|
|
128
231
|
let typeName = name;
|
|
@@ -135,6 +238,8 @@ export const typeParserMethods = {
|
|
|
135
238
|
this.semi();
|
|
136
239
|
}
|
|
137
240
|
this.expect(T.RBRACE);
|
|
138
|
-
|
|
241
|
+
const node = { kind: "InterfaceType", methods, embeds };
|
|
242
|
+
if (unionConstraint) node.unionConstraint = unionConstraint;
|
|
243
|
+
return node;
|
|
139
244
|
},
|
|
140
245
|
};
|
package/src/parser.js
CHANGED
|
@@ -195,10 +195,23 @@ export class Parser {
|
|
|
195
195
|
}
|
|
196
196
|
// plain function
|
|
197
197
|
const name = this.expect(T.IDENT).value;
|
|
198
|
+
// generic function: func Name[T any, U Stringer](...) ...
|
|
199
|
+
let typeParams = null;
|
|
200
|
+
if (this.check(T.LBRACKET) && this._looksLikeTypeParamList()) {
|
|
201
|
+
typeParams = this.parseTypeParamList();
|
|
202
|
+
}
|
|
198
203
|
const { params, returnType } = this.parseSignature();
|
|
199
204
|
const body = this.parseBlock();
|
|
200
205
|
this.semi();
|
|
201
|
-
return {
|
|
206
|
+
return {
|
|
207
|
+
kind: "FuncDecl",
|
|
208
|
+
name,
|
|
209
|
+
typeParams,
|
|
210
|
+
params,
|
|
211
|
+
returnType,
|
|
212
|
+
body,
|
|
213
|
+
_line,
|
|
214
|
+
};
|
|
202
215
|
}
|
|
203
216
|
|
|
204
217
|
parseAsyncFuncOrMethod() {
|
|
@@ -219,6 +232,24 @@ export class Parser {
|
|
|
219
232
|
);
|
|
220
233
|
}
|
|
221
234
|
|
|
235
|
+
// Lookahead: does the [ ... ] after a func name or type name look like a type param list?
|
|
236
|
+
// Type param lists contain IDENT followed by a constraint (another IDENT or keyword),
|
|
237
|
+
// not expressions like integers or operators.
|
|
238
|
+
_looksLikeTypeParamList() {
|
|
239
|
+
const i = this.pos + 1; // skip [
|
|
240
|
+
const src = this.tokens;
|
|
241
|
+
// First token must be an IDENT (the type param name)
|
|
242
|
+
if (!src[i] || src[i].type !== T.IDENT) return false;
|
|
243
|
+
// Second token should be a constraint (IDENT like 'any', 'comparable', another type name)
|
|
244
|
+
// or TILDE for union constraints
|
|
245
|
+
const second = src[i + 1];
|
|
246
|
+
if (!second) return false;
|
|
247
|
+
if (second.type === T.IDENT) return true; // T any, T Stringer
|
|
248
|
+
if (second.type === T.TILDE) return true; // T ~int
|
|
249
|
+
if (second.type === T.INTERFACE) return true; // T interface{...}
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
|
|
222
253
|
parseSignature() {
|
|
223
254
|
const params = this.parseParamList();
|
|
224
255
|
const returnType = this.parseReturnType();
|
|
@@ -234,13 +265,13 @@ export class Parser {
|
|
|
234
265
|
if (this.check(T.RPAREN)) break;
|
|
235
266
|
// Collect names, then type
|
|
236
267
|
const names = [];
|
|
237
|
-
if (this.check(T.IDENT) && this.
|
|
268
|
+
if (this.check(T.IDENT) && this._isNamedParam()) {
|
|
238
269
|
// named: a, b int or ns ...int
|
|
239
270
|
names.push(this.advance().value);
|
|
240
271
|
while (
|
|
241
272
|
this.match(T.COMMA) &&
|
|
242
273
|
this.check(T.IDENT) &&
|
|
243
|
-
this.
|
|
274
|
+
this._isNamedParam()
|
|
244
275
|
) {
|
|
245
276
|
names.push(this.advance().value);
|
|
246
277
|
}
|
|
@@ -260,6 +291,23 @@ export class Parser {
|
|
|
260
291
|
return params;
|
|
261
292
|
}
|
|
262
293
|
|
|
294
|
+
// Lookahead: determine if current IDENT starts a named param (name type)
|
|
295
|
+
// vs an unnamed param (just a type). Scans IDENT (COMMA IDENT)* and checks
|
|
296
|
+
// if a type-start token follows — if so, the IDENTs are names; if ) or ,
|
|
297
|
+
// follows, the IDENTs are themselves types.
|
|
298
|
+
_isNamedParam() {
|
|
299
|
+
let pos = this.pos + 1; // skip current IDENT
|
|
300
|
+
while (
|
|
301
|
+
this.tokens[pos]?.type === T.COMMA &&
|
|
302
|
+
this.tokens[pos + 1]?.type === T.IDENT
|
|
303
|
+
) {
|
|
304
|
+
pos += 2;
|
|
305
|
+
}
|
|
306
|
+
const next = this.tokens[pos];
|
|
307
|
+
if (!next) return false;
|
|
308
|
+
return this.looksLikeType(next) || next.type === T.ELLIPSIS;
|
|
309
|
+
}
|
|
310
|
+
|
|
263
311
|
// Heuristic: peek2 is a param-name separator if it's a comma, rparen, or a type token
|
|
264
312
|
isParamType(tok) {
|
|
265
313
|
if (!tok) return false;
|
|
@@ -340,10 +388,15 @@ export class Parser {
|
|
|
340
388
|
return decls;
|
|
341
389
|
}
|
|
342
390
|
const name = this.expect(T.IDENT).value;
|
|
391
|
+
// generic type: type Name[T any] struct { ... }
|
|
392
|
+
let typeParams = null;
|
|
393
|
+
if (this.check(T.LBRACKET) && this._looksLikeTypeParamList()) {
|
|
394
|
+
typeParams = this.parseTypeParamList();
|
|
395
|
+
}
|
|
343
396
|
const isAlias = this.match(T.ASSIGN);
|
|
344
397
|
const type = this.parseType();
|
|
345
398
|
this.semi();
|
|
346
|
-
return { kind: "TypeDecl", name, type, isAlias };
|
|
399
|
+
return { kind: "TypeDecl", name, typeParams, type, isAlias };
|
|
347
400
|
}
|
|
348
401
|
|
|
349
402
|
parseVarDecl() {
|