gofront 1.0.0 → 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.
Files changed (38) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/src/cli-core.js +65 -0
  4. package/src/codegen/expressions.js +3 -0
  5. package/src/codegen/statements.js +3 -0
  6. package/src/codegen/stdlib/builder.js +3 -0
  7. package/src/codegen/stdlib/bytes.js +3 -0
  8. package/src/codegen/stdlib/errors.js +3 -0
  9. package/src/codegen/stdlib/fmt.js +3 -0
  10. package/src/codegen/stdlib/gom.js +3 -0
  11. package/src/codegen/stdlib/html.js +3 -0
  12. package/src/codegen/stdlib/index.js +3 -0
  13. package/src/codegen/stdlib/io.js +3 -0
  14. package/src/codegen/stdlib/maps.js +3 -0
  15. package/src/codegen/stdlib/math.js +3 -0
  16. package/src/codegen/stdlib/os.js +3 -0
  17. package/src/codegen/stdlib/path.js +3 -0
  18. package/src/codegen/stdlib/rand.js +3 -0
  19. package/src/codegen/stdlib/regexp.js +3 -0
  20. package/src/codegen/stdlib/slices.js +3 -0
  21. package/src/codegen/stdlib/sort.js +3 -0
  22. package/src/codegen/stdlib/strconv.js +3 -0
  23. package/src/codegen/stdlib/strings.js +3 -0
  24. package/src/codegen/stdlib/time.js +3 -0
  25. package/src/codegen/stdlib/unicode.js +3 -0
  26. package/src/codegen/stdlib/utf8.js +3 -0
  27. package/src/codegen/templ.js +3 -0
  28. package/src/compiler.js +63 -4
  29. package/src/index.js +32 -126
  30. package/src/parser/declarations.js +347 -0
  31. package/src/parser/index.js +3 -346
  32. package/src/typechecker/assignability.js +3 -0
  33. package/src/typechecker/expressions.js +9 -0
  34. package/src/typechecker/index.js +2 -1
  35. package/src/typechecker/resolve.js +9 -2
  36. package/src/typechecker/statements.js +3 -0
  37. package/src/typechecker/termination.js +3 -0
  38. package/src/typechecker/types.js +1 -0
@@ -1,6 +1,7 @@
1
1
  // Recursive-descent parser. Produces an AST from a GoFront token stream.
2
2
  //
3
3
  // Split into sub-modules under parser/:
4
+ // declarations.js — top-level declarations (func, method, type, var, const)
4
5
  // types.js — type expression parsing (slice, map, struct, interface, etc.)
5
6
  // statements.js — block, control flow, simple statements
6
7
  // expressions.js — operator precedence, unary, postfix, primary, literals
@@ -17,6 +18,7 @@
17
18
  // CompositeLit, KeyValueExpr, FuncLit, AwaitExpr
18
19
 
19
20
  import { T } from "../tokens.js";
21
+ import { declarationParseMethods } from "./declarations.js";
20
22
  import { expressionParserMethods } from "./expressions.js";
21
23
  import { statementParserMethods } from "./statements.js";
22
24
  import { typeParserMethods } from "./types.js";
@@ -151,354 +153,9 @@ export class Parser {
151
153
  this.semi();
152
154
  return { kind: "ImportDecl", imports };
153
155
  }
154
-
155
- // ── Top-level declarations ───────────────────────────────────
156
-
157
- parseTopDecl() {
158
- const t = this.peek();
159
- switch (t.type) {
160
- case T.ASYNC:
161
- return this.parseAsyncFuncOrMethod();
162
- case T.FUNC:
163
- return this.parseFuncOrMethod();
164
- case T.TYPE:
165
- return this.parseTypeDecl();
166
- case T.VAR:
167
- return this.parseVarDecl();
168
- case T.CONST:
169
- return this.parseConstDecl();
170
- default:
171
- this.err("Expected top-level declaration");
172
- }
173
- }
174
-
175
- parseFuncOrMethod() {
176
- const _line = this.peek().line;
177
- this.expect(T.FUNC);
178
- // method: func (recv Type) Name(...)
179
- if (this.check(T.LPAREN)) {
180
- this.expect(T.LPAREN);
181
- const recvName =
182
- this.check(T.IDENT) && !this.isReceiverTerminator(this.peek2())
183
- ? this.advance().value
184
- : "_";
185
- this.match(T.STAR); // pointer receiver: (c *Counter) — strip *, treat same as value receiver
186
- const recvType = this.parseTypeName();
187
- this.expect(T.RPAREN);
188
- const name = this.expect(T.IDENT).value;
189
- const { params, returnType } = this.parseSignature();
190
- const body = this.parseBlock();
191
- this.semi();
192
- return {
193
- kind: "MethodDecl",
194
- recvName,
195
- recvType,
196
- name,
197
- params,
198
- returnType,
199
- body,
200
- _line,
201
- };
202
- }
203
- // plain function
204
- const name = this.expect(T.IDENT).value;
205
- // generic function: func Name[T any, U Stringer](...) ...
206
- let typeParams = null;
207
- if (this.check(T.LBRACKET) && this._looksLikeTypeParamList()) {
208
- typeParams = this.parseTypeParamList();
209
- }
210
- const { params, returnType } = this.parseSignature();
211
- const body = this.parseBlock();
212
- this.semi();
213
- return {
214
- kind: "FuncDecl",
215
- name,
216
- typeParams,
217
- params,
218
- returnType,
219
- body,
220
- _line,
221
- };
222
- }
223
-
224
- parseAsyncFuncOrMethod() {
225
- this.expect(T.ASYNC);
226
- const decl = this.parseFuncOrMethod();
227
- decl.async = true;
228
- return decl;
229
- }
230
-
231
- // Returns true if tok is a receiver-list terminator (i.e. not a type name)
232
- isReceiverTerminator(tok) {
233
- return (
234
- tok &&
235
- (tok.type === T.RPAREN ||
236
- tok.type === T.COMMA ||
237
- tok.type === T.SEMICOLON ||
238
- tok.type === T.EOF)
239
- );
240
- }
241
-
242
- // Lookahead: does the [ ... ] after a func name or type name look like a type param list?
243
- // Type param lists contain IDENT followed by a constraint (another IDENT or keyword),
244
- // not expressions like integers or operators.
245
- _looksLikeTypeParamList() {
246
- const i = this.pos + 1; // skip [
247
- const src = this.tokens;
248
- // First token must be an IDENT (the type param name)
249
- if (!src[i] || src[i].type !== T.IDENT) return false;
250
- // Second token should be a constraint (IDENT like 'any', 'comparable', another type name)
251
- // or TILDE for union constraints
252
- const second = src[i + 1];
253
- if (!second) return false;
254
- if (second.type === T.IDENT) return true; // T any, T Stringer
255
- if (second.type === T.TILDE) return true; // T ~int
256
- if (second.type === T.INTERFACE) return true; // T interface{...}
257
- return false;
258
- }
259
-
260
- parseSignature() {
261
- const params = this.parseParamList();
262
- const returnType = this.parseReturnType();
263
- return { params, returnType };
264
- }
265
-
266
- // Parses (a, b int, c string) style param list
267
- parseParamList() {
268
- this.expect(T.LPAREN);
269
- const params = [];
270
- if (!this.check(T.RPAREN)) {
271
- do {
272
- if (this.check(T.RPAREN)) break;
273
- // Collect names, then type
274
- const names = [];
275
- if (this.check(T.IDENT) && this._isNamedParam()) {
276
- // named: a, b int or ns ...int
277
- names.push(this.advance().value);
278
- while (
279
- this.match(T.COMMA) &&
280
- this.check(T.IDENT) &&
281
- this._isNamedParam()
282
- ) {
283
- names.push(this.advance().value);
284
- }
285
- // variadic: name ...T
286
- const variadic = this.match(T.ELLIPSIS);
287
- const type = this.parseType();
288
- for (const n of names) params.push({ name: n, type, variadic });
289
- } else {
290
- // unnamed: just a type (possibly variadic: ...T)
291
- const variadic = this.match(T.ELLIPSIS);
292
- const type = this.parseType();
293
- params.push({ name: "_", type, variadic });
294
- }
295
- } while (this.match(T.COMMA));
296
- }
297
- this.expect(T.RPAREN);
298
- return params;
299
- }
300
-
301
- // Lookahead: determine if current IDENT starts a named param (name type)
302
- // vs an unnamed param (just a type). Scans IDENT (COMMA IDENT)* and checks
303
- // if a type-start token follows — if so, the IDENTs are names; if ) or ,
304
- // follows, the IDENTs are themselves types.
305
- _isNamedParam() {
306
- let pos = this.pos + 1; // skip current IDENT
307
- while (
308
- this.tokens[pos]?.type === T.COMMA &&
309
- this.tokens[pos + 1]?.type === T.IDENT
310
- ) {
311
- pos += 2;
312
- }
313
- const next = this.tokens[pos];
314
- if (!next) return false;
315
- return this.looksLikeType(next) || next.type === T.ELLIPSIS;
316
- }
317
-
318
- looksLikeType(tok) {
319
- return (
320
- tok.type === T.LBRACKET ||
321
- tok.type === T.STAR ||
322
- tok.type === T.MAP ||
323
- tok.type === T.FUNC ||
324
- tok.type === T.INTERFACE ||
325
- tok.type === T.STRUCT ||
326
- (tok.type === T.IDENT && tok.value !== "_")
327
- );
328
- }
329
-
330
- parseReturnType(inTypeExpr = false) {
331
- // No return type
332
- if (
333
- this.check(T.LBRACE) ||
334
- this.check(T.RBRACE) ||
335
- this.check(T.SEMICOLON) ||
336
- this.check(T.EOF)
337
- )
338
- return null;
339
- // Inside a type expression (e.g. func type in a tuple), `,` and `)` also end the type
340
- if (inTypeExpr && (this.check(T.COMMA) || this.check(T.RPAREN)))
341
- return null;
342
- // Multiple return values: (int, string) or named (result int, ok bool)
343
- if (this.check(T.LPAREN)) {
344
- this.advance();
345
- const types = [];
346
- const names = []; // non-null if named returns
347
- let isNamed = false;
348
- do {
349
- // Named returns: only treat IDENT as a name if peek2 is an actual type token
350
- if (this.check(T.IDENT) && this.looksLikeType(this.peek2())) {
351
- names.push(this.advance().value);
352
- isNamed = true;
353
- } else {
354
- names.push(null);
355
- }
356
- types.push(this.parseType());
357
- } while (this.match(T.COMMA));
358
- this.expect(T.RPAREN);
359
- const typeNode =
360
- types.length === 1 ? types[0] : { kind: "TupleType", types };
361
- // Attach named return info so typechecker can inject them into scope
362
- if (isNamed)
363
- typeNode._namedReturns = names.map((n, i) => ({
364
- name: n,
365
- type: types[i],
366
- }));
367
- return typeNode;
368
- }
369
- return this.parseType();
370
- }
371
-
372
- parseTypeDecl() {
373
- this.expect(T.TYPE);
374
- if (this.match(T.LPAREN)) {
375
- const decls = [];
376
- while (!this.check(T.RPAREN) && !this.check(T.EOF)) {
377
- const name = this.expect(T.IDENT).value;
378
- const isAlias = this.match(T.ASSIGN);
379
- const type = this.parseType();
380
- decls.push({ kind: "TypeDecl", name, type, isAlias });
381
- this.semi();
382
- }
383
- this.expect(T.RPAREN);
384
- this.semi();
385
- return decls;
386
- }
387
- const name = this.expect(T.IDENT).value;
388
- // generic type: type Name[T any] struct { ... }
389
- let typeParams = null;
390
- if (this.check(T.LBRACKET) && this._looksLikeTypeParamList()) {
391
- typeParams = this.parseTypeParamList();
392
- }
393
- const isAlias = this.match(T.ASSIGN);
394
- const type = this.parseType();
395
- this.semi();
396
- return { kind: "TypeDecl", name, typeParams, type, isAlias };
397
- }
398
-
399
- parseVarDecl() {
400
- this.expect(T.VAR);
401
- const decls = [];
402
- if (this.match(T.LPAREN)) {
403
- while (!this.check(T.RPAREN) && !this.check(T.EOF)) {
404
- decls.push(this.parseVarSpec());
405
- this.semi();
406
- }
407
- this.expect(T.RPAREN);
408
- } else {
409
- decls.push(this.parseVarSpec());
410
- }
411
- this.semi();
412
- return { kind: "VarDecl", decls };
413
- }
414
-
415
- parseVarSpec() {
416
- const names = [this.expect(T.IDENT).value];
417
- while (this.match(T.COMMA)) names.push(this.expect(T.IDENT).value);
418
- let type = null;
419
- let value = null;
420
- if (
421
- !this.check(T.ASSIGN) &&
422
- !this.check(T.SEMICOLON) &&
423
- !this.check(T.RPAREN)
424
- ) {
425
- type = this.parseType();
426
- }
427
- if (this.match(T.ASSIGN)) value = this.parseExprList();
428
- return { names, type, value };
429
- }
430
-
431
- parseConstDecl() {
432
- this.expect(T.CONST);
433
- const decls = [];
434
- if (this.match(T.LPAREN)) {
435
- let iotaVal = 0;
436
- let prevRawExprs = null; // raw AST (with iota Idents) from the previous spec
437
- let prevType = null;
438
- while (!this.check(T.RPAREN) && !this.check(T.EOF)) {
439
- const names = [this.expect(T.IDENT).value];
440
- while (this.match(T.COMMA)) names.push(this.expect(T.IDENT).value);
441
- let type = null;
442
- let rawExprs; // before iota substitution
443
- if (this.check(T.ASSIGN)) {
444
- this.advance();
445
- rawExprs = this.parseExprList();
446
- prevRawExprs = rawExprs;
447
- prevType = null;
448
- } else if (!this.check(T.SEMICOLON) && !this.check(T.RPAREN)) {
449
- // optional type annotation before =
450
- type = this.parseType();
451
- prevType = type;
452
- this.expect(T.ASSIGN);
453
- rawExprs = this.parseExprList();
454
- prevRawExprs = rawExprs;
455
- } else {
456
- // Omitted expression — repeat previous expression with updated iota
457
- rawExprs = prevRawExprs ?? [
458
- { kind: "BasicLit", litKind: "INT", value: String(iotaVal) },
459
- ];
460
- type = prevType ?? null;
461
- }
462
- // Substitute iota deeply in a cloned expression AST
463
- const value = rawExprs.map((v) => this._substituteIota(v, iotaVal));
464
- decls.push({ names, type, value });
465
- iotaVal++;
466
- this.semi();
467
- }
468
- this.expect(T.RPAREN);
469
- } else {
470
- const names = [this.expect(T.IDENT).value];
471
- while (this.match(T.COMMA)) names.push(this.expect(T.IDENT).value);
472
- let type = null;
473
- if (!this.check(T.ASSIGN)) type = this.parseType();
474
- this.expect(T.ASSIGN);
475
- const value = this.parseExprList();
476
- decls.push({ names, type, value });
477
- }
478
- this.semi();
479
- return { kind: "ConstDecl", decls };
480
- }
481
-
482
- // Deep-clone an expression AST, replacing `iota` identifiers with the given integer.
483
- _substituteIota(node, val) {
484
- if (!node || typeof node !== "object") return node;
485
- if (node.kind === "Ident" && node.name === "iota") {
486
- return { kind: "BasicLit", litKind: "INT", value: String(val) };
487
- }
488
- // Shallow-clone the node, then recursively substitute in all child properties
489
- const clone = { ...node };
490
- for (const key of Object.keys(clone)) {
491
- const v = clone[key];
492
- if (Array.isArray(v)) {
493
- clone[key] = v.map((item) => this._substituteIota(item, val));
494
- } else if (v && typeof v === "object" && v.kind) {
495
- clone[key] = this._substituteIota(v, val);
496
- }
497
- }
498
- return clone;
499
- }
500
156
  }
501
157
 
502
158
  Object.assign(Parser.prototype, typeParserMethods);
503
159
  Object.assign(Parser.prototype, statementParserMethods);
504
160
  Object.assign(Parser.prototype, expressionParserMethods);
161
+ Object.assign(Parser.prototype, declarationParseMethods);
@@ -24,6 +24,8 @@ import {
24
24
  UNTYPED_INT,
25
25
  } from "./types.js";
26
26
 
27
+ /** @typedef {import('./index.js').TypeChecker} TypeChecker */
28
+
27
29
  // Untyped constant assignability: maps source.base → Set of compatible target.name values
28
30
  const UNTYPED_COMPAT = {
29
31
  int: new Set(["int", "float64", "complex128", "complex64"]),
@@ -33,6 +35,7 @@ const UNTYPED_COMPAT = {
33
35
  complex128: new Set(["complex128", "complex64"]),
34
36
  };
35
37
 
38
+ /** @type {ThisType<TypeChecker>} */
36
39
  export const assignabilityMethods = {
37
40
  // ── Binary result types ───────────────────────────────────────────
38
41
 
@@ -17,6 +17,7 @@ import {
17
17
  NIL,
18
18
  Scope,
19
19
  STRING,
20
+ TAINTED_ANY,
20
21
  typeStr,
21
22
  UNTYPED_BOOL,
22
23
  UNTYPED_COMPLEX,
@@ -26,6 +27,8 @@ import {
26
27
  VOID,
27
28
  } from "./types.js";
28
29
 
30
+ /** @typedef {import('./index.js').TypeChecker} TypeChecker */
31
+
29
32
  // Dispatch table for builtin function type checking.
30
33
  // Each value is (self, name, expr, argTypes, scope) => returnType.
31
34
  const BUILTIN_CHECK = {
@@ -80,6 +83,7 @@ const CHECK_EXPR_DELEGATE = {
80
83
  InstantiationExpr: "_checkInstantiationExpr",
81
84
  };
82
85
 
86
+ /** @type {ThisType<TypeChecker>} */
83
87
  export const expressionCheckMethods = {
84
88
  checkExpr(expr, scope) {
85
89
  const t = this._checkExpr(expr, scope);
@@ -115,6 +119,7 @@ export const expressionCheckMethods = {
115
119
  case "BinaryExpr": {
116
120
  const lt = this.checkExpr(expr.left, scope);
117
121
  const rt = this.checkExpr(expr.right, scope);
122
+ if (lt?._tainted || rt?._tainted) return TAINTED_ANY;
118
123
  if (isAny(lt) || isAny(rt))
119
124
  return this.binaryResultType(expr.op, ANY, ANY, expr);
120
125
  return this.binaryResultType(expr.op, lt, rt, expr);
@@ -185,6 +190,7 @@ export const expressionCheckMethods = {
185
190
 
186
191
  _checkSelectorExpr(expr, scope) {
187
192
  const baseType = this.checkExpr(expr.expr, scope);
193
+ if (baseType?._tainted) return TAINTED_ANY;
188
194
  if (expr.expr._isTypeRef) {
189
195
  let base = baseType.kind === "named" ? baseType.underlying : baseType;
190
196
  base = this.resolveType(base);
@@ -230,6 +236,7 @@ export const expressionCheckMethods = {
230
236
  _checkIndexExpr(expr, scope) {
231
237
  const bt = this.checkExpr(expr.expr, scope);
232
238
  this.checkExpr(expr.index, scope);
239
+ if (bt?._tainted) return TAINTED_ANY;
233
240
  if (isAny(bt)) return ANY;
234
241
  const btu =
235
242
  bt.kind === "named"
@@ -252,6 +259,7 @@ export const expressionCheckMethods = {
252
259
  if (expr.low) this.checkExpr(expr.low, scope);
253
260
  if (expr.high) this.checkExpr(expr.high, scope);
254
261
  if (expr.max) this.checkExpr(expr.max, scope);
262
+ if (bt?._tainted) return TAINTED_ANY;
255
263
  if (isAny(bt)) return ANY;
256
264
  if (bt.kind === "slice" || bt.kind === "array")
257
265
  return { kind: "slice", elem: bt.elem };
@@ -468,6 +476,7 @@ export const expressionCheckMethods = {
468
476
 
469
477
  const argTypes = this._resolveCallArgTypes(expr, scope);
470
478
 
479
+ if (fnType?._tainted) return TAINTED_ANY;
471
480
  if (isAny(fnType)) return ANY;
472
481
 
473
482
  // Named type used as a constructor/conversion: Greeter(fn), NodeFunc(fn)
@@ -22,6 +22,7 @@ import {
22
22
  ERROR,
23
23
  isVoid,
24
24
  Scope,
25
+ TAINTED_ANY,
25
26
  TypeCheckError,
26
27
  typeStr,
27
28
  VOID,
@@ -124,7 +125,7 @@ export class TypeChecker {
124
125
  this._currentSource,
125
126
  );
126
127
  this.errors.push(e);
127
- return ANY; // recovery type
128
+ return TAINTED_ANY; // tainted recovery type — suppresses downstream cascade errors
128
129
  }
129
130
 
130
131
  _reportUnused(scope, node) {
@@ -7,10 +7,13 @@ import {
7
7
  defaultType,
8
8
  isAny,
9
9
  Scope,
10
+ TAINTED_ANY,
10
11
  typeStr,
11
12
  VOID,
12
13
  } from "./types.js";
13
14
 
15
+ /** @typedef {import('./index.js').TypeChecker} TypeChecker */
16
+
14
17
  // Dispatch table for substituteType — keyed by type.kind.
15
18
  const SUBSTITUTE_DISPATCH = {
16
19
  basic: (_s, type) => type,
@@ -69,6 +72,7 @@ const SUBSTITUTE_DISPATCH = {
69
72
  }),
70
73
  };
71
74
 
75
+ /** @type {ThisType<TypeChecker>} */
72
76
  export const resolveMethods = {
73
77
  resolveTypeNode(node, scope) {
74
78
  if (!node) return ANY;
@@ -217,7 +221,9 @@ export const resolveMethods = {
217
221
  }
218
222
  }
219
223
  }
220
- return { kind: "interface", methods };
224
+ const result = { kind: "interface", methods };
225
+ if (node.unionConstraint) result.unionConstraint = node.unionConstraint;
226
+ return result;
221
227
  },
222
228
 
223
229
  // ── Generics — substitution, instantiation, inference ────────────
@@ -354,7 +360,8 @@ export const resolveMethods = {
354
360
 
355
361
  fieldType(baseType, field, node) {
356
362
  baseType = this.resolveType(baseType);
357
- if (!baseType || isAny(baseType)) return ANY;
363
+ if (!baseType || isAny(baseType))
364
+ return baseType?._tainted ? TAINTED_ANY : ANY;
358
365
  if (this._isTransparentPointerField(baseType, field))
359
366
  baseType = this.resolveType(baseType.base);
360
367
  if (baseType.kind === "named" && baseType.methods?.has(field))
@@ -26,6 +26,9 @@ import {
26
26
  Scope,
27
27
  } from "./types.js";
28
28
 
29
+ /** @typedef {import('./index.js').TypeChecker} TypeChecker */
30
+
31
+ /** @type {ThisType<TypeChecker>} */
29
32
  export const statementCheckMethods = {
30
33
  checkBlock(block, scope, returnType) {
31
34
  for (const stmt of block.stmts) {
@@ -1,6 +1,9 @@
1
1
  // Termination analysis — determines whether a statement/block always returns.
2
2
  // Installed as a mixin on TypeChecker.prototype.
3
3
 
4
+ /** @typedef {import('./index.js').TypeChecker} TypeChecker */
5
+
6
+ /** @type {ThisType<TypeChecker>} */
4
7
  export const terminationMethods = {
5
8
  _isPanicCall(expr) {
6
9
  return (
@@ -48,6 +48,7 @@ export const FLOAT64 = { kind: "basic", name: "float64" };
48
48
  export const STRING = { kind: "basic", name: "string" };
49
49
  export const BOOL = { kind: "basic", name: "bool" };
50
50
  export const ANY = { kind: "basic", name: "any" };
51
+ export const TAINTED_ANY = { kind: "basic", name: "any", _tainted: true };
51
52
  export const VOID = { kind: "basic", name: "void" };
52
53
  export const NIL = { kind: "basic", name: "nil" };
53
54
  export const ERROR = {