gofront 1.6.0 → 1.7.0

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 (80) hide show
  1. package/README.md +35 -39
  2. package/package.json +37 -4
  3. package/src/backend/js/expressions.js +559 -144
  4. package/src/backend/js/index.js +174 -49
  5. package/src/backend/js/runtime.js +1518 -54
  6. package/src/backend/js/statements.js +91 -73
  7. package/src/backend/js/stdlib/base64.js +12 -0
  8. package/src/backend/js/stdlib/binary.js +102 -0
  9. package/src/backend/js/stdlib/bits.js +15 -1
  10. package/src/backend/js/stdlib/builder.js +28 -9
  11. package/src/backend/js/stdlib/bytes.js +75 -18
  12. package/src/backend/js/stdlib/errors.js +25 -1
  13. package/src/backend/js/stdlib/fmt.js +70 -13
  14. package/src/backend/js/stdlib/hex.js +17 -0
  15. package/src/backend/js/stdlib/html.js +1 -1
  16. package/src/backend/js/stdlib/index.js +28 -0
  17. package/src/backend/js/stdlib/io.js +105 -3
  18. package/src/backend/js/stdlib/json.js +972 -0
  19. package/src/backend/js/stdlib/math.js +13 -1
  20. package/src/backend/js/stdlib/path.js +18 -4
  21. package/src/backend/js/stdlib/rand.js +10 -5
  22. package/src/backend/js/stdlib/sha256.js +12 -0
  23. package/src/backend/js/stdlib/simd.js +24 -0
  24. package/src/backend/js/stdlib/slices.js +52 -26
  25. package/src/backend/js/stdlib/strconv.js +21 -5
  26. package/src/backend/js/stdlib/strings.js +148 -15
  27. package/src/backend/js/stdlib/sync.js +13 -0
  28. package/src/backend/js/stdlib/time.js +90 -19
  29. package/src/backend/js/stdlib/unicode.js +2 -2
  30. package/src/backend/wasm/boundary.js +1225 -41
  31. package/src/backend/wasm/const-fold.js +205 -0
  32. package/src/backend/wasm/emit-builtins.js +220 -76
  33. package/src/backend/wasm/emit-exprs.js +475 -276
  34. package/src/backend/wasm/emit-maps.js +144 -8
  35. package/src/backend/wasm/emit-stdlib.js +12 -3005
  36. package/src/backend/wasm/emit-stmts.js +264 -270
  37. package/src/backend/wasm/emit.js +124 -87
  38. package/src/backend/wasm/encode.js +140 -29
  39. package/src/backend/wasm/index.js +1001 -32
  40. package/src/backend/wasm/optimize.js +17 -6
  41. package/src/backend/wasm/stdlib/bytes.js +5039 -0
  42. package/src/backend/wasm/stdlib/core.js +8903 -0
  43. package/src/backend/wasm/stdlib/index.js +43 -0
  44. package/src/backend/wasm/stdlib/io.js +2161 -0
  45. package/src/backend/wasm/stdlib/packages.js +2400 -0
  46. package/src/backend/wasm/stdlib/pure.js +720 -0
  47. package/src/backend/wasm/stdlib/simd.js +234 -0
  48. package/src/backend/wasm/stdlib/time.js +1200 -0
  49. package/src/backend/wasm/types.js +174 -1
  50. package/src/backend/wasm/wat.js +37 -0
  51. package/src/cli-core.js +1 -1
  52. package/src/compiler.js +18 -4
  53. package/src/dts-parser.js +150 -140
  54. package/src/index.js +2 -6
  55. package/src/lexer.js +80 -29
  56. package/src/lower/boxing.js +1 -7
  57. package/src/lower/captures.js +2 -6
  58. package/src/lower/embedding.js +2 -1
  59. package/src/lower/ownership.js +30 -9
  60. package/src/minifier.js +20 -68
  61. package/src/parser/declarations.js +3 -6
  62. package/src/parser/expressions.js +3 -8
  63. package/src/parser/index.js +18 -3
  64. package/src/parser/statements.js +25 -38
  65. package/src/parser/struct-tag.js +144 -0
  66. package/src/parser/types.js +64 -8
  67. package/src/resolver.js +2 -27
  68. package/src/templ-lexer.js +29 -63
  69. package/src/templ-parser.js +22 -45
  70. package/src/typechecker/assignability.js +126 -13
  71. package/src/typechecker/expressions.js +168 -16
  72. package/src/typechecker/index.js +35 -8
  73. package/src/typechecker/resolve.js +187 -8
  74. package/src/typechecker/statements.js +68 -9
  75. package/src/typechecker/stdlib/core.js +280 -89
  76. package/src/typechecker/stdlib/extended.js +490 -1
  77. package/src/typechecker/stdlib/simd.js +73 -0
  78. package/src/typechecker/stdlib.js +2 -0
  79. package/src/typechecker/types.js +362 -9
  80. package/src/vendor.js +86 -11
@@ -118,17 +118,15 @@ export class TemplLexer {
118
118
  continue;
119
119
  }
120
120
 
121
- // Detect `templ` keyword at identifier boundaries
121
+ // Detect `templ` or `css` keyword at identifier boundaries
122
122
  if (this._matchKeyword("templ")) {
123
123
  this.tokens.push(new Token(TT.TEMPL_KW, "templ", this.line, this.col));
124
- this._lexTemplDecl();
124
+ this._lexBlockDecl("templ");
125
125
  continue;
126
126
  }
127
-
128
- // Detect `css` keyword at identifier boundaries
129
127
  if (this._matchKeyword("css")) {
130
128
  this.tokens.push(new Token(TT.CSS_KW, "css", this.line, this.col));
131
- this._lexCssDecl();
129
+ this._lexBlockDecl("css");
132
130
  continue;
133
131
  }
134
132
 
@@ -201,24 +199,25 @@ export class TemplLexer {
201
199
  for (const t of this._goTokens(chunk)) this.tokens.push(t);
202
200
  }
203
201
 
204
- // ── Templ declaration lexing ─────────────────────────────────
202
+ // ── Templ / CSS declaration lexing ───────────────────────────
205
203
 
206
- // Called right after consuming `templ` keyword.
207
- // Lexes: Name(params) { <HTML body> }
208
- _lexTemplDecl() {
204
+ // Called right after consuming `templ` or `css` keyword.
205
+ // Lexes: Name(params) { <body> }
206
+ _lexBlockDecl(kw) {
209
207
  this.skipWhitespace();
210
208
 
211
- // Name
212
- const nameStart = this.pos;
213
- while (!this.eof() && /\w/.test(this.peek())) this.advance();
214
- const name = this.src.slice(nameStart, this.pos);
215
- if (!name) this.err("expected component name after 'templ'");
209
+ const name = this._readWord();
210
+ if (!name) {
211
+ this.err(
212
+ `expected ${kw === "templ" ? "component name" : "name"} after '${kw}'`,
213
+ );
214
+ }
216
215
  this.tokens.push(new Token(T.IDENT, name, this.line, this.col));
217
216
 
218
217
  this.skipWhitespace();
219
218
 
220
219
  // Parameter list ( ... ) — lex as Go
221
- if (this.peek() !== "(") this.err("expected '(' after templ name");
220
+ if (this.peek() !== "(") this.err(`expected '(' after ${kw} name`);
222
221
  const paramStart = this.pos;
223
222
  this._skipBalanced("(", ")");
224
223
  const paramSrc = this.src.slice(paramStart, this.pos);
@@ -227,43 +226,12 @@ export class TemplLexer {
227
226
  this.skipWhitespace();
228
227
 
229
228
  // Opening { of body
230
- if (this.peek() !== "{") this.err("expected '{' after templ params");
229
+ if (this.peek() !== "{") this.err(`expected '{' after ${kw} params`);
231
230
  this.advance(); // consume {
232
231
  this.tokens.push(new Token(T.LBRACE, "{", this.line, this.col));
233
232
 
234
- // HTML body
235
- this._lexHtmlBody(1);
236
- }
237
-
238
- // ── CSS declaration lexing ───────────────────────────────────
239
-
240
- _lexCssDecl() {
241
- this.skipWhitespace();
242
-
243
- // Name (identifier)
244
- const nameStart = this.pos;
245
- while (!this.eof() && /\w/.test(this.peek())) this.advance();
246
- const name = this.src.slice(nameStart, this.pos);
247
- if (!name) this.err("expected name after 'css'");
248
- this.tokens.push(new Token(T.IDENT, name, this.line, this.col));
249
-
250
- this.skipWhitespace();
251
-
252
- // Parameter list (balanced parens)
253
- if (this.peek() !== "(") this.err("expected '(' after css name");
254
- const paramStart = this.pos;
255
- this._skipBalanced("(", ")");
256
- const paramSrc = this.src.slice(paramStart, this.pos);
257
- for (const t of this._goTokens(paramSrc)) this.tokens.push(t);
258
-
259
- this.skipWhitespace();
260
-
261
- // Opening { of body
262
- if (this.peek() !== "{") this.err("expected '{' after css params");
263
- this.advance(); // consume {
264
- this.tokens.push(new Token(T.LBRACE, "{", this.line, this.col));
265
-
266
- this._lexCssBody();
233
+ if (kw === "templ") this._lexHtmlBody(1);
234
+ else this._lexCssBody();
267
235
  }
268
236
 
269
237
  _skipCssString(quote) {
@@ -660,7 +628,7 @@ export class TemplLexer {
660
628
  // ── HTML text content ─────────────────────────────────────────
661
629
 
662
630
  _lexHtmlText() {
663
- let text = "";
631
+ const start = this.pos;
664
632
  const line = this.line;
665
633
  while (!this.eof()) {
666
634
  const c = this.peek();
@@ -674,9 +642,9 @@ export class TemplLexer {
674
642
  (c === "d" && this._peekKeyword("default"))
675
643
  )
676
644
  break;
677
- text += this.advance();
645
+ this.advance();
678
646
  }
679
- const trimmed = text.trim();
647
+ const trimmed = this.src.slice(start, this.pos).trim();
680
648
  if (trimmed)
681
649
  this.tokens.push(new Token(TT.HTML_TEXT, trimmed, line, this.col));
682
650
  }
@@ -729,17 +697,15 @@ export class TemplLexer {
729
697
  }
730
698
 
731
699
  _readHtmlName() {
732
- let name = "";
733
- while (!this.eof() && /[a-zA-Z0-9_-]/.test(this.peek()))
734
- name += this.advance();
735
- return name;
700
+ const start = this.pos;
701
+ while (!this.eof() && /[a-zA-Z0-9_-]/.test(this.peek())) this.advance();
702
+ return this.src.slice(start, this.pos);
736
703
  }
737
704
 
738
705
  _readHtmlAttrName() {
739
- let name = "";
740
- while (!this.eof() && /[a-zA-Z0-9_:.-]/.test(this.peek()))
741
- name += this.advance();
742
- return name;
706
+ const start = this.pos;
707
+ while (!this.eof() && /[a-zA-Z0-9_:.-]/.test(this.peek())) this.advance();
708
+ return this.src.slice(start, this.pos);
743
709
  }
744
710
 
745
711
  _readQuotedString() {
@@ -756,9 +722,9 @@ export class TemplLexer {
756
722
  }
757
723
 
758
724
  _readWord() {
759
- let w = "";
760
- while (!this.eof() && /\w/.test(this.peek())) w += this.advance();
761
- return w;
725
+ const start = this.pos;
726
+ while (!this.eof() && /\w/.test(this.peek())) this.advance();
727
+ return this.src.slice(start, this.pos);
762
728
  }
763
729
 
764
730
  _skipToOpenBrace() {
@@ -48,55 +48,31 @@ export class TemplParser extends Parser {
48
48
 
49
49
  // ── CssDecl ───────────────────────────────────────────────────
50
50
 
51
- parseCssDecl() {
51
+ _parseDecl(kind, bodyFn) {
52
52
  const _line = this.peek().line;
53
- this.advance(); // consume CSS_KW
54
-
53
+ this.advance();
55
54
  const name = this.expect(T.IDENT).value;
56
-
57
- // Parse parameter list (standard Go params: LPAREN...RPAREN)
58
55
  const params = this.parseParamList();
59
-
60
- // Optional semicolon between ) and {
61
56
  this.semi();
62
-
63
- // Consume opening {
64
57
  this.expect(T.LBRACE);
65
-
66
- // Consume CSS_BODY
67
- const bodyToken = this.expect(TT.CSS_BODY);
68
- const cssText = bodyToken.value;
69
-
70
- // Consume closing }
71
- this.expect(T.RBRACE);
58
+ const extra = bodyFn();
72
59
  this.semi();
73
-
74
- return { kind: "CssDecl", name, params, cssText, _line };
60
+ return { kind, name, params, ...extra, _line };
75
61
  }
76
62
 
77
- // ── TemplDecl ─────────────────────────────────────────────────
63
+ parseCssDecl() {
64
+ return this._parseDecl("CssDecl", () => {
65
+ const bodyToken = this.expect(TT.CSS_BODY);
66
+ const cssText = bodyToken.value;
67
+ this.expect(T.RBRACE);
68
+ return { cssText };
69
+ });
70
+ }
78
71
 
79
72
  parseTemplDecl() {
80
- const _line = this.peek().line;
81
- this.advance(); // consume TEMPL_KW
82
-
83
- const name = this.expect(T.IDENT).value;
84
-
85
- // Parse parameter list (standard Go params: LPAREN...RPAREN)
86
- const params = this.parseParamList();
87
-
88
- // Optional semicolon between ) and {
89
- this.semi();
90
-
91
- // Consume the opening { of the HTML body
92
- this.expect(T.LBRACE);
93
-
94
- // Parse the HTML body; parseHtmlChildren consumes the closing RBRACE
95
- const body = this.parseHtmlChildren();
96
-
97
- this.semi();
98
-
99
- return { kind: "TemplDecl", name, params, body, _line };
73
+ return this._parseDecl("TemplDecl", () => ({
74
+ body: this.parseHtmlChildren(),
75
+ }));
100
76
  }
101
77
 
102
78
  // ── HTML node parsing ─────────────────────────────────────────
@@ -229,14 +205,15 @@ export class TemplParser extends Parser {
229
205
  !this.check(T.RBRACE) &&
230
206
  !this.check(T.EOF)
231
207
  ) {
232
- if (this.check(TT.TEMPL_CASE)) {
208
+ if (this.check(TT.TEMPL_CASE) || this.check(TT.TEMPL_DEFAULT)) {
209
+ const isDefault = this.check(TT.TEMPL_DEFAULT);
233
210
  const caseTok = this.advance();
234
211
  const body = this.parseTemplCaseBody();
235
- cases.push({ kind: "TemplCase", caseTokens: caseTok.value, body });
236
- } else if (this.check(TT.TEMPL_DEFAULT)) {
237
- this.advance();
238
- const body = this.parseTemplCaseBody();
239
- cases.push({ kind: "TemplCase", caseTokens: null, body });
212
+ cases.push({
213
+ kind: "TemplCase",
214
+ caseTokens: isDefault ? null : caseTok.value,
215
+ body,
216
+ });
240
217
  } else {
241
218
  this.advance(); // skip unexpected
242
219
  }
@@ -27,6 +27,16 @@ import {
27
27
  UNTYPED_INT,
28
28
  } from "./types.js";
29
29
 
30
+ // Unnamed type literal kinds for Go's "identical underlying types" assignability rule.
31
+ const TYPE_LITERAL_KINDS = new Set([
32
+ "slice",
33
+ "map",
34
+ "func",
35
+ "pointer",
36
+ "array",
37
+ "struct",
38
+ ]);
39
+
30
40
  /** @typedef {import('./index.js').TypeChecker} TypeChecker */
31
41
 
32
42
  // Untyped constant assignability: maps source.base → Set of compatible target.name values
@@ -182,24 +192,105 @@ export const assignabilityMethods = {
182
192
  // Tags struct values / struct pointers flowing into an interface so codegen can
183
193
  // tell `T` from `*T` at runtime (both compile to the same class instance).
184
194
  _markIfaceBox(target, source, node) {
185
- if (this._skipIfaceMark) return;
186
- if (!node || typeof node !== "object" || !target || !source) return;
195
+ if (
196
+ this._skipIfaceMark ||
197
+ !node ||
198
+ typeof node !== "object" ||
199
+ !target ||
200
+ !source
201
+ )
202
+ return;
187
203
  if (target.kind === "typeParam") return;
188
204
  const tBase = this.resolveType(
189
205
  target.kind === "named" ? target.underlying : target,
190
206
  );
191
207
  if (!isAny(target) && tBase?.kind !== "interface") return;
192
- const structOf = (t) => {
193
- const r = this.resolveType(t);
194
- const b = r?.kind === "named" ? this.resolveType(r.underlying) : r;
195
- return b?.kind === "struct";
196
- };
208
+ const named = this._namedMethodBoxSource(source);
209
+ if (named) {
210
+ node._ifaceBox = named.ptr ? "named_ptr" : "named";
211
+ node._ifaceNamedType = named.type;
212
+ return;
213
+ }
197
214
  const sBase = this.resolveType(
198
215
  source.kind === "named" ? source.underlying : source,
199
216
  );
200
- if (sBase?.kind === "struct") node._ifaceBox = "value";
201
- else if (sBase?.kind === "pointer" && structOf(sBase.base))
202
- node._ifaceBox = "ptr";
217
+ const info = this._classifyIfaceBox(sBase);
218
+ if (!info) return;
219
+ node._ifaceBox = info.box;
220
+ if (info.ptrFields) node._ifacePtrFields = info.ptrFields;
221
+ },
222
+
223
+ // A named non-struct type with methods (or a pointer to one) entering an
224
+ // interface: the JS backend keeps such values raw and boxes them here so
225
+ // dynamic method dispatch works.
226
+ _namedMethodBoxSource(source) {
227
+ const isPtr = source?.kind === "pointer";
228
+ const t = isPtr ? source.base : source;
229
+ if (t?.kind !== "named") return null;
230
+ const u = this.resolveType(t.underlying);
231
+ if (!u || u.kind === "struct" || u.kind === "interface") return null;
232
+ const methods = t.methods ?? t._generic?.methods;
233
+ if (!methods || methods.size === 0) return null;
234
+ // Only user-declared types (methods carry _ptrRecv); stdlib named types
235
+ // such as time.Duration have no box class.
236
+ if (![...methods.values()].some((m) => Object.hasOwn(m ?? {}, "_ptrRecv")))
237
+ return null;
238
+ return { type: t, ptr: isPtr };
239
+ },
240
+
241
+ _getStructPtrFields(st, prefix = "", seen = new Set()) {
242
+ if (st?.kind !== "struct" || !st?.fields) return [];
243
+ if (seen.has(st)) return [];
244
+ const nextSeen = new Set(seen);
245
+ nextSeen.add(st);
246
+ const ptrs = [];
247
+ for (const [fname, ftype] of st.fields.entries()) {
248
+ const full = prefix ? `${prefix}.${fname}` : fname;
249
+ const rf = this.resolveType(ftype);
250
+ const bf = rf?.kind === "named" ? this.resolveType(rf.underlying) : rf;
251
+ if (isPointer(bf)) {
252
+ ptrs.push(full);
253
+ } else if (bf?.kind === "struct") {
254
+ ptrs.push(...this._getStructPtrFields(bf, full, nextSeen));
255
+ }
256
+ }
257
+ return ptrs;
258
+ },
259
+
260
+ _classifyIfaceBox(sBase) {
261
+ if (sBase?.kind === "struct") {
262
+ const pf = this._getStructPtrFields(sBase);
263
+ return { box: "value", ptrFields: pf.length > 0 ? pf : null };
264
+ }
265
+ if (sBase?.kind === "pointer") {
266
+ const st = this.resolveType(
267
+ sBase.base?.kind === "named" ? sBase.base.underlying : sBase.base,
268
+ );
269
+ if (st?.kind === "struct") {
270
+ const pf = this._getStructPtrFields(st);
271
+ return { box: "ptr", ptrFields: pf.length > 0 ? pf : null };
272
+ }
273
+ return null;
274
+ }
275
+ if (sBase?.kind === "map") {
276
+ const valType = this.resolveType(sBase.value ?? sBase.val ?? sBase.elem);
277
+ const keyType = this.resolveType(sBase.key);
278
+ return {
279
+ box: isPointer(valType) || isPointer(keyType) ? "ptr_map" : "map",
280
+ };
281
+ }
282
+ if (sBase?.kind === "slice") {
283
+ const elemType = this.resolveType(sBase.elem);
284
+ if (isPointer(elemType)) return { box: "ptr_slice" };
285
+ const elemBase =
286
+ elemType?.kind === "named"
287
+ ? this.resolveType(elemType.underlying)
288
+ : elemType;
289
+ const pf =
290
+ elemBase?.kind === "struct" ? this._getStructPtrFields(elemBase) : [];
291
+ return pf.length > 0 ? { box: "slice", ptrFields: pf } : null;
292
+ }
293
+ return null;
203
294
  },
204
295
 
205
296
  // A js package cannot provide an implementation of a wasm interface: the
@@ -259,9 +350,20 @@ export const assignabilityMethods = {
259
350
  );
260
351
  return;
261
352
  }
353
+ if (this._identicalUnderlyingLiteral(target, source, tBase, sBase)) return;
262
354
  this.err(`Cannot assign ${typeStr(source)} to ${typeStr(target)}`, node);
263
355
  },
264
356
 
357
+ // Identical underlying types are assignable when one side is an unnamed
358
+ // type literal (`var n Names = []string{...}`), as in Go.
359
+ _identicalUnderlyingLiteral(target, source, tBase, sBase) {
360
+ if ((target.kind === "named") === (source.kind === "named")) return false;
361
+ const unnamed = target.kind === "named" ? source : target;
362
+ return (
363
+ TYPE_LITERAL_KINDS.has(unnamed.kind) && typeStr(tBase) === typeStr(sBase)
364
+ );
365
+ },
366
+
265
367
  _isUntypedAssignable(target, source) {
266
368
  if (isUntyped(target)) return true;
267
369
  // Named types with a basic underlying type (`type Mode string`) accept
@@ -371,12 +473,23 @@ export const assignabilityMethods = {
371
473
  return true;
372
474
  },
373
475
 
476
+ _unpackReturns(rList) {
477
+ if (
478
+ rList.length === 1 &&
479
+ (rList[0]?.kind === "tuple" || rList[0]?.kind === "TupleType") &&
480
+ rList[0].types
481
+ ) {
482
+ return rList[0].types;
483
+ }
484
+ return rList;
485
+ },
486
+
374
487
  _implementsMethod(required, actual) {
375
488
  if (!actual) return false;
376
489
  const rp = required.params ?? [],
377
490
  ap = actual.params ?? [];
378
- const rr = required.returns ?? [],
379
- ar = actual.returns ?? [];
491
+ const rr = this._unpackReturns(required.returns ?? []),
492
+ ar = this._unpackReturns(actual.returns ?? []);
380
493
  if (!this._sigParamsMatch(rp, ap)) return false;
381
494
  if (!!required.variadic !== !!actual.variadic) return false;
382
495
  return this._sigParamsMatch(rr, ar);
@@ -398,7 +511,7 @@ export const assignabilityMethods = {
398
511
  }
399
512
  const methodMap =
400
513
  base?.kind === "struct"
401
- ? base.methods
514
+ ? base.methods || srcType.methods
402
515
  : srcType.kind === "named"
403
516
  ? srcType.methods
404
517
  : null;