gofront 0.0.3 → 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.
@@ -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.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
- }
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 = { kind: "SliceExpr", expr, low, high, max };
129
+ expr = {
130
+ kind: "InstantiationExpr",
131
+ expr,
132
+ typeArgs,
133
+ _line: expr._line,
134
+ };
131
135
  } else {
132
- this.expect(T.RBRACKET);
133
- expr = { kind: "IndexExpr", expr, index: low };
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" };
@@ -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("select statement is not supported in GoFront (no channels)");
75
- break;
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:
@@ -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
- return { kind: "InterfaceType", methods, embeds };
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
@@ -172,7 +172,7 @@ export class Parser {
172
172
  if (this.check(T.LPAREN)) {
173
173
  this.expect(T.LPAREN);
174
174
  const recvName =
175
- this.check(T.IDENT) && !this.isTypeName(this.peek2())
175
+ this.check(T.IDENT) && !this.isReceiverTerminator(this.peek2())
176
176
  ? this.advance().value
177
177
  : "_";
178
178
  this.match(T.STAR); // pointer receiver: (c *Counter) — strip *, treat same as value receiver
@@ -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 { kind: "FuncDecl", name, params, returnType, body, _line };
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() {
@@ -208,8 +221,8 @@ export class Parser {
208
221
  return decl;
209
222
  }
210
223
 
211
- // Returns true if tok looks like a type name token (for receiver parsing)
212
- isTypeName(tok) {
224
+ // Returns true if tok is a receiver-list terminator (i.e. not a type name)
225
+ isReceiverTerminator(tok) {
213
226
  return (
214
227
  tok &&
215
228
  (tok.type === T.RPAREN ||
@@ -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.isParamType(this.peek2())) {
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.isParamType(this.peek2())
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() {
@@ -383,31 +436,34 @@ export class Parser {
383
436
  const decls = [];
384
437
  if (this.match(T.LPAREN)) {
385
438
  let iotaVal = 0;
439
+ let prevRawExprs = null; // raw AST (with iota Idents) from the previous spec
440
+ let prevType = null;
386
441
  while (!this.check(T.RPAREN) && !this.check(T.EOF)) {
387
442
  const names = [this.expect(T.IDENT).value];
388
443
  while (this.match(T.COMMA)) names.push(this.expect(T.IDENT).value);
389
444
  let type = null;
390
- let value;
445
+ let rawExprs; // before iota substitution
391
446
  if (this.check(T.ASSIGN)) {
392
447
  this.advance();
393
- value = this.parseExprList();
448
+ rawExprs = this.parseExprList();
449
+ prevRawExprs = rawExprs;
450
+ prevType = null;
394
451
  } else if (!this.check(T.SEMICOLON) && !this.check(T.RPAREN)) {
395
452
  // optional type annotation before =
396
453
  type = this.parseType();
454
+ prevType = type;
397
455
  this.expect(T.ASSIGN);
398
- value = this.parseExprList();
456
+ rawExprs = this.parseExprList();
457
+ prevRawExprs = rawExprs;
399
458
  } else {
400
- // implicit iota
401
- value = [
459
+ // Omitted expression — repeat previous expression with updated iota
460
+ rawExprs = prevRawExprs ?? [
402
461
  { kind: "BasicLit", litKind: "INT", value: String(iotaVal) },
403
462
  ];
463
+ type = prevType ?? null;
404
464
  }
405
- // Replace iota identifier with current counter value
406
- value = value.map((v) =>
407
- v.kind === "Ident" && v.name === "iota"
408
- ? { kind: "BasicLit", litKind: "INT", value: String(iotaVal) }
409
- : v,
410
- );
465
+ // Substitute iota deeply in a cloned expression AST
466
+ const value = rawExprs.map((v) => this._substituteIota(v, iotaVal));
411
467
  decls.push({ names, type, value });
412
468
  iotaVal++;
413
469
  this.semi();
@@ -425,6 +481,25 @@ export class Parser {
425
481
  this.semi();
426
482
  return { kind: "ConstDecl", decls };
427
483
  }
484
+
485
+ // Deep-clone an expression AST, replacing `iota` identifiers with the given integer.
486
+ _substituteIota(node, val) {
487
+ if (!node || typeof node !== "object") return node;
488
+ if (node.kind === "Ident" && node.name === "iota") {
489
+ return { kind: "BasicLit", litKind: "INT", value: String(val) };
490
+ }
491
+ // Shallow-clone the node, then recursively substitute in all child properties
492
+ const clone = { ...node };
493
+ for (const key of Object.keys(clone)) {
494
+ const v = clone[key];
495
+ if (Array.isArray(v)) {
496
+ clone[key] = v.map((item) => this._substituteIota(item, val));
497
+ } else if (v && typeof v === "object" && v.kind) {
498
+ clone[key] = this._substituteIota(v, val);
499
+ }
500
+ }
501
+ return clone;
502
+ }
428
503
  }
429
504
 
430
505
  Object.assign(Parser.prototype, typeParserMethods);