gofront 0.0.6 → 0.0.7
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 +80 -19
- package/package.json +7 -4
- package/src/codegen/expressions.js +252 -3
- package/src/codegen/statements.js +30 -26
- package/src/codegen.js +68 -5
- package/src/parser.js +0 -10
- package/src/typechecker/expressions.js +38 -9
- package/src/typechecker/stdlib.js +764 -0
- package/src/typechecker/types.js +15 -20
- package/src/typechecker.js +25 -601
package/src/codegen.js
CHANGED
|
@@ -35,6 +35,7 @@ export class CodeGen {
|
|
|
35
35
|
this.out = [];
|
|
36
36
|
this.indent = 0;
|
|
37
37
|
this.structNames = new Set();
|
|
38
|
+
this.namedWrapperNames = new Set();
|
|
38
39
|
this.jsImports = jsImports;
|
|
39
40
|
this.bundledPackages = bundledPackages;
|
|
40
41
|
this.namedReturnVars = null; // names of current function's named return vars
|
|
@@ -92,7 +93,7 @@ export class CodeGen {
|
|
|
92
93
|
}
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
// Build a map:
|
|
96
|
+
// Build a map: typeName → [MethodDecl, ...]
|
|
96
97
|
const methods = new Map();
|
|
97
98
|
for (const d of program.decls) {
|
|
98
99
|
if (d.kind === "MethodDecl") {
|
|
@@ -102,6 +103,18 @@ export class CodeGen {
|
|
|
102
103
|
}
|
|
103
104
|
}
|
|
104
105
|
|
|
106
|
+
// Collect named non-struct types that have methods (emitted as wrapper classes)
|
|
107
|
+
for (const d of program.decls) {
|
|
108
|
+
if (
|
|
109
|
+
d.kind === "TypeDecl" &&
|
|
110
|
+
d.type.kind !== "StructType" &&
|
|
111
|
+
d.type.kind !== "InterfaceType" &&
|
|
112
|
+
(methods.get(d.name) ?? []).length > 0
|
|
113
|
+
) {
|
|
114
|
+
this.namedWrapperNames.add(d.name);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
105
118
|
// Emit ESM import statements for npm packages
|
|
106
119
|
for (const [importPath, names] of this.jsImports) {
|
|
107
120
|
if (names.length === 0) continue;
|
|
@@ -248,13 +261,41 @@ export class CodeGen {
|
|
|
248
261
|
} else if (decl.type.kind === "InterfaceType") {
|
|
249
262
|
// Interfaces are compile-time only — no JS output needed.
|
|
250
263
|
this.line(`// interface ${decl.name} (compile-time only)`);
|
|
264
|
+
} else if (methodDecls.length > 0) {
|
|
265
|
+
// Named non-struct type with methods — emit an ES6 wrapper class.
|
|
266
|
+
this.genNamedTypeClass(decl.name, methodDecls);
|
|
251
267
|
} else {
|
|
252
|
-
// Type alias — no JS equivalent needed unless it's used as a constructor.
|
|
253
|
-
// e.g. type MyInt int → just a comment
|
|
254
268
|
this.line(`// type ${decl.name} = ${this.typeComment(decl.type)}`);
|
|
255
269
|
}
|
|
256
270
|
}
|
|
257
271
|
|
|
272
|
+
genNamedTypeClass(name, methodDecls) {
|
|
273
|
+
const namedType = this.checker?.types.get(name);
|
|
274
|
+
const underlying = namedType?.underlying;
|
|
275
|
+
let field, ctorDefault;
|
|
276
|
+
if (underlying?.kind === "func") {
|
|
277
|
+
field = "_fn";
|
|
278
|
+
ctorDefault = "null";
|
|
279
|
+
} else if (underlying?.kind === "map") {
|
|
280
|
+
field = "_map";
|
|
281
|
+
ctorDefault = "{}";
|
|
282
|
+
} else {
|
|
283
|
+
field = "_items";
|
|
284
|
+
ctorDefault = "[]";
|
|
285
|
+
}
|
|
286
|
+
this.line(`class ${name} {`);
|
|
287
|
+
this.indented(() => {
|
|
288
|
+
this.line(
|
|
289
|
+
`constructor(${field} = ${ctorDefault}) { this.${field} = ${field}; }`,
|
|
290
|
+
);
|
|
291
|
+
for (const m of methodDecls) {
|
|
292
|
+
this.blank();
|
|
293
|
+
this.genMethod(m, field);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
this.line("}");
|
|
297
|
+
}
|
|
298
|
+
|
|
258
299
|
genStruct(name, structTypeAst, methodDecls) {
|
|
259
300
|
const fields = [];
|
|
260
301
|
if (this.checker) {
|
|
@@ -317,23 +358,45 @@ export class CodeGen {
|
|
|
317
358
|
this.line("}");
|
|
318
359
|
}
|
|
319
360
|
|
|
320
|
-
genMethod(decl) {
|
|
361
|
+
genMethod(decl, recvField = null) {
|
|
321
362
|
const params = decl.params.map((p) => p.name).join(", ");
|
|
322
363
|
const asyncPrefix = decl.async ? "async " : "";
|
|
323
364
|
this.line(`${asyncPrefix}${decl.name}(${params}) {`);
|
|
324
365
|
const prevBoxed = this._boxedVars;
|
|
325
366
|
this._boxedVars = new Set();
|
|
326
367
|
this._scanAddressTaken(decl.body);
|
|
368
|
+
const prevUnwrapped = this._unwrappedRecv;
|
|
327
369
|
this.indented(() => {
|
|
328
370
|
if (decl.recvName && decl.recvName !== "_") {
|
|
329
|
-
|
|
371
|
+
if (recvField) {
|
|
372
|
+
this.line(`const ${decl.recvName} = this.${recvField};`);
|
|
373
|
+
this._unwrappedRecv = decl.recvName;
|
|
374
|
+
} else {
|
|
375
|
+
this.line(`const ${decl.recvName} = this;`);
|
|
376
|
+
}
|
|
330
377
|
}
|
|
331
378
|
this._withNamedReturns(decl, () => this._genBody(decl.body));
|
|
332
379
|
});
|
|
380
|
+
this._unwrappedRecv = prevUnwrapped;
|
|
333
381
|
this._boxedVars = prevBoxed;
|
|
334
382
|
this.line("}");
|
|
335
383
|
}
|
|
336
384
|
|
|
385
|
+
// Returns the wrapper field name ("_fn", "_items", "_map") if `type` is a named
|
|
386
|
+
// non-struct type emitted as a wrapper class, or null otherwise.
|
|
387
|
+
// Pass `expr` so we can skip unwrapping when the expression is the method receiver
|
|
388
|
+
// (which was already unwrapped to `this.<field>` at the top of the method body).
|
|
389
|
+
_namedWrapperField(type, expr = null) {
|
|
390
|
+
if (type?.kind !== "named") return null;
|
|
391
|
+
if (!this.namedWrapperNames.has(type.name)) return null;
|
|
392
|
+
if (expr?.kind === "Ident" && expr.name === this._unwrappedRecv)
|
|
393
|
+
return null;
|
|
394
|
+
const u = type.underlying;
|
|
395
|
+
if (u?.kind === "func") return "_fn";
|
|
396
|
+
if (u?.kind === "map") return "_map";
|
|
397
|
+
return "_items";
|
|
398
|
+
}
|
|
399
|
+
|
|
337
400
|
// ── Function declarations ────────────────────────────────────
|
|
338
401
|
|
|
339
402
|
genFuncDecl(decl, nameOverride) {
|
package/src/parser.js
CHANGED
|
@@ -308,16 +308,6 @@ export class Parser {
|
|
|
308
308
|
return this.looksLikeType(next) || next.type === T.ELLIPSIS;
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
-
// Heuristic: peek2 is a param-name separator if it's a comma, rparen, or a type token
|
|
312
|
-
isParamType(tok) {
|
|
313
|
-
if (!tok) return false;
|
|
314
|
-
// T.RPAREN is intentionally excluded: when peek2 is ")", the current IDENT
|
|
315
|
-
// is the type of an unnamed param (e.g. func(int)), not a param name.
|
|
316
|
-
return (
|
|
317
|
-
tok.type === T.COMMA || tok.type === T.ELLIPSIS || this.looksLikeType(tok)
|
|
318
|
-
);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
311
|
looksLikeType(tok) {
|
|
322
312
|
return (
|
|
323
313
|
tok.type === T.LBRACKET ||
|
|
@@ -111,8 +111,16 @@ export const expressionCheckMethods = {
|
|
|
111
111
|
if (expr.expr._isTypeRef) {
|
|
112
112
|
let base = baseType.kind === "named" ? baseType.underlying : baseType;
|
|
113
113
|
base = this.resolveType(base);
|
|
114
|
+
let methodType = null;
|
|
114
115
|
if (base?.kind === "struct" && base.methods?.has(expr.field)) {
|
|
115
|
-
|
|
116
|
+
methodType = base.methods.get(expr.field);
|
|
117
|
+
} else if (
|
|
118
|
+
baseType.kind === "named" &&
|
|
119
|
+
baseType.methods?.has(expr.field)
|
|
120
|
+
) {
|
|
121
|
+
methodType = baseType.methods.get(expr.field);
|
|
122
|
+
}
|
|
123
|
+
if (methodType) {
|
|
116
124
|
// Return a func type with the receiver prepended to params
|
|
117
125
|
expr._isMethodExpr = true;
|
|
118
126
|
expr._methodExprRecv = baseType;
|
|
@@ -129,7 +137,10 @@ export const expressionCheckMethods = {
|
|
|
129
137
|
if (ft?.kind === "func") {
|
|
130
138
|
let base = baseType.kind === "named" ? baseType.underlying : baseType;
|
|
131
139
|
base = this.resolveType(base);
|
|
132
|
-
if (
|
|
140
|
+
if (
|
|
141
|
+
(base?.kind === "struct" && base.methods?.has(expr.field)) ||
|
|
142
|
+
(baseType.kind === "named" && baseType.methods?.has(expr.field))
|
|
143
|
+
) {
|
|
133
144
|
expr._isMethodValue = true;
|
|
134
145
|
}
|
|
135
146
|
}
|
|
@@ -140,9 +151,14 @@ export const expressionCheckMethods = {
|
|
|
140
151
|
const bt = this.checkExpr(expr.expr, scope);
|
|
141
152
|
this.checkExpr(expr.index, scope);
|
|
142
153
|
if (isAny(bt)) return ANY;
|
|
143
|
-
|
|
154
|
+
// Named types (e.g. type Nums []int) are indexable via their underlying
|
|
155
|
+
const btu =
|
|
156
|
+
bt.kind === "named"
|
|
157
|
+
? (this.resolveType(bt.underlying) ?? bt.underlying)
|
|
158
|
+
: bt;
|
|
159
|
+
if (btu.kind === "slice" || btu.kind === "array") {
|
|
144
160
|
// Compile-time bounds checking for arrays
|
|
145
|
-
if (
|
|
161
|
+
if (btu.kind === "array" && btu.size != null) {
|
|
146
162
|
const idx = this._constIntValue(expr.index);
|
|
147
163
|
if (idx !== null) {
|
|
148
164
|
if (idx < 0) {
|
|
@@ -150,7 +166,7 @@ export const expressionCheckMethods = {
|
|
|
150
166
|
`invalid array index ${idx} (index must not be negative)`,
|
|
151
167
|
expr,
|
|
152
168
|
);
|
|
153
|
-
} else if (idx >=
|
|
169
|
+
} else if (idx >= btu.size) {
|
|
154
170
|
this.err(
|
|
155
171
|
`invalid array index ${idx} (out of bounds for ${typeStr(bt)})`,
|
|
156
172
|
expr,
|
|
@@ -158,11 +174,11 @@ export const expressionCheckMethods = {
|
|
|
158
174
|
}
|
|
159
175
|
}
|
|
160
176
|
}
|
|
161
|
-
return
|
|
177
|
+
return btu.elem;
|
|
162
178
|
}
|
|
163
|
-
if (
|
|
164
|
-
expr._mapValueType =
|
|
165
|
-
return
|
|
179
|
+
if (btu.kind === "map") {
|
|
180
|
+
expr._mapValueType = btu.value; // for codegen zero-value fallback
|
|
181
|
+
return btu.value;
|
|
166
182
|
}
|
|
167
183
|
if (isString(bt)) return INT; // byte
|
|
168
184
|
return this.err(`Cannot index type ${typeStr(bt)}`, expr);
|
|
@@ -410,6 +426,19 @@ export const expressionCheckMethods = {
|
|
|
410
426
|
|
|
411
427
|
if (isAny(fnType)) return ANY;
|
|
412
428
|
|
|
429
|
+
// Named type used as a constructor/conversion: Greeter(fn), NodeFunc(fn)
|
|
430
|
+
// The callee is a type name, not a callable variable.
|
|
431
|
+
if (fnType.kind === "named" && expr.func._isTypeRef) {
|
|
432
|
+
expr._isTypeConversion = true;
|
|
433
|
+
expr._conversionTargetType = fnType;
|
|
434
|
+
return fnType;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Named func types (e.g. type Handler func(int) int) are callable via variable
|
|
438
|
+
if (fnType.kind === "named" && fnType.underlying?.kind === "func") {
|
|
439
|
+
fnType = fnType.underlying;
|
|
440
|
+
}
|
|
441
|
+
|
|
413
442
|
// Generic function inference
|
|
414
443
|
if (fnType.kind === "generic") {
|
|
415
444
|
const typeArgs = this.inferTypeArgs(fnType, argTypes);
|