gofront 1.7.0 → 1.8.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 (128) hide show
  1. package/README.md +13 -13
  2. package/package.json +8 -37
  3. package/src/backend/js/expressions.js +1341 -148
  4. package/src/backend/js/fmt-runtime.js +573 -0
  5. package/src/backend/js/index.js +375 -57
  6. package/src/backend/js/runtime.js +1460 -133
  7. package/src/backend/js/statements.js +900 -86
  8. package/src/backend/js/stdlib/bits.js +87 -14
  9. package/src/backend/js/stdlib/builder.js +41 -6
  10. package/src/backend/js/stdlib/bytes.js +253 -7
  11. package/src/backend/js/stdlib/errors.js +2 -1
  12. package/src/backend/js/stdlib/fmt.js +349 -10
  13. package/src/backend/js/stdlib/index.js +8 -3
  14. package/src/backend/js/stdlib/json.js +247 -52
  15. package/src/backend/js/stdlib/maps.js +0 -8
  16. package/src/backend/js/stdlib/math.js +17 -1
  17. package/src/backend/js/stdlib/simd.js +10 -0
  18. package/src/backend/js/stdlib/slices.js +36 -6
  19. package/src/backend/js/stdlib/strconv.js +10 -5
  20. package/src/backend/js/stdlib/strings.js +83 -4
  21. package/src/backend/js/stdlib/testing.js +7 -0
  22. package/src/backend/js/stdlib/time.js +79 -55
  23. package/src/backend/js/stdlib/unicode.js +1 -1
  24. package/src/backend/js/stdlib/utf8.js +1 -1
  25. package/src/backend/js/templ.js +38 -76
  26. package/src/backend/js/type-info.js +272 -0
  27. package/src/backend/shared/fmt-types.js +188 -0
  28. package/src/backend/wasm/boundary.js +436 -80
  29. package/src/backend/wasm/emit-builtins.js +303 -13
  30. package/src/backend/wasm/emit-exprs.js +1397 -203
  31. package/src/backend/wasm/emit-maps.js +61 -13
  32. package/src/backend/wasm/emit-stmts.js +1359 -164
  33. package/src/backend/wasm/emit.js +512 -77
  34. package/src/backend/wasm/encode.js +43 -13
  35. package/src/backend/wasm/globals.js +452 -0
  36. package/src/backend/wasm/index.js +194 -2489
  37. package/src/backend/wasm/method-expr.js +381 -0
  38. package/src/backend/wasm/module-emitter.js +2240 -0
  39. package/src/backend/wasm/monomorph.js +62 -4
  40. package/src/backend/wasm/prescan.js +397 -0
  41. package/src/backend/wasm/stdlib/bytes.js +1804 -127
  42. package/src/backend/wasm/stdlib/core.js +1574 -199
  43. package/src/backend/wasm/stdlib/index.js +0 -5
  44. package/src/backend/wasm/stdlib/io.js +1567 -150
  45. package/src/backend/wasm/stdlib/packages.js +2 -0
  46. package/src/backend/wasm/stdlib/simd.js +293 -28
  47. package/src/backend/wasm/stdlib/time.js +1041 -12
  48. package/src/backend/wasm/types.js +200 -15
  49. package/src/backend/wasm/variadic.js +93 -0
  50. package/src/backend/wasm/wat.js +47 -20
  51. package/src/{cli-core.js → cli/cli-core.js} +236 -10
  52. package/src/{test-runner.js → cli/test-runner.js} +332 -39
  53. package/src/{vendor.js → cli/vendor.js} +3 -4
  54. package/src/compiler.js +107 -269
  55. package/src/index.js +15 -169
  56. package/src/{lexer.js → lexer/lexer.js} +102 -55
  57. package/src/{tokens.js → lexer/tokens.js} +1 -0
  58. package/src/link/sourcemap.js +97 -0
  59. package/src/link/stdlib.js +515 -0
  60. package/src/link/wasm.js +294 -0
  61. package/src/lower/bounds.js +394 -0
  62. package/src/lower/boxing.js +1 -1
  63. package/src/lower/captures.js +176 -32
  64. package/src/lower/index.js +26 -18
  65. package/src/lower/json-codecs.js +1398 -0
  66. package/src/lower/ownership.js +22 -1
  67. package/src/lower/range.js +86 -1
  68. package/src/lower/reachability.js +132 -0
  69. package/src/parser/declarations.js +58 -13
  70. package/src/parser/expressions.js +29 -7
  71. package/src/parser/index.js +2 -1
  72. package/src/parser/statements.js +37 -22
  73. package/src/{templ-parser.js → parser/templ.js} +138 -15
  74. package/src/parser/types.js +69 -9
  75. package/src/resolver.js +22 -1
  76. package/src/typechecker/assignability.js +176 -17
  77. package/src/typechecker/const-fold.js +356 -0
  78. package/src/typechecker/constants.js +256 -0
  79. package/src/typechecker/expressions.js +931 -122
  80. package/src/typechecker/index.js +459 -67
  81. package/src/typechecker/init-order.js +330 -0
  82. package/src/typechecker/resolve.js +451 -56
  83. package/src/typechecker/statements.js +90 -9
  84. package/src/typechecker/stdlib/core.js +333 -55
  85. package/src/typechecker/stdlib/excluded.js +898 -0
  86. package/src/typechecker/stdlib/extended.js +322 -26
  87. package/src/typechecker/{stdlib.js → stdlib/index.js} +5 -5
  88. package/src/typechecker/stdlib/simd.js +65 -15
  89. package/src/typechecker/termination.js +1 -1
  90. package/src/typechecker/types.js +149 -25
  91. package/stdlib/bytes/iter.go +192 -0
  92. package/stdlib/bytes/search.go +240 -0
  93. package/stdlib/bytes/split.go +218 -0
  94. package/stdlib/cmp/cmp.go +65 -0
  95. package/stdlib/encoding/json/errors.go +31 -0
  96. package/stdlib/encoding/json/indent.go +71 -0
  97. package/stdlib/encoding/json/number.go +12 -0
  98. package/stdlib/encoding/json/stream.go +173 -0
  99. package/stdlib/errors/overlay.go +12 -0
  100. package/stdlib/hash/hash.go +53 -0
  101. package/stdlib/internal/jsonrt/dec.go +738 -0
  102. package/stdlib/internal/jsonrt/enc.go +366 -0
  103. package/stdlib/internal/jsonrt/indent.go +135 -0
  104. package/stdlib/internal/jsonrt/number.go +27 -0
  105. package/stdlib/internal/jsonrt/reader.go +73 -0
  106. package/stdlib/internal/jsonrt/scanner.go +649 -0
  107. package/stdlib/io/io.go +346 -0
  108. package/stdlib/iter/iter.go +18 -0
  109. package/stdlib/maps/iter.go +65 -0
  110. package/stdlib/math/extra.go +270 -0
  111. package/stdlib/slices/extra.go +73 -0
  112. package/stdlib/slices/iter.go +113 -0
  113. package/stdlib/sort/sort.go +561 -0
  114. package/stdlib/strconv/quote.go +341 -0
  115. package/stdlib/strings/extra.go +35 -0
  116. package/stdlib/strings/iter.go +122 -0
  117. package/stdlib/sync/extra.go +105 -0
  118. package/stdlib/unicode/unicode.go +112 -0
  119. package/stdlib/unicode/utf8/bytes.go +239 -0
  120. package/src/backend/wasm/const-fold.js +0 -205
  121. package/src/backend/wasm/emit-stdlib.js +0 -13
  122. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  123. /package/src/{colors.js → cli/colors.js} +0 -0
  124. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  125. /package/src/{minifier.js → cli/minifier.js} +0 -0
  126. /package/src/{project-config.js → cli/project-config.js} +0 -0
  127. /package/src/{pwa.js → cli/pwa.js} +0 -0
  128. /package/src/{templ-lexer.js → lexer/templ-lexer.js} +0 -0
@@ -4,25 +4,28 @@
4
4
 
5
5
  // Parsers return [value, msg|null] from the shared __strconv_* helpers
6
6
  // (runtime.js); the message becomes a Go error value.
7
- const parsed = (call, toValue = "r[0]") =>
8
- `((r) => [${toValue}, r[1] === null ? null : __error(r[1], r[1].includes("invalid syntax") ? __strconv_err_syntax : r[1].includes("value out of range") ? __strconv_err_range : null)])(${call})`;
7
+ const parsed = (fnName, numArg, call, toValue = "r[0]") =>
8
+ `((r, s) => [${toValue}, r[1] === null ? null : (typeof NumError$$strconv !== "undefined" ? ((o) => { o.__p = true; return o; })(new NumError$$strconv("${fnName}", String(s), r[1].includes("value out of range") ? __strconv_err_range : __strconv_err_syntax)) : __error(r[1], r[1].includes("invalid syntax") ? __strconv_err_syntax : r[1].includes("value out of range") ? __strconv_err_range : null))])(${call}, ${numArg})`;
9
9
 
10
10
  const STRCONV_DISPATCH = {
11
11
  Itoa: (a) => `String(${a[0]})`,
12
- Atoi: (a) => parsed(`__strconv_atoi(${a[0]})`, "Number(r[0])"),
12
+ Atoi: (a) => parsed("Atoi", a[0], `__strconv_atoi(${a[0]})`, "Number(r[0])"),
13
13
  FormatBool: (a) => `String(${a[0]})`,
14
14
  FormatInt: (a) => `(${a[0]}).toString(${a[1]})`,
15
15
  FormatFloat: (a) =>
16
16
  a.length < 3
17
17
  ? `String(${a[0]})`
18
18
  : `__strconv_format_float(${a[0]}, ${a[1]}, ${a[2]})`,
19
- ParseFloat: (a) => parsed(`__strconv_parse_float(${a[0]})`),
19
+ ParseFloat: (a) =>
20
+ parsed("ParseFloat", a[0], `__strconv_parse_float(${a[0]})`),
20
21
  ParseInt: (a) =>
21
22
  parsed(
23
+ "ParseInt",
24
+ a[0],
22
25
  `__strconv_parse_int(${a[0]}, ${a[1]}, ${a[2] ?? 64})`,
23
26
  "Number(r[0])",
24
27
  ),
25
- ParseBool: (a) => parsed(`__strconv_parse_bool(${a[0]})`),
28
+ ParseBool: (a) => parsed("ParseBool", a[0], `__strconv_parse_bool(${a[0]})`),
26
29
  Quote: (a) => `JSON.stringify(${a[0]})`,
27
30
  Unquote: (a) =>
28
31
  `((s) => { if (typeof s === "string" && s.startsWith("\`") && s.endsWith("\`") && s.length >= 2) { if (s.slice(1, -1).includes("\`")) return ["", __strconv_err_syntax]; return [s.slice(1, -1), null]; } try { const v = JSON.parse(s); if (typeof v !== "string") throw 0; return [v, null]; } catch(e) { return ["", __strconv_err_syntax]; } })(${a[0]})`,
@@ -37,6 +40,8 @@ const STRCONV_DISPATCH = {
37
40
  FormatUint: (a) => `(BigInt.asUintN(64, BigInt(${a[0]}))).toString(${a[1]})`,
38
41
  ParseUint: (a) =>
39
42
  parsed(
43
+ "ParseUint",
44
+ a[0],
40
45
  `__strconv_parse_uint(${a[0]}, ${a[1]}, ${a[2] ?? 64})`,
41
46
  "Number(r[0])",
42
47
  ),
@@ -16,9 +16,9 @@ const STRINGS_M2 = {
16
16
  LastIndex: "lastIndexOf",
17
17
  Repeat: "repeat",
18
18
  Split: "split",
19
- Join: "join",
20
19
  };
21
20
  const STRINGS_DISPATCH = {
21
+ Join: ([a, sep]) => `(${a} ?? []).join(${sep})`,
22
22
  Count: ([s, s2]) =>
23
23
  `((s, sep) => sep === "" ? s.length + 1 : s.split(sep).length - 1)(${s}, ${s2})`,
24
24
  Replace: ([s, s2, s3, n]) =>
@@ -37,7 +37,8 @@ const STRINGS_DISPATCH = {
37
37
  TrimRight: ([s, s2]) =>
38
38
  `((str, cut) => { const cs = new Set(cut); let j = str.length; while (j > 0 && cs.has(str[j - 1])) j--; return str.slice(0, j); })(${s}, ${s2})`,
39
39
  EqualFold: ([s, s2]) => `${s}.toLowerCase() === ${s2}.toLowerCase()`,
40
- Fields: ([s]) => `(${s}).trim() === '' ? [] : (${s}).trim().split(/\\s+/)`,
40
+ Fields: ([s]) =>
41
+ `(${s}).split(/[ \\t\\n\\v\\f\\r\\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]+/).filter(p => p !== "")`,
41
42
  FieldsFunc: ([s, f]) =>
42
43
  `((s, fn) => { const res = []; const runes = Array.from(s ?? ""); let cur = ""; for (const ch of runes) { const cp = ch.codePointAt(0); if (fn(cp)) { if (cur.length > 0) { res.push(cur); cur = ""; } } else { cur += ch; } } if (cur.length > 0) res.push(cur); return res; })(${s}, ${f})`,
43
44
  Cut: ([s, s2]) =>
@@ -169,8 +170,83 @@ const STRINGS_DISPATCH = {
169
170
  `((s, f) => { for (let i = 0; i < s.length; i++) { const cp = s.codePointAt(i); if (f(cp)) return i; if (cp > 0xFFFF) i++; } return -1; })(${s}, ${s2})`,
170
171
  LastIndexFunc: ([s, s2]) =>
171
172
  `((s, f) => { for (let i = s.length - 1; i >= 0; i--) { let cp = s.codePointAt(i); if (i > 0 && s.charCodeAt(i) >= 0xDC00 && s.charCodeAt(i) <= 0xDFFF && s.charCodeAt(i - 1) >= 0xD800 && s.charCodeAt(i - 1) <= 0xDBFF) { cp = s.codePointAt(i - 1); i--; } if (f(cp)) return i; } return -1; })(${s}, ${s2})`,
173
+ ToValidUTF8: ([s, repl]) =>
174
+ `(${s} ? ${s}.replace(/(?:[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?<![\\uD800-\\uDBFF])[\\uDC00-\\uDFFF])+/g, ${repl} ?? "") : (${s} ?? ""))`,
172
175
  NewReader: ([s]) =>
173
- `((s) => ({ _bytes: new TextEncoder().encode(s), _pos: 0, Read(p) { if (this._pos >= this._bytes.length) return [0, __io_eof]; const n = Math.min(p.length, this._bytes.length - this._pos); for (let i = 0; i < n; i++) p[i] = this._bytes[this._pos + i]; this._pos += n; return [n, null]; }, Len() { return Math.max(0, this._bytes.length - this._pos); }, Reset(s2) { this._bytes = new TextEncoder().encode(s2); this._pos = 0; } }))(${s})`,
176
+ `((s) => ({
177
+ _bytes: new TextEncoder().encode(s ?? ""),
178
+ _pos: 0,
179
+ _prevRune: -1,
180
+ Read(p) {
181
+ if (this._pos >= this._bytes.length) return [0, __io_eof];
182
+ const n = Math.min(p.length, this._bytes.length - this._pos);
183
+ for (let i = 0; i < n; i++) p[i] = this._bytes[this._pos + i];
184
+ this._pos += n;
185
+ this._prevRune = -1;
186
+ return [n, null];
187
+ },
188
+ ReadAt(b, off) {
189
+ const o = Number(off);
190
+ if (o < 0) return [0, __error("strings.Reader.ReadAt: negative offset")];
191
+ if (o >= this._bytes.length) return [0, __io_eof];
192
+ const n = Math.min(b.length, this._bytes.length - o);
193
+ for (let i = 0; i < n; i++) b[i] = this._bytes[o + i];
194
+ return [n, n < b.length ? __io_eof : null];
195
+ },
196
+ ReadByte() {
197
+ if (this._pos >= this._bytes.length) return [0, __io_eof];
198
+ this._prevRune = -1;
199
+ return [this._bytes[this._pos++], null];
200
+ },
201
+ UnreadByte() {
202
+ if (this._pos <= 0) return __error("strings.Reader.UnreadByte: at beginning of string");
203
+ this._prevRune = -1;
204
+ this._pos--;
205
+ return null;
206
+ },
207
+ ReadRune() {
208
+ if (this._pos >= this._bytes.length) return [0, 0, __io_eof];
209
+ const rem = this._bytes.subarray(this._pos);
210
+ const str = new TextDecoder().decode(rem);
211
+ const cp = str.codePointAt(0);
212
+ const runeStr = str.slice(0, cp > 0xffff ? 2 : 1);
213
+ const sz = new TextEncoder().encode(runeStr).length;
214
+ this._prevRune = this._pos;
215
+ this._pos += sz;
216
+ return [cp, sz, null];
217
+ },
218
+ UnreadRune() {
219
+ if (this._prevRune === undefined || this._prevRune < 0 || this._prevRune >= this._pos) {
220
+ return __error("strings.Reader.UnreadRune: previous operation was not ReadRune");
221
+ }
222
+ this._pos = this._prevRune;
223
+ this._prevRune = -1;
224
+ return null;
225
+ },
226
+ Seek(offset, whence) {
227
+ const off = BigInt(offset);
228
+ const w = Number(whence);
229
+ let newPos;
230
+ if (w === 0) newPos = off;
231
+ else if (w === 1) newPos = BigInt(this._pos) + off;
232
+ else if (w === 2) newPos = BigInt(this._bytes.length) + off;
233
+ else return [0n, __error("strings.Reader.Seek: invalid whence")];
234
+ if (newPos < 0n) return [0n, __error("strings.Reader.Seek: negative position")];
235
+ this._pos = Number(newPos);
236
+ this._prevRune = -1;
237
+ return [newPos, null];
238
+ },
239
+ Size() { return BigInt(this._bytes.length); },
240
+ WriteTo(w) {
241
+ if (this._pos >= this._bytes.length) return [0, null];
242
+ const rem = this._bytes.subarray(this._pos);
243
+ const [n, err] = w.Write(rem);
244
+ this._pos += n;
245
+ return [n, err];
246
+ },
247
+ Len() { return Math.max(0, this._bytes.length - this._pos); },
248
+ Reset(s2) { this._bytes = new TextEncoder().encode(s2 ?? ""); this._pos = 0; this._prevRune = -1; }
249
+ }))(${s})`,
174
250
  NewReplacer: (args) =>
175
251
  `((...pairs) => {
176
252
  const p = [];
@@ -211,7 +287,10 @@ const STRINGS_DISPATCH = {
211
287
  /** @type {ThisType<CodeGen>} */
212
288
  export const stringsMethods = {
213
289
  _genStrings(fn, a) {
214
- if (fn === "NewReader") this.useHelper("ioEOF");
290
+ if (fn === "NewReader") {
291
+ this.useHelper("ioEOF");
292
+ this.useHelper("error");
293
+ }
215
294
  const args = a();
216
295
  const [s, s2] = args;
217
296
  if (STRINGS_M1[fn]) return `${s}.${STRINGS_M1[fn]}()`;
@@ -10,6 +10,13 @@ export const testingMethods = {
10
10
  return "false";
11
11
  case "Verbose":
12
12
  return "Boolean(globalThis.__gofront_verbose)";
13
+ case "Testing":
14
+ return "true";
15
+ case "Init":
16
+ return "/* testing.Init */";
17
+ case "Benchmark":
18
+ this.useHelper("testing");
19
+ return `__testing_Benchmark(${_a()[0]})`;
13
20
  }
14
21
  return undefined;
15
22
  },
@@ -3,53 +3,52 @@
3
3
  /** @typedef {import('../index.js').CodeGen} CodeGen */
4
4
 
5
5
  const TIME_METHOD_DISPATCH = {
6
- Year: (r) =>
7
- `(${r}._offset === 0 ? ${r}._d.getUTCFullYear() : ${r}._d.getFullYear())`,
8
- Month: (r) =>
9
- `(${r}._offset === 0 ? ${r}._d.getUTCMonth() + 1 : ${r}._d.getMonth() + 1)`,
10
- Day: (r) => `(${r}._offset === 0 ? ${r}._d.getUTCDate() : ${r}._d.getDate())`,
11
- Hour: (r) =>
12
- `(${r}._offset === 0 ? ${r}._d.getUTCHours() : ${r}._d.getHours())`,
13
- Minute: (r) =>
14
- `(${r}._offset === 0 ? ${r}._d.getUTCMinutes() : ${r}._d.getMinutes())`,
15
- Second: (r) =>
16
- `(${r}._offset === 0 ? ${r}._d.getUTCSeconds() : ${r}._d.getSeconds())`,
17
- Weekday: (r) =>
18
- `(${r}._offset === 0 ? ${r}._d.getUTCDay() : ${r}._d.getDay())`,
6
+ Year: (r) => `__timeYear(${r})`,
7
+ Month: (r) => `__timeMonth(${r})`,
8
+ Day: (r) => `__timeDay(${r})`,
9
+ Hour: (r) => `__timeHour(${r})`,
10
+ Minute: (r) => `__timeMinute(${r})`,
11
+ Second: (r) => `__timeSecond(${r})`,
12
+ Weekday: (r) => `__timeWeekday(${r})`,
19
13
  Unix: (r) => `Math.floor(${r}._d.getTime() / 1000)`,
20
14
  UnixMilli: (r) => `${r}._d.getTime()`,
21
15
  Add: (r, a) =>
22
- `{_d: new Date(${r}._d.getTime() + Math.round(Number(${a[0]}) / 1000000))}`,
16
+ `{_d: new Date(${r}._d.getTime() + Math.round(Number(${a[0]}) / 1000000)), _offset: ${r}._offset, _loc: ${r}._loc, _ns: ${r}._ns}`,
23
17
  Sub: (r, a) =>
24
18
  `Math.round((${r}._d.getTime() - (${a[0]})._d.getTime()) * 1000000)`,
25
19
  Before: (r, a) => `(${r}._d < (${a[0]})._d)`,
26
20
  After: (r, a) => `(${r}._d > (${a[0]})._d)`,
27
- Equal: (r, a) => `(${r}._d.getTime() === (${a[0]})._d.getTime())`,
21
+ Equal: (r, a) =>
22
+ `(${r}._d.getTime() === (${a[0]})._d.getTime() && (${r}._ns || 0) === ((${a[0]})._ns || 0))`,
28
23
  IsZero: (r) =>
29
24
  `(!${r} || !${r}._d || ${r}._isZero === true || ${r}._d.getTime() === -62135596800000 || (${r}._d.getUTCFullYear() <= 1 && ${r}._d.getUTCMonth() === 0 && ${r}._d.getUTCDate() === 1 && ${r}._d.getUTCHours() === 0 && ${r}._d.getUTCMinutes() === 0 && ${r}._d.getUTCSeconds() === 0 && ${r}._d.getUTCMilliseconds() === 0))`,
30
- UnixNano: (r) => `(${r}?._d ? Math.round(${r}._d.getTime() * 1000000) : 0)`,
25
+ UnixNano: (r) =>
26
+ `(${r}?._d ? Math.round(${r}._d.getTime() * 1000000) + (${r}._ns || 0) : 0)`,
31
27
  Nanosecond: (r) =>
32
- `(${r}?._d ? (${r}._offset === 0 ? ${r}._d.getUTCMilliseconds() : ${r}._d.getMilliseconds()) * 1000000 : 0)`,
33
- UTC: (r) => `({_d: ${r}?._d ?? new Date(0), _offset: 0})`,
34
- Local: (r) => `({_d: ${r}?._d ?? new Date(0)})`,
35
- AddDate: (r, a) =>
36
- `({_d: new Date(${r}._d.getFullYear() + Number(${a[0]}), ${r}._d.getMonth() + Number(${a[1]}), ${r}._d.getDate() + Number(${a[2]}))})`,
37
- Truncate: (r, a) =>
38
- `({_d: new Date(Math.floor(${r}._d.getTime() / (Number(${a[0]}) / 1000000)) * (Number(${a[0]}) / 1000000))})`,
39
- Round: (r, a) =>
40
- `({_d: new Date(Math.round(${r}._d.getTime() / (Number(${a[0]}) / 1000000)) * (Number(${a[0]}) / 1000000))})`,
41
- Date: (r) =>
42
- `[${r}._offset === 0 ? ${r}._d.getUTCFullYear() : ${r}._d.getFullYear(), ${r}._offset === 0 ? ${r}._d.getUTCMonth() + 1 : ${r}._d.getMonth() + 1, ${r}._offset === 0 ? ${r}._d.getUTCDate() : ${r}._d.getDate()]`,
43
- Clock: (r) =>
44
- `[${r}._offset === 0 ? ${r}._d.getUTCHours() : ${r}._d.getHours(), ${r}._offset === 0 ? ${r}._d.getUTCMinutes() : ${r}._d.getMinutes(), ${r}._offset === 0 ? ${r}._d.getUTCSeconds() : ${r}._d.getSeconds()]`,
45
- YearDay: (r) =>
46
- `Math.floor((${r}._d - new Date(${r}._d.getFullYear(), 0, 0)) / 86400000)`,
28
+ `(${r}?._d ? (typeof ${r}._offset === "number" ? ${r}._d.getUTCMilliseconds() : ${r}._d.getMilliseconds()) * 1000000 + (${r}._ns || 0) : 0)`,
29
+ UTC: (r) =>
30
+ `({_d: ${r}?._d ?? new Date(0), _offset: 0, _loc: null, _ns: ${r}?._ns || 0})`,
31
+ Local: (r) =>
32
+ `({_d: ${r}?._d ?? new Date(0), _offset: undefined, _loc: null, _ns: ${r}?._ns || 0})`,
33
+ AddDate: (r, a) => `__timeAddDate(${r}, ${a[0]}, ${a[1]}, ${a[2]})`,
34
+ Truncate: (r, a) => `__timeTruncate(${r}, ${a[0]})`,
35
+ Round: (r, a) => `__timeRound(${r}, ${a[0]})`,
36
+ Date: (r) => `__timeDate(${r})`,
37
+ Clock: (r) => `__timeClock(${r})`,
38
+ YearDay: (r) => `__timeYearDay(${r})`,
47
39
  Compare: (r, a) =>
48
- `(${r}._d.getTime() < (${a[0]})._d.getTime() ? -1 : (${r}._d.getTime() > (${a[0]})._d.getTime() ? 1 : 0))`,
49
- In: (r) => `${r}`,
50
- Location: () => `null`,
40
+ `(${r}._d.getTime() < (${a[0]})._d.getTime() ? -1 : (${r}._d.getTime() > (${a[0]})._d.getTime() ? 1 : ((${r}._ns || 0) < ((${a[0]})._ns || 0) ? -1 : ((${r}._ns || 0) > ((${a[0]})._ns || 0) ? 1 : 0))))`,
41
+ ISOWeek: (r) => `__timeISOWeek(${r})`,
42
+ In: (r, a) =>
43
+ `((() => { const _loc = ${a[0]}; const _off = _loc && typeof _loc._offset === "number" ? _loc._offset : 0; return { _d: ${r}._d, _offset: _off, _loc, _ns: ${r}._ns }; })())`,
44
+ Location: (r) =>
45
+ `(${r}._loc ?? (${r}._offset === 0 ? { _name: "UTC", _offset: 0, String() { return "UTC"; } } : { _name: "Local", _offset: -${r}._d.getTimezoneOffset() * 60, String() { return "Local"; } }))`,
51
46
  Zone: (r) =>
52
- `[${r}._offset === 0 ? "UTC" : "LOCAL", ${r}._offset === 0 ? 0 : -${r}._d.getTimezoneOffset() * 60]`,
47
+ `[${r}._loc ? ${r}._loc._name : (${r}._offset === 0 ? "UTC" : "LOCAL"), ${r}._loc ? ${r}._loc._offset : (${r}._offset === 0 ? 0 : -${r}._d.getTimezoneOffset() * 60)]`,
48
+ MarshalJSON: (r) => `__timeMarshalJSON(${r})`,
49
+ UnmarshalJSON: (r, a) => `__timeUnmarshalJSON(${r}, ${a[0]})`,
50
+ MarshalText: (r) => `__timeMarshalText(${r})`,
51
+ UnmarshalText: (r, a) => `__timeUnmarshalText(${r}, ${a[0]})`,
53
52
  };
54
53
 
55
54
  const DURATION_METHOD_DISPATCH = {
@@ -60,6 +59,9 @@ const DURATION_METHOD_DISPATCH = {
60
59
  Minutes: (r) => `(Number(${r}) / 60000000000)`,
61
60
  Hours: (r) => `(Number(${r}) / 3600000000000)`,
62
61
  String: (r) => `__durationString(${r})`,
62
+ Truncate: (r, a) => `(${a[0]} <= 0 ? ${r} : ${r} - (${r} % ${a[0]}))`,
63
+ Round: (r, a) => `__durationRound(${r}, ${a[0]})`,
64
+ Abs: (r) => `(${r} >= 0 ? ${r} : -(${r}))`,
63
65
  };
64
66
 
65
67
  const TIME_DISPATCH = {
@@ -69,11 +71,16 @@ const TIME_DISPATCH = {
69
71
  `(Math.round(((typeof globalThis.__GOFRONT_CLOCK === "function" ? globalThis.__GOFRONT_CLOCK() : Date.now()) - (${a[0]})._d.getTime()) * 1000000))`,
70
72
  Sleep: (a) => `await new Promise(r => setTimeout(r, ${a[0]} / 1000000))`,
71
73
  Unix: (a) =>
72
- `{_d: new Date(Number(${a[0]}) * 1000 + Math.round(Number(${a[1] || 0}) / 1000000))}`,
73
- UnixMicro: (a) => `{_d: new Date(Math.round(Number(${a[0]}) / 1000))}`,
74
+ `{_d: new Date(Number(${a[0]}) * 1000 + Math.round(Number(${a[1] || 0}) / 1000000)), _ns: Math.round(Number(${a[1] || 0}) % 1000000)}`,
75
+ UnixMicro: (a) =>
76
+ `{_d: new Date(Math.round(Number(${a[0]}) / 1000)), _ns: Math.round((Number(${a[0]}) % 1000) * 1000)}`,
74
77
  UnixMilli: (a) => `{_d: new Date(Number(${a[0]}))}`,
75
78
  Date: (a) =>
76
- `{_d: new Date(${a[0]}, ${a[1]} - 1, ${a[2]}, ${a[3]}, ${a[4]}, ${a[5]}, Math.round(Number(${a[6] || 0}) / 1000000))}`,
79
+ `{_d: new Date(${a[0]}, ${a[1]} - 1, ${a[2]}, ${a[3]}, ${a[4]}, ${a[5]}, Math.round(Number(${a[6] || 0}) / 1000000)), _ns: Math.round(Number(${a[6] || 0}) % 1000000)}`,
80
+ ParseDuration: (a) => `__timeParseDuration(${a[0]})`,
81
+ FixedZone: (a) =>
82
+ `({ _name: ${a[0]}, _offset: ${a[1]}, String() { return this._name; } })`,
83
+ AfterFunc: (a) => `__timeAfterFunc(${a[0]}, ${a[1]})`,
77
84
  };
78
85
 
79
86
  /** @type {ThisType<CodeGen>} */
@@ -82,12 +89,13 @@ export const timeMethods = {
82
89
  const recv = expr.func.expr;
83
90
  const recvJs = this.genExpr(recv);
84
91
  const args = expr.args.map((a) => this.genExpr(a));
92
+ this.useHelper("timeFmt");
93
+ this.useHelper("duration");
94
+ this.useHelper("timeParse");
85
95
  if (method === "Format") {
86
- this.useHelper("timeFmt");
87
96
  return `__timeFmt(${recvJs}, ${args[0]})`;
88
97
  }
89
98
  if (method === "String") {
90
- this.useHelper("timeFmt");
91
99
  return `__timeFmt(${recvJs}, "2006-01-02 15:04:05.999999999 -0700 MST")`;
92
100
  }
93
101
  const gen = TIME_METHOD_DISPATCH[method];
@@ -97,30 +105,46 @@ export const timeMethods = {
97
105
  _genDurationMethodCall(method, expr) {
98
106
  const recv = expr.func.expr;
99
107
  const recvJs = this.genExpr(recv);
100
- if (method === "String") {
101
- this.useHelper("duration");
102
- }
108
+ const args = expr.args.map((a) => this.genExpr(a));
109
+ this.useHelper("duration");
103
110
  const gen = DURATION_METHOD_DISPATCH[method];
104
- return gen ? gen(recvJs) : undefined;
111
+ return gen ? gen(recvJs, args) : undefined;
112
+ },
113
+
114
+ _genTimeDate(args, expr) {
115
+ this.useHelper("timeFmt");
116
+ const locArg = expr?.args?.[7];
117
+ const isUtc =
118
+ (locArg?.kind === "SelectorExpr" &&
119
+ (locArg.sel?.name === "UTC" || locArg.field === "UTC")) ||
120
+ (locArg?.kind === "Ident" && locArg.name === "UTC") ||
121
+ args[7] === '"UTC"';
122
+ if (isUtc) {
123
+ return `{_d: new Date(Date.UTC(${args[0]}, ${args[1]} - 1, ${args[2]}, ${args[3]}, ${args[4]}, ${args[5]}, Math.floor(Number(${args[6] || 0}) / 1000000))), _offset: 0, _loc: null, _ns: Math.floor(Number(${args[6] || 0}) % 1000000)}`;
124
+ }
125
+ return `((() => { const _loc = ${args[7]}; const _off = _loc && typeof _loc._offset === "number" ? _loc._offset : undefined; const _ms = Math.floor(Number(${args[6] || 0}) / 1000000); const _remNs = Math.floor(Number(${args[6] || 0}) % 1000000); if (_off !== undefined) { const _d = new Date(Date.UTC(${args[0]}, ${args[1]} - 1, ${args[2]}, ${args[3]}, ${args[4]}, ${args[5]}, _ms) - _off * 1000); return { _d, _offset: _off, _loc, _ns: _remNs }; } return { _d: new Date(${args[0]}, ${args[1]} - 1, ${args[2]}, ${args[3]}, ${args[4]}, ${args[5]}, _ms), _offset: undefined, _loc: null, _ns: _remNs }; })())`;
105
126
  },
106
127
 
107
128
  _genTime(fn, a, expr) {
129
+ if (fn === "Date") return this._genTimeDate(a(), expr);
108
130
  if (fn === "Parse") {
109
131
  this.useHelper("timeParse");
132
+ this.useHelper("timeFmt");
110
133
  return `__timeParse(${a()[0]}, ${a()[1]})`;
111
134
  }
112
- if (fn === "Date") {
135
+ if (fn === "ParseDuration") {
136
+ this.useHelper("duration");
137
+ return `__timeParseDuration(${a()[0]})`;
138
+ }
139
+ if (fn === "FixedZone") {
140
+ this.useHelper("timeFmt");
141
+ const args = a();
142
+ return `({ _name: ${args[0]}, _offset: ${args[1]}, String() { return this._name; } })`;
143
+ }
144
+ if (fn === "AfterFunc") {
145
+ this.useHelper("timeFmt");
113
146
  const args = a();
114
- const locArg = expr?.args?.[7];
115
- const isUtc =
116
- (locArg?.kind === "SelectorExpr" &&
117
- (locArg.sel?.name === "UTC" || locArg.field === "UTC")) ||
118
- (locArg?.kind === "Ident" && locArg.name === "UTC") ||
119
- args[7] === '"UTC"';
120
- if (isUtc) {
121
- return `{_d: new Date(Date.UTC(${args[0]}, ${args[1]} - 1, ${args[2]}, ${args[3]}, ${args[4]}, ${args[5]}, Math.round(Number(${args[6] || 0}) / 1000000))), _offset: 0}`;
122
- }
123
- return `{_d: new Date(${args[0]}, ${args[1]} - 1, ${args[2]}, ${args[3]}, ${args[4]}, ${args[5]}, Math.round(Number(${args[6] || 0}) / 1000000))}`;
147
+ return `__timeAfterFunc(${args[0]}, ${args[1]})`;
124
148
  }
125
149
  const gen = TIME_DISPATCH[fn];
126
150
  return gen ? gen(a()) : undefined;
@@ -10,7 +10,7 @@ export const unicodeMethods = {
10
10
  const UNICODE_TEST = {
11
11
  IsLetter: `/\\p{L}/u.test(${cp})`,
12
12
  IsDigit: `/\\p{Nd}/u.test(${cp})`,
13
- IsSpace: `/\\s/.test(${cp})`,
13
+ IsSpace: `/[ \\t\\n\\v\\f\\r\\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]/.test(${cp})`,
14
14
  IsUpper: `/\\p{Lu}/u.test(${cp})`,
15
15
  IsLower: `/\\p{Ll}/u.test(${cp})`,
16
16
  IsPunct: `/\\p{P}/u.test(${cp})`,
@@ -9,7 +9,7 @@ const UTF8_DISPATCH = {
9
9
  ValidString: (a) =>
10
10
  `/[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?<![\\uD800-\\uDBFF])[\\uDC00-\\uDFFF]/.test(${a[0]}) === false`,
11
11
  ValidRune: (a) =>
12
- `(${a[0]}) >= 0 && (${a[0]}) <= 0x10FFFF && !((${a[0]}) >= 0xD800 && (${a[0]}) <= 0xDFFF)`,
12
+ `((${a[0]}) >= 0 && (${a[0]}) <= 0x10FFFF && !((${a[0]}) >= 0xD800 && (${a[0]}) <= 0xDFFF))`,
13
13
  DecodeRuneInString: (a) =>
14
14
  `((s) => { if (!s) return [0xFFFD, 0]; const cp = s.codePointAt(0); return [cp, cp > 0xFFFF ? 2 : 1]; })(${a[0]})`,
15
15
  DecodeLastRuneInString: (a) =>
@@ -2,9 +2,6 @@
2
2
  // Mounts an HTML/expression tree onto a parent DOM node by emitting plain
3
3
  // document.createElement / appendChild / setAttribute calls.
4
4
 
5
- import { Parser } from "../../parser/index.js";
6
- import { T, Token } from "../../tokens.js";
7
-
8
5
  /** @typedef {import('./index.js').CodeGen} CodeGen */
9
6
 
10
7
  /** @type {ThisType<CodeGen>} */
@@ -17,6 +14,12 @@ export const templGenMethods = {
17
14
  .join(", ");
18
15
  this.line(`function ${decl.name}(${params}) {`);
19
16
  this.indented(() => {
17
+ // `@W(xs...)` passes `{ __sp: xs }` like any Go variadic call.
18
+ const last = decl.params[decl.params.length - 1];
19
+ if (last?.variadic && last.name && last.name !== "_") {
20
+ this.useHelper("va");
21
+ this.line(`${last.name} = __va(${last.name});`);
22
+ }
20
23
  this.line(`return {Mount(___p, ___refs) {`);
21
24
  this.indented(() => {
22
25
  this._genTemplNodes(decl.body, "___p");
@@ -79,7 +82,7 @@ export const templGenMethods = {
79
82
  );
80
83
  case "TemplExpr":
81
84
  return this.line(
82
- `${p}.appendChild(document.createTextNode(String(${this._genTemplTokenExpr(node.tokens)})));`,
85
+ `${p}.appendChild(document.createTextNode(String(${this._genTemplExpr(node, '""')})));`,
83
86
  );
84
87
  case "TemplComponent":
85
88
  return this._genTemplComponentMount(node, p);
@@ -101,20 +104,12 @@ export const templGenMethods = {
101
104
 
102
105
  _genTemplComponentMount(node, p) {
103
106
  // Special case: @templ.Raw(expr) → insertAdjacentHTML
104
- const toks = node.tokens;
105
- if (
106
- toks.length >= 4 &&
107
- toks[0]?.value === "templ" &&
108
- toks[1]?.type === T.DOT &&
109
- toks[2]?.value === "Raw" &&
110
- toks[3]?.type === T.LPAREN
111
- ) {
112
- const argTokens = toks.slice(4, toks.length - 1);
113
- const argJs = this._genTemplTokenExpr(argTokens);
107
+ if (node.raw) {
108
+ const argJs = this._genTemplExpr(node, '""');
114
109
  return this.line(`${p}.insertAdjacentHTML("beforeend", ${argJs});`);
115
110
  }
116
111
  return this.line(
117
- `(${this._genTemplTokenCallExpr(node.tokens)}).Mount(${p}, ___refs);`,
112
+ `(${this._genTemplExpr(node, "null")}).Mount(${p}, ___refs);`,
118
113
  );
119
114
  },
120
115
 
@@ -160,7 +155,7 @@ export const templGenMethods = {
160
155
  );
161
156
  }
162
157
  } else if (attr.kind === "expr") {
163
- const exprJs = this._genTemplTokenExpr(attr.tokens);
158
+ const exprJs = this._genTemplExpr(attr, '""');
164
159
  if (name === "class") {
165
160
  this.line(setClass(exprJs));
166
161
  } else {
@@ -171,7 +166,7 @@ export const templGenMethods = {
171
166
  } else if (attr.kind === "bool") {
172
167
  this.line(`${el}.setAttribute(${JSON.stringify(name)}, "");`);
173
168
  } else if (attr.kind === "cond-bool") {
174
- const exprJs = this._genTemplTokenExpr(attr.tokens);
169
+ const exprJs = this._genTemplExpr(attr, '""');
175
170
  this.line(
176
171
  `if(${exprJs})${el}.setAttribute(${JSON.stringify(name)}, "");`,
177
172
  );
@@ -179,7 +174,7 @@ export const templGenMethods = {
179
174
  },
180
175
 
181
176
  _genTemplIf(node, p) {
182
- const condJs = this._genTemplCondTokens(node.condTokens);
177
+ const condJs = this._genTemplExpr(node, "false");
183
178
  this.line(`if (${condJs}) {`);
184
179
  this.indented(() => this._genTemplNodes(node.then, p));
185
180
  this._genTemplIfElse(node.else_, p);
@@ -192,7 +187,7 @@ export const templGenMethods = {
192
187
  }
193
188
  if (else_.length === 1 && else_[0].kind === "TemplIf") {
194
189
  const elseIf = else_[0];
195
- this.line(`} else if (${this._genTemplCondTokens(elseIf.condTokens)}) {`);
190
+ this.line(`} else if (${this._genTemplExpr(elseIf, "false")}) {`);
196
191
  this.indented(() => this._genTemplNodes(elseIf.then, p));
197
192
  this._genTemplIfElse(elseIf.else_, p);
198
193
  } else {
@@ -203,16 +198,16 @@ export const templGenMethods = {
203
198
  },
204
199
 
205
200
  _genTemplSwitch(node, p) {
206
- const { exprTokens, cases } = node;
201
+ const { cases } = node;
207
202
  if (cases.length === 0) return;
208
- const exprJs = this._genTemplTokenExpr(exprTokens);
203
+ const exprJs = this._genTemplExpr(node, '""');
209
204
  this.line(`switch (${exprJs}) {`);
210
205
  this.indented(() => {
211
206
  for (const c of cases) {
212
207
  const label =
213
208
  c.caseTokens === null
214
209
  ? "default"
215
- : `case ${this._genTemplTokenExpr(c.caseTokens)}`;
210
+ : `case ${this._genTemplExpr(c, '""')}`;
216
211
  this.line(`${label}: {`);
217
212
  this.indented(() => this._genTemplNodes(c.body, p));
218
213
  this.line(`break; }`);
@@ -222,35 +217,10 @@ export const templGenMethods = {
222
217
  },
223
218
 
224
219
  _genTemplFor(node, p) {
225
- // stmtTokens include the `for` keyword plus the range clause (no body).
226
- // Strip any trailing semicolons (inserted by the Go lexer on newlines),
227
- // then add a fake empty body `{ }` so the parser can complete the ForStmt.
228
- let tokens = node.stmtTokens;
229
- while (tokens.length > 0 && tokens[tokens.length - 1].type === T.SEMICOLON)
230
- tokens = tokens.slice(0, -1);
231
- const eofTok = new Token(T.EOF, "", 1, 1);
232
- const lbrace = new Token(T.LBRACE, "{", 1, 1);
233
- const rbrace = new Token(T.RBRACE, "}", 1, 1);
234
- const semi = new Token(T.SEMICOLON, ";", 1, 1);
235
- const withFor =
236
- tokens[0]?.type === T.FOR
237
- ? [...tokens, lbrace, rbrace, semi, eofTok]
238
- : [
239
- new Token(T.FOR, "for", 1, 1),
240
- ...tokens,
241
- lbrace,
242
- rbrace,
243
- semi,
244
- eofTok,
245
- ];
246
- try {
247
- const p2 = new Parser(withFor);
248
- const forStmt = p2.parseFor();
249
- this._genTemplRangeFor(forStmt, node.body, p);
250
- } catch {
251
- // Fallback for non-range loops: emit a comment + skip
252
- this.line(`/* templ for: ${this._tokensToSource(tokens)} */`);
253
- }
220
+ if (node.forError) throw node.forError;
221
+ if (!node.forStmt)
222
+ throw new Error("templ for header was not parsed by TemplParser");
223
+ this._genTemplRangeFor(node.forStmt, node.body, p);
254
224
  },
255
225
 
256
226
  _genTemplRangeFor(forStmt, templBody, mountParent) {
@@ -290,37 +260,29 @@ export const templGenMethods = {
290
260
  return `for (const [${keyName}, ${valName}] of __s(${iterJs}).entries()) {`;
291
261
  },
292
262
 
293
- // Parse a token array as a Go expression and emit JS.
294
- _genTemplTokenExpr(tokens) {
295
- if (!tokens || tokens.length === 0) return '""';
296
- const eofTok = new Token(T.EOF, "", 1, 1);
297
- const p = new Parser([...tokens, eofTok]);
298
- const ast = p.parseExpr();
299
- return this.genExpr(ast);
263
+ // Emit JS for the Go expression TemplParser parsed onto `node`. `emptyJs` is
264
+ // the value when the node holds no tokens. A parse error is raised here,
265
+ // not in the parser, so it surfaces at the same point as it always did.
266
+ _genTemplExpr(node, emptyJs) {
267
+ if (node.exprError) throw node.exprError;
268
+ if (node.expr === undefined)
269
+ throw new Error("templ expression was not parsed by TemplParser");
270
+ return node.expr ? this.genExpr(node.expr) : emptyJs;
300
271
  },
301
272
 
302
- // Parse a token array as a Go call expression and emit JS.
303
- _genTemplTokenCallExpr(tokens) {
304
- if (!tokens || tokens.length === 0) return "null";
305
- const eofTok = new Token(T.EOF, "", 1, 1);
306
- const p = new Parser([...tokens, eofTok]);
307
- const ast = p.parseExpr();
308
- return this.genExpr(ast);
273
+ // Rename handlers for `_renameReservedIdents`: templ bodies keep their Go
274
+ // expressions in non-enumerable props (see parser/templ.js), which its
275
+ // generic walk does not visit, so `class` in `{ class }` would stay unsafe.
276
+ _renameTemplExprNode(node) {
277
+ this._renameReservedIdents(node.expr);
309
278
  },
310
279
 
311
- // Extract condition JS from condTokens (which may start with `if`).
312
- _genTemplCondTokens(tokens) {
313
- if (!tokens || tokens.length === 0) return "false";
314
- let toks = tokens;
315
- if (toks[0]?.type === T.IF) toks = toks.slice(1); // skip `if` keyword
316
- const eofTok = new Token(T.EOF, "", 1, 1);
317
- const p = new Parser([...toks, eofTok]);
318
- const ast = p.parseExpr();
319
- return this.genExpr(ast);
280
+ _renameTemplForNode(node) {
281
+ this._renameReservedIdents(node.forStmt);
320
282
  },
321
283
 
322
- _tokensToSource(tokens) {
323
- return tokens.map((t) => t.value).join(" ");
284
+ _renameTemplElementNode(node) {
285
+ for (const attr of node.attrs ?? []) this._renameReservedIdents(attr.expr);
324
286
  },
325
287
 
326
288
  // Fresh variable name counter