gofront 1.5.1 → 1.6.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 (91) hide show
  1. package/README.md +174 -466
  2. package/package.json +16 -20
  3. package/src/backend/js/expressions.js +288 -128
  4. package/src/backend/js/index.js +138 -56
  5. package/src/backend/js/runtime.js +1055 -53
  6. package/src/backend/js/source-map.js +32 -0
  7. package/src/backend/js/statements.js +151 -83
  8. package/src/backend/js/stdlib/base64.js +12 -0
  9. package/src/backend/js/stdlib/binary.js +102 -0
  10. package/src/backend/js/stdlib/bits.js +15 -1
  11. package/src/backend/js/stdlib/builder.js +28 -9
  12. package/src/backend/js/stdlib/bytes.js +75 -18
  13. package/src/backend/js/stdlib/errors.js +22 -3
  14. package/src/backend/js/stdlib/fmt.js +95 -20
  15. package/src/backend/js/stdlib/hex.js +17 -0
  16. package/src/backend/js/stdlib/html.js +1 -1
  17. package/src/backend/js/stdlib/index.js +19 -0
  18. package/src/backend/js/stdlib/io.js +105 -3
  19. package/src/backend/js/stdlib/maps.js +1 -1
  20. package/src/backend/js/stdlib/math.js +13 -1
  21. package/src/backend/js/stdlib/path.js +20 -6
  22. package/src/backend/js/stdlib/rand.js +10 -5
  23. package/src/backend/js/stdlib/sha256.js +12 -0
  24. package/src/backend/js/stdlib/shared.js +17 -0
  25. package/src/backend/js/stdlib/slices.js +53 -27
  26. package/src/backend/js/stdlib/sort.js +1 -1
  27. package/src/backend/js/stdlib/strconv.js +52 -11
  28. package/src/backend/js/stdlib/strings.js +152 -16
  29. package/src/backend/js/stdlib/sync.js +13 -0
  30. package/src/backend/js/stdlib/testing.js +2 -2
  31. package/src/backend/js/stdlib/time.js +93 -22
  32. package/src/backend/js/stdlib/unicode.js +2 -2
  33. package/src/backend/js/templ.js +1 -1
  34. package/src/backend/wasm/boundary.js +1677 -58
  35. package/src/backend/wasm/const-fold.js +205 -0
  36. package/src/backend/wasm/emit-builtins.js +1230 -0
  37. package/src/backend/wasm/emit-exprs.js +2096 -0
  38. package/src/backend/wasm/emit-maps.js +1999 -0
  39. package/src/backend/wasm/emit-shared.js +519 -0
  40. package/src/backend/wasm/emit-stdlib.js +13 -0
  41. package/src/backend/wasm/emit-stmts.js +2466 -0
  42. package/src/backend/wasm/emit.js +656 -5031
  43. package/src/backend/wasm/encode.js +259 -85
  44. package/src/backend/wasm/glue.js +19 -5
  45. package/src/backend/wasm/index.js +2432 -86
  46. package/src/backend/wasm/monomorph.js +711 -0
  47. package/src/backend/wasm/optimize.js +236 -0
  48. package/src/backend/wasm/stdlib/bytes.js +5039 -0
  49. package/src/backend/wasm/stdlib/core.js +8903 -0
  50. package/src/backend/wasm/stdlib/index.js +40 -0
  51. package/src/backend/wasm/stdlib/io.js +2161 -0
  52. package/src/backend/wasm/stdlib/packages.js +2400 -0
  53. package/src/backend/wasm/stdlib/pure.js +720 -0
  54. package/src/backend/wasm/stdlib/time.js +1200 -0
  55. package/src/backend/wasm/types.js +312 -36
  56. package/src/backend/wasm/wat.js +49 -2
  57. package/src/cli-core.js +276 -73
  58. package/src/compiler.js +162 -25
  59. package/src/dts-parser.js +79 -126
  60. package/src/index.js +88 -121
  61. package/src/lexer.js +80 -29
  62. package/src/lower/boxing.js +4 -6
  63. package/src/lower/captures.js +12 -23
  64. package/src/lower/escape.js +4 -9
  65. package/src/lower/functions.js +277 -5
  66. package/src/lower/globals.js +7 -15
  67. package/src/lower/index.js +5 -2
  68. package/src/lower/ownership.js +35 -19
  69. package/src/lower/range.js +2 -5
  70. package/src/lower/walk.js +25 -0
  71. package/src/minifier.js +20 -68
  72. package/src/parser/declarations.js +3 -6
  73. package/src/parser/expressions.js +3 -8
  74. package/src/parser/index.js +18 -3
  75. package/src/parser/statements.js +25 -38
  76. package/src/parser/types.js +1 -2
  77. package/src/resolver.js +3 -27
  78. package/src/templ-lexer.js +29 -63
  79. package/src/templ-parser.js +22 -45
  80. package/src/test-runner.js +20 -0
  81. package/src/typechecker/assignability.js +123 -14
  82. package/src/typechecker/expressions.js +244 -4
  83. package/src/typechecker/index.js +27 -1
  84. package/src/typechecker/resolve.js +71 -12
  85. package/src/typechecker/statements.js +0 -7
  86. package/src/typechecker/stdlib/core.js +292 -103
  87. package/src/typechecker/stdlib/extended.js +471 -4
  88. package/src/typechecker/stdlib/shared.js +53 -0
  89. package/src/typechecker/stdlib.js +2 -0
  90. package/src/typechecker/types.js +545 -8
  91. package/src/vendor.js +2 -2
@@ -6,26 +6,40 @@ const PATH_DISPATCH = {
6
6
  Base: (a) =>
7
7
  `((p) => { if (!p) return "."; const stripped = p.replace(/\\/+$/, ""); if (!stripped) return "/"; const i = stripped.lastIndexOf("/"); return i < 0 ? stripped : stripped.slice(i + 1) || "/"; })(${a[0]})`,
8
8
  Dir: (a) =>
9
- `((p) => { const i = p.lastIndexOf("/"); if (i < 0) return "."; if (i === 0) return "/"; return p.slice(0, i); })(${a[0]})`,
9
+ `((p) => { const i = p.lastIndexOf("/"); if (i < 0) return "."; const res = p.slice(0, i); return res === "" ? "/" : __pathClean(res); })(${a[0]})`,
10
10
  Ext: (a) =>
11
11
  `((p) => { const b = p.slice(p.lastIndexOf("/") + 1); const i = b.lastIndexOf("."); return i <= 0 ? "" : b.slice(i); })(${a[0]})`,
12
12
  IsAbs: (a) => `(${a[0]}).startsWith("/")`,
13
13
  Split: (a) =>
14
14
  `((p) => { const i = p.lastIndexOf("/"); return i < 0 ? ["", p] : [p.slice(0, i + 1), p.slice(i + 1)]; })(${a[0]})`,
15
15
  Match: (a) =>
16
- `((pat, name) => { try { const sp=/[.+^$()|[\\]\\\\]/g; const re = new RegExp("^" + pat.replace(sp, "\\\\$&").replace(/\\*/g, "[^/]*").replace(/\\?/g, "[^/]") + "$"); return [re.test(name), null]; } catch(e) { return [false, "syntax error in pattern"]; } })(${a[0]}, ${a[1]})`,
16
+ `((pat, name) => { const [m, err] = __pathMatch(pat, name); return [m, err ? __path_err_bad_pattern : null]; })(${a[0]}, ${a[1]})`,
17
17
  };
18
18
 
19
19
  /** @type {ThisType<CodeGen>} */
20
20
  export const pathMethods = {
21
21
  _genPath(fn, a, expr) {
22
+ if (fn === "Match") {
23
+ this.useHelper("error");
24
+ this.useHelper("pathError");
25
+ this.useHelper("pathClean");
26
+ }
27
+ if (fn === "Dir") {
28
+ this.useHelper("pathClean");
29
+ }
22
30
  if (fn === "Join") {
23
- this._usesPathClean = true;
24
- const allArgs = expr.args.map((e) => this.genExpr(e)).join(", ");
25
- return `__pathClean([${allArgs}].filter(x => x !== "").join("/"))`;
31
+ this.useHelper("pathClean");
32
+ const allArgs = expr.args
33
+ .map((e) =>
34
+ e._spread || expr.ellipsis
35
+ ? `...(${this.genExpr(e)})`
36
+ : this.genExpr(e),
37
+ )
38
+ .join(", ");
39
+ return `((arr) => arr.length === 0 ? "" : __pathClean(arr.join("/")))([${allArgs}].filter(x => x !== ""))`;
26
40
  }
27
41
  if (fn === "Clean") {
28
- this._usesPathClean = true;
42
+ this.useHelper("pathClean");
29
43
  return `__pathClean(${a()[0]})`;
30
44
  }
31
45
  const gen = PATH_DISPATCH[fn];
@@ -3,9 +3,12 @@
3
3
  /** @typedef {import('../index.js').CodeGen} CodeGen */
4
4
 
5
5
  const RAND_DISPATCH = {
6
- Intn: (a) => `Math.floor(Math.random() * ${a[0]})`,
7
- Int63n: (a) => `Math.floor(Math.random() * ${a[0]})`,
8
- Int31n: (a) => `Math.floor(Math.random() * ${a[0]})`,
6
+ Intn: (a) =>
7
+ `((n) => { if (n <= 0) throw new Error("invalid argument to Intn"); return Math.floor(Math.random() * n); })(${a[0]})`,
8
+ Int63n: (a) =>
9
+ `((n) => { if (n <= 0) throw new Error("invalid argument to Int63n"); return Math.floor(Math.random() * n); })(${a[0]})`,
10
+ Int31n: (a) =>
11
+ `((n) => { if (n <= 0) throw new Error("invalid argument to Int31n"); return Math.floor(Math.random() * n); })(${a[0]})`,
9
12
  Float64: () => "Math.random()",
10
13
  Float32: () => "Math.random()",
11
14
  Int: () => "Math.floor(Math.random() * 2147483647)",
@@ -13,14 +16,16 @@ const RAND_DISPATCH = {
13
16
  Int63: () => "Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)",
14
17
  Seed: () => "(void 0)",
15
18
  Shuffle: (a) =>
16
- `((n, f) => { for (let i = n - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); f(i, j); } })(${a[0]}, ${a[1]})`,
19
+ `((n, f) => { if (n < 0) throw new Error("invalid argument to Shuffle"); for (let i = n - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); f(i, j); } })(${a[0]}, ${a[1]})`,
17
20
  Perm: (a) =>
18
- `((n) => { const a = Array.from({length: n}, (_, i) => i); for (let i = n - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; } return a; })(${a[0]})`,
21
+ `((n) => { if (n < 0) throw new Error("invalid argument to Perm"); const a = Array.from({length: n}, (_, i) => i); for (let i = n - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; } return a; })(${a[0]})`,
22
+ Read: (a) => `__crypto_rand_read(${a[0]})`,
19
23
  };
20
24
 
21
25
  /** @type {ThisType<CodeGen>} */
22
26
  export const randMethods = {
23
27
  _genRand(fn, a) {
28
+ if (fn === "Read") this.useHelper("cryptoRand");
24
29
  const gen = RAND_DISPATCH[fn];
25
30
  return gen ? gen(a()) : undefined;
26
31
  },
@@ -0,0 +1,12 @@
1
+ // src/backend/js/stdlib/sha256.js
2
+ // CodeGen for Go crypto/sha256 package.
3
+
4
+ export const sha256Methods = {
5
+ _genSha256(fn, a) {
6
+ this.useHelper("sha256");
7
+ const args = a();
8
+ if (fn === "Sum256") return `__sha256.Sum256(${args[0]})`;
9
+ if (fn === "New") return "__sha256.New()";
10
+ return undefined;
11
+ },
12
+ };
@@ -0,0 +1,17 @@
1
+ // CodeGen for the `gofront/shared` package: in js packages a shared buffer is
2
+ // just a TypedArray (no linear memory is involved); in wasm packages the facade
3
+ // hands out zero-copy views onto the module memory.
4
+
5
+ import { SHARED_KINDS } from "../../../typechecker/stdlib/shared.js";
6
+
7
+ /** @typedef {import('../index.js').CodeGen} CodeGen */
8
+
9
+ /** @type {ThisType<CodeGen>} */
10
+ export const sharedMethods = {
11
+ _genShared(fn, a) {
12
+ if (!fn.startsWith("New")) return undefined;
13
+ const info = SHARED_KINDS[fn.slice(3)];
14
+ if (!info) return undefined;
15
+ return `new ${info.ctor}(${a()[0]})`;
16
+ },
17
+ };
@@ -1,51 +1,77 @@
1
- // CodeGen for Go `slices` package.
1
+ import { isTypedArraySlice } from "../../../typechecker/types.js";
2
2
 
3
3
  /** @typedef {import('../index.js').CodeGen} CodeGen */
4
4
 
5
5
  const SLICES_DISPATCH = {
6
6
  Contains: (a) => `${a[0]}.includes(${a[1]})`,
7
+ ContainsFunc: (a) =>
8
+ `((s, f) => { s = s || []; for (let i = 0; i < s.length; i++) { if (f(s[i])) return true; } return false; })(${a[0]}, ${a[1]})`,
7
9
  Index: (a) => `${a[0]}.indexOf(${a[1]})`,
10
+ IndexFunc: (a) =>
11
+ `((s, f) => { s = s || []; for (let i = 0; i < s.length; i++) { if (f(s[i])) return i; } return -1; })(${a[0]}, ${a[1]})`,
12
+ BinarySearch: (a) =>
13
+ `((s, target) => { s = s || []; let i = 0, j = s.length; while (i < j) { const h = (i + j) >>> 1; if (s[h] < target) i = h + 1; else j = h; } return [i, i < s.length && s[i] === target]; })(${a[0]}, ${a[1]})`,
14
+ BinarySearchFunc: (a) =>
15
+ `((s, target, cmp) => { s = s || []; let i = 0, j = s.length; while (i < j) { const h = (i + j) >>> 1; if (cmp(s[h], target) < 0) i = h + 1; else j = h; } return [i, i < s.length && cmp(s[i], target) === 0]; })(${a[0]}, ${a[1]}, ${a[2]})`,
8
16
  Compare: (a) =>
9
- `((a, b) => { for (let i = 0; i < a.length && i < b.length; i++) { if (a[i] < b[i]) return -1; if (a[i] > b[i]) return 1; } return a.length - b.length; })(${a[0]}, ${a[1]})`,
10
- Sort: (a) => `${a[0]}.sort((a, b) => a < b ? -1 : a > b ? 1 : 0)`,
11
- SortFunc: (a) => `${a[0]}.sort(${a[1]})`,
12
- SortStableFunc: (a) => `${a[0]}.sort(${a[1]})`,
17
+ `((a, b) => { a = a || []; b = b || []; for (let i = 0; i < a.length && i < b.length; i++) { const x = a[i], y = b[i]; const xN = typeof x === "number" && isNaN(x), yN = typeof y === "number" && isNaN(y); if (xN && yN) continue; if (xN) return -1; if (yN) return 1; if (x < y) return -1; if (x > y) return 1; } return a.length < b.length ? -1 : a.length > b.length ? 1 : 0; })(${a[0]}, ${a[1]})`,
18
+ Sort: (a) =>
19
+ `((s) => s ? s.sort((a, b) => a < b ? -1 : a > b ? 1 : 0) : null)(${a[0]})`,
20
+ SortFunc: (a) => `((s, f) => s ? s.sort(f) : null)(${a[0]}, ${a[1]})`,
21
+ SortStableFunc: (a) => `((s, f) => s ? s.sort(f) : null)(${a[0]}, ${a[1]})`,
13
22
  IsSorted: (a) =>
14
- `((s) => { for (let i = 1; i < s.length; i++) { if (s[i] < s[i-1]) return false; } return true; })(${a[0]})`,
23
+ `((s) => { s = s || []; for (let i = 0; i < s.length; i++) { if (typeof s[i] === "number" && isNaN(s[i])) return false; if (i > 0 && s[i] < s[i-1]) return false; } return true; })(${a[0]})`,
15
24
  IsSortedFunc: (a) =>
16
- `((s, f) => { for (let i = 1; i < s.length; i++) { if (f(s[i], s[i-1]) < 0) return false; } return true; })(${a[0]}, ${a[1]})`,
17
- Reverse: (a) => `${a[0]}.reverse()`,
18
- Max: (a) => `Math.max(...${a[0]})`,
19
- Min: (a) => `Math.min(...${a[0]})`,
25
+ `((s, f) => { s = s || []; for (let i = 1; i < s.length; i++) { if (f(s[i], s[i-1]) < 0) return false; } return true; })(${a[0]}, ${a[1]})`,
26
+ Reverse: (a) => `((s) => s ? s.reverse() : null)(${a[0]})`,
27
+ Max: (a) =>
28
+ `((s) => { if (!s || s.length === 0) throw new Error("slices.Max: empty list"); let m = s[0]; for (let i = 1; i < s.length; i++) { const x = s[i]; if ((typeof x === "number" && isNaN(x)) || x > m || (x === 0 && m === 0 && Object.is(m, -0) && !Object.is(x, -0))) m = x; } return m; })(${a[0]})`,
29
+ Min: (a) =>
30
+ `((s) => { if (!s || s.length === 0) throw new Error("slices.Min: empty list"); let m = s[0]; for (let i = 1; i < s.length; i++) { const x = s[i]; if ((typeof x === "number" && isNaN(x)) || x < m || (x === 0 && m === 0 && Object.is(x, -0))) m = x; } return m; })(${a[0]})`,
20
31
  MaxFunc: (a) =>
21
- `((s, f) => s.reduce((m, x) => f(x, m) > 0 ? x : m))(${a[0]}, ${a[1]})`,
32
+ `((s, f) => { if (!s || s.length === 0) throw new Error("slices.MaxFunc: empty list"); return s.reduce((m, x) => f(x, m) > 0 ? x : m); })(${a[0]}, ${a[1]})`,
22
33
  MinFunc: (a) =>
23
- `((s, f) => s.reduce((m, x) => f(x, m) < 0 ? x : m))(${a[0]}, ${a[1]})`,
24
- Clone: (a) => `${a[0]}.slice()`,
34
+ `((s, f) => { if (!s || s.length === 0) throw new Error("slices.MinFunc: empty list"); return s.reduce((m, x) => f(x, m) < 0 ? x : m); })(${a[0]}, ${a[1]})`,
35
+ Clone: (a) => `((s) => s ? s.slice() : null)(${a[0]})`,
25
36
  Compact: (a) =>
26
- `((s) => s.filter((v, i) => i === 0 || v !== s[i-1]))(${a[0]})`,
37
+ `((s) => { if (!s || s.length === 0) return s; let k = 1; for (let i = 1; i < s.length; i++) { if (s[i] !== s[k - 1]) s[k++] = s[i]; } return ArrayBuffer.isView(s) ? s.subarray(0, k) : (s.length = k, s); })(${a[0]})`,
27
38
  CompactFunc: (a) =>
28
- `((s, f) => s.filter((v, i) => i === 0 || !f(v, s[i-1])))(${a[0]}, ${a[1]})`,
29
- Concat: (a) => `[].concat(${a.join(", ")})`,
30
- Delete: (a) => `[...${a[0]}.slice(0, ${a[1]}), ...${a[0]}.slice(${a[2]})]`,
31
- DeleteFunc: (a) => `${a[0]}.filter((v) => !${a[1]}(v))`,
32
- Insert: (a) =>
33
- `[...${a[0]}.slice(0, ${a[1]}), ${a.slice(2).join(", ")}, ...${a[0]}.slice(${a[1]})]`,
34
- Replace: (a) =>
35
- `[...${a[0]}.slice(0, ${a[1]}), ${a.slice(3).join(", ")}, ...${a[0]}.slice(${a[2]})]`,
36
- Grow: (a) => `${a[0]}.slice()`,
37
- Clip: (a) => `${a[0]}.slice()`,
39
+ `((s, f) => { if (!s || s.length === 0) return s; let k = 1; for (let i = 1; i < s.length; i++) { if (!f(s[i], s[k - 1])) s[k++] = s[i]; } return ArrayBuffer.isView(s) ? s.subarray(0, k) : (s.length = k, s); })(${a[0]}, ${a[1]})`,
40
+ Concat: (a) =>
41
+ a.length === 0
42
+ ? "null"
43
+ : `((...slices) => { const nonNull = slices.filter(Boolean); if (nonNull.length === 0) return slices[0] === null ? null : []; if (ArrayBuffer.isView(nonNull[0])) { const totalLen = nonNull.reduce((sum, s) => sum + s.length, 0); const out = new nonNull[0].constructor(totalLen); let off = 0; for (const s of nonNull) { out.set(s, off); off += s.length; } return out; } return [].concat(...slices.map(s => s ? (ArrayBuffer.isView(s) ? Array.from(s) : s) : [])); })(${a.map((x) => `(${x})`).join(", ")})`,
44
+ Delete: (a) =>
45
+ `((s, i, j) => { if (!s && i === 0 && j === 0) return s; s = s || []; if (i < 0 || j < i || j > s.length) throw new Error("runtime error: slice bounds out of range"); if (ArrayBuffer.isView(s)) { s.copyWithin(i, j); return s.subarray(0, s.length - (j - i)); } s.splice(i, j - i); return s; })(${a[0]}, ${a[1]}, ${a[2]})`,
46
+ DeleteFunc: (a) =>
47
+ `((s, f) => { if (!s || s.length === 0) return s; let k = 0; for (let i = 0; i < s.length; i++) { if (!f(s[i])) s[k++] = s[i]; } return ArrayBuffer.isView(s) ? s.subarray(0, k) : (s.length = k, s); })(${a[0]}, ${a[1]})`,
48
+ Insert: (a, isTyped) => {
49
+ const items = a.slice(2);
50
+ const nilInit = isTyped ? "new Uint8Array(0)" : "[]";
51
+ return `((s, i, ...v) => { if (!s && i === 0) s = ${nilInit}; if (!s || i < 0 || i > s.length) throw new Error("runtime error: slice bounds out of range"); if (ArrayBuffer.isView(s)) { const res = new s.constructor(s.length + v.length); res.set(s.subarray(0, i), 0); res.set(v, i); res.set(s.subarray(i), i + v.length); return res; } s.splice(i, 0, ...v); return s; })(${a[0]}, ${a[1]}${items.length ? `, ${items.join(", ")}` : ""})`;
52
+ },
53
+ Replace: (a, isTyped) => {
54
+ const items = a.slice(3);
55
+ const nilInit = isTyped ? "new Uint8Array(0)" : "[]";
56
+ return `((s, i, j, ...v) => { if (!s && i === 0 && j === 0) s = ${nilInit}; if (!s || i < 0 || j < i || j > s.length) throw new Error("runtime error: slice bounds out of range"); if (ArrayBuffer.isView(s)) { const res = new s.constructor(s.length - (j - i) + v.length); res.set(s.subarray(0, i), 0); res.set(v, i); res.set(s.subarray(j), i + v.length); return res; } s.splice(i, j - i, ...v); return s; })(${a[0]}, ${a[1]}, ${a[2]}${items.length ? `, ${items.join(", ")}` : ""})`;
57
+ },
58
+ Grow: (a, isTyped) => {
59
+ const nilInit = isTyped ? "new Uint8Array(0)" : "[]";
60
+ return `((s, n) => { if (n < 0) throw new Error("runtime error: makeslice: len out of range"); if (!s) return ${nilInit}; return s.slice(); })(${a[0]}, ${a[1]})`;
61
+ },
62
+ Clip: (a) => `((s) => s ? s.slice() : null)(${a[0]})`,
38
63
  };
39
64
 
40
65
  /** @type {ThisType<CodeGen>} */
41
66
  export const slicesMethods = {
42
- _genSlices(fn, a) {
67
+ _genSlices(fn, a, expr) {
43
68
  if (fn === "Equal") {
44
- this._usesEqual = true;
69
+ this.useHelper("equal");
45
70
  const [a1, b1] = a();
46
71
  return `__equal(${a1}, ${b1})`;
47
72
  }
73
+ const isTyped = isTypedArraySlice(expr?.args?.[0]?._type);
48
74
  const gen = SLICES_DISPATCH[fn];
49
- return gen ? gen(a()) : undefined;
75
+ return gen ? gen(a(), isTyped) : undefined;
50
76
  },
51
77
  };
@@ -23,7 +23,7 @@ const SORT_DISPATCH = {
23
23
  /** @type {ThisType<CodeGen>} */
24
24
  export const sortMethods = {
25
25
  _genSort(fn, a) {
26
- if (fn === "Slice" || fn === "SliceStable") this._usesSortSlice = true;
26
+ if (fn === "Slice" || fn === "SliceStable") this.useHelper("sortSlice");
27
27
  const gen = SORT_DISPATCH[fn];
28
28
  return gen ? gen(a()) : undefined;
29
29
  },
@@ -2,32 +2,73 @@
2
2
 
3
3
  /** @typedef {import('../index.js').CodeGen} CodeGen */
4
4
 
5
+ // Parsers return [value, msg|null] from the shared __strconv_* helpers
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})`;
9
+
5
10
  const STRCONV_DISPATCH = {
6
11
  Itoa: (a) => `String(${a[0]})`,
7
- Atoi: (a) =>
8
- `(Number.isNaN(Number(${a[0]})) ? [0, "invalid syntax"] : [Number(${a[0]}) | 0, null])`,
12
+ Atoi: (a) => parsed(`__strconv_atoi(${a[0]})`, "Number(r[0])"),
9
13
  FormatBool: (a) => `String(${a[0]})`,
10
14
  FormatInt: (a) => `(${a[0]}).toString(${a[1]})`,
11
- FormatFloat: (a) => `String(${a[0]})`,
12
- ParseFloat: (a) =>
13
- `(Number.isNaN(Number(${a[0]})) ? [0, "invalid syntax"] : [Number(${a[0]}), null])`,
15
+ FormatFloat: (a) =>
16
+ a.length < 3
17
+ ? `String(${a[0]})`
18
+ : `__strconv_format_float(${a[0]}, ${a[1]}, ${a[2]})`,
19
+ ParseFloat: (a) => parsed(`__strconv_parse_float(${a[0]})`),
14
20
  ParseInt: (a) =>
15
- `(Number.isNaN(parseInt(${a[0]}, ${a[1]} || 10)) ? [0, "invalid syntax"] : [parseInt(${a[0]}, ${a[1]} || 10), null])`,
16
- ParseBool: (a) =>
17
- `(${a[0]} === "true" || ${a[0]} === "1" ? [true, null] : ${a[0]} === "false" || ${a[0]} === "0" ? [false, null] : [false, "invalid syntax"])`,
21
+ parsed(
22
+ `__strconv_parse_int(${a[0]}, ${a[1]}, ${a[2] ?? 64})`,
23
+ "Number(r[0])",
24
+ ),
25
+ ParseBool: (a) => parsed(`__strconv_parse_bool(${a[0]})`),
18
26
  Quote: (a) => `JSON.stringify(${a[0]})`,
19
27
  Unquote: (a) =>
20
- `((s) => { try { const v = JSON.parse(s); return [v, null]; } catch(e) { return ["", "invalid syntax"]; } })(${a[0]})`,
28
+ `((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]})`,
21
29
  AppendInt: (a) =>
22
- `[...(${a[0]}), ...new TextEncoder().encode((${a[1]}).toString(${a[2]}))]`,
30
+ `((d, s) => { const e = new TextEncoder().encode(s); const dl = d ? d.length : 0; const r = new Uint8Array(dl + e.length); if (dl > 0) r.set(d, 0); r.set(e, dl); return r; })(${a[0]}, (${a[1]}).toString(${a[2]}))`,
31
+ AppendUint: (a) =>
32
+ `((d, s) => { const e = new TextEncoder().encode(s); const dl = d ? d.length : 0; const r = new Uint8Array(dl + e.length); if (dl > 0) r.set(d, 0); r.set(e, dl); return r; })(${a[0]}, (BigInt.asUintN(64, BigInt(${a[1]}))).toString(${a[2]}))`,
33
+ AppendBool: (a) =>
34
+ `((d, s) => { const e = new TextEncoder().encode(s); const dl = d ? d.length : 0; const r = new Uint8Array(dl + e.length); if (dl > 0) r.set(d, 0); r.set(e, dl); return r; })(${a[0]}, String(${a[1]}))`,
23
35
  AppendFloat: (a) =>
24
- `[...(${a[0]}), ...new TextEncoder().encode(${a[1]}.toFixed(${a[3]} < 0 ? 6 : ${a[3]}))]`,
36
+ `((d, s) => { const e = new TextEncoder().encode(s); const dl = d ? d.length : 0; const r = new Uint8Array(dl + e.length); if (dl > 0) r.set(d, 0); r.set(e, dl); return r; })(${a[0]}, __strconv_format_float(${a[1]}, typeof (${a[2]}) === "string" ? (${a[2]}).charCodeAt(0) : (${a[2]}), ${a[3]}))`,
37
+ FormatUint: (a) => `(BigInt.asUintN(64, BigInt(${a[0]}))).toString(${a[1]})`,
38
+ ParseUint: (a) =>
39
+ parsed(
40
+ `__strconv_parse_uint(${a[0]}, ${a[1]}, ${a[2] ?? 64})`,
41
+ "Number(r[0])",
42
+ ),
25
43
  };
26
44
 
45
+ const USES_ERROR = new Set([
46
+ "Atoi",
47
+ "ParseFloat",
48
+ "ParseInt",
49
+ "ParseUint",
50
+ "ParseBool",
51
+ "Unquote",
52
+ ]);
53
+ const USES_STRCONV = new Set([
54
+ "Atoi",
55
+ "ParseFloat",
56
+ "ParseInt",
57
+ "ParseUint",
58
+ "ParseBool",
59
+ "FormatFloat",
60
+ "AppendFloat",
61
+ ]);
62
+
27
63
  /** @type {ThisType<CodeGen>} */
28
64
  export const strconvMethods = {
29
65
  _genStrconv(fn, a) {
30
66
  const gen = STRCONV_DISPATCH[fn];
67
+ if (gen && USES_ERROR.has(fn)) {
68
+ this.useHelper("error");
69
+ this.useHelper("strconvError");
70
+ }
71
+ if (gen && USES_STRCONV.has(fn)) this.useHelper("strconv");
31
72
  return gen ? gen(a()) : undefined;
32
73
  },
33
74
  };
@@ -21,30 +21,87 @@ const STRINGS_M2 = {
21
21
  const STRINGS_DISPATCH = {
22
22
  Count: ([s, s2]) =>
23
23
  `((s, sep) => sep === "" ? s.length + 1 : s.split(sep).length - 1)(${s}, ${s2})`,
24
- Replace: ([s, s2, s3]) => `${s}.replace(${s2}, ${s3})`,
24
+ Replace: ([s, s2, s3, n]) =>
25
+ n === undefined
26
+ ? `${s}.replace(${s2}, ${s3})`
27
+ : `((str, o, nw, cnt) => { if (cnt < 0) return str.replaceAll(o, nw); let r = ""; let i = 0; for (; cnt > 0; cnt--) { const j = str.indexOf(o, i); if (j < 0) break; r += str.slice(i, j) + nw; i = j + o.length; if (o.length === 0) { if (i >= str.length) break; r += str[i]; i++; } } return r + str.slice(i); })(${s}, ${s2}, ${s3}, ${n})`,
25
28
  ReplaceAll: ([s, s2, s3]) => `${s}.replaceAll(${s2}, ${s3})`,
26
29
  Trim: ([s, s2]) =>
27
- `${s}.replace(new RegExp(\`^[\${${s2}}]+|[\${${s2}}]+$\`, "g"), "")`,
30
+ `((str, cut) => { const cs = new Set(cut); let i = 0, j = str.length; while (i < j && cs.has(str[i])) i++; while (j > i && cs.has(str[j - 1])) j--; return str.slice(i, j); })(${s}, ${s2})`,
28
31
  TrimPrefix: ([s, s2]) =>
29
32
  `((s, pre) => s.startsWith(pre) ? s.slice(pre.length) : s)(${s}, ${s2})`,
30
33
  TrimSuffix: ([s, s2]) =>
31
34
  `((s, suf) => !suf.length || !s.endsWith(suf) ? s : s.slice(0, -suf.length))(${s}, ${s2})`,
32
- TrimLeft: ([s, s2]) => `${s}.replace(new RegExp(\`^[\${${s2}}]+\`), "")`,
33
- TrimRight: ([s, s2]) => `${s}.replace(new RegExp(\`[\${${s2}}]+$\`), "")`,
35
+ TrimLeft: ([s, s2]) =>
36
+ `((str, cut) => { const cs = new Set(cut); let i = 0; while (i < str.length && cs.has(str[i])) i++; return str.slice(i); })(${s}, ${s2})`,
37
+ TrimRight: ([s, s2]) =>
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})`,
34
39
  EqualFold: ([s, s2]) => `${s}.toLowerCase() === ${s2}.toLowerCase()`,
35
40
  Fields: ([s]) => `(${s}).trim() === '' ? [] : (${s}).trim().split(/\\s+/)`,
41
+ FieldsFunc: ([s, f]) =>
42
+ `((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})`,
36
43
  Cut: ([s, s2]) =>
37
44
  `((s, sep) => { const i = s.indexOf(sep); return i < 0 ? [s, "", false] : [s.slice(0, i), s.slice(i + sep.length), true]; })(${s}, ${s2})`,
38
45
  CutPrefix: ([s, s2]) =>
39
- `(${s}).startsWith(${s2}) ? [(${s}).slice((${s2}).length), true] : [${s}, false]`,
46
+ `((str, pre) => str.startsWith(pre) ? [str.slice(pre.length), true] : [str, false])(${s}, ${s2})`,
40
47
  CutSuffix: ([s, s2]) =>
41
- `(${s}).endsWith(${s2}) ? [(${s}).slice(0, -(${s2}).length), true] : [${s}, false]`,
48
+ `((str, suf) => { if (suf === "") return [str, true]; return str.endsWith(suf) ? [str.slice(0, -suf.length), true] : [str, false]; })(${s}, ${s2})`,
42
49
  SplitN: ([s, s2, s3]) =>
43
- `((s, sep, n) => { if (n === 0) return []; if (n < 0) return s.split(sep); const r = []; let cur = s; for (let i = 1; i < n && cur.length; i++) { const j = cur.indexOf(sep); if (j < 0) break; r.push(cur.slice(0, j)); cur = cur.slice(j + sep.length); } r.push(cur); return r; })(${s}, ${s2}, ${s3})`,
50
+ `((s, sep, n) => {
51
+ if (n === 0) return null;
52
+ if (n < 0) return sep === "" ? [...s] : s.split(sep);
53
+ if (sep === "") {
54
+ const chars = [...s];
55
+ if (chars.length <= n) return chars;
56
+ const r = chars.slice(0, n - 1);
57
+ r.push(chars.slice(n - 1).join(""));
58
+ return r;
59
+ }
60
+ const r = [];
61
+ let cur = s;
62
+ for (let i = 1; i < n && cur.length; i++) {
63
+ const j = cur.indexOf(sep);
64
+ if (j < 0) break;
65
+ r.push(cur.slice(0, j));
66
+ cur = cur.slice(j + sep.length);
67
+ }
68
+ r.push(cur);
69
+ return r;
70
+ })(${s}, ${s2}, ${s3})`,
44
71
  SplitAfter: ([s, s2]) =>
45
- `((s, sep) => { if (sep === "") return [...s]; const r = []; let cur = s; while (cur.length) { const j = cur.indexOf(sep); if (j < 0) { r.push(cur); break; } r.push(cur.slice(0, j + sep.length)); cur = cur.slice(j + sep.length); } return r; })(${s}, ${s2})`,
72
+ `((s, sep) => {
73
+ if (sep === "") return [...s];
74
+ const r = [];
75
+ let cur = s;
76
+ while (true) {
77
+ const j = cur.indexOf(sep);
78
+ if (j < 0) { r.push(cur); break; }
79
+ r.push(cur.slice(0, j + sep.length));
80
+ cur = cur.slice(j + sep.length);
81
+ }
82
+ return r;
83
+ })(${s}, ${s2})`,
46
84
  SplitAfterN: ([s, s2, s3]) =>
47
- `((s, sep, n) => { if (n === 0) return []; if (n < 0 || sep === "") return ((s, sep) => { if (sep === "") return [...s]; const r = []; let cur = s; while (cur.length) { const j = cur.indexOf(sep); if (j < 0) { r.push(cur); break; } r.push(cur.slice(0, j + sep.length)); cur = cur.slice(j + sep.length); } return r; })(s, sep); const r = []; let cur = s; for (let i = 1; i < n && cur.length; i++) { const j = cur.indexOf(sep); if (j < 0) break; r.push(cur.slice(0, j + sep.length)); cur = cur.slice(j + sep.length); } r.push(cur); return r; })(${s}, ${s2}, ${s3})`,
85
+ `((s, sep, n) => {
86
+ if (n === 0) return null;
87
+ if (sep === "") {
88
+ const chars = [...s];
89
+ if (n < 0 || chars.length <= n) return chars;
90
+ const r = chars.slice(0, n - 1);
91
+ r.push(chars.slice(n - 1).join(""));
92
+ return r;
93
+ }
94
+ const r = [];
95
+ let cur = s;
96
+ for (let i = 1; (n < 0 || i < n) && cur.length; i++) {
97
+ const j = cur.indexOf(sep);
98
+ if (j < 0) break;
99
+ r.push(cur.slice(0, j + sep.length));
100
+ cur = cur.slice(j + sep.length);
101
+ }
102
+ r.push(cur);
103
+ return r;
104
+ })(${s}, ${s2}, ${s3})`,
48
105
  IndexAny: ([s, s2]) =>
49
106
  `((s, chars) => { let m = -1; for (const c of chars) { const i = s.indexOf(c); if (i >= 0 && (m < 0 || i < m)) m = i; } return m; })(${s}, ${s2})`,
50
107
  LastIndexAny: ([s, s2]) =>
@@ -55,27 +112,106 @@ const STRINGS_DISPATCH = {
55
112
  IndexByte: ([s, s2]) => `(${s}).indexOf(String.fromCharCode(${s2}))`,
56
113
  LastIndexByte: ([s, s2]) => `(${s}).lastIndexOf(String.fromCharCode(${s2}))`,
57
114
  Map: ([s, s2]) =>
58
- `[...(${s2})].map(c => String.fromCodePoint((${s})(c.codePointAt(0)))).join("")`,
115
+ `((f, s) => { let out = ""; for (const c of s) { const r = f(c.codePointAt(0)); if (r >= 0) out += String.fromCodePoint(r); } return out; })(${s}, ${s2})`,
59
116
  Title: ([s]) => `(${s}).replace(/\\b\\w/g, c => c.toUpperCase())`,
60
117
  TrimFunc: ([s, s2]) =>
61
- `((s, f) => { let l = 0, r = s.length; while (l < r && f(s.codePointAt(l))) l++; while (r > l && f(s.codePointAt(r - 1))) r--; return s.slice(l, r); })(${s}, ${s2})`,
118
+ `((s, f) => {
119
+ let l = 0, r = s.length;
120
+ while (l < r) {
121
+ const cp = s.codePointAt(l);
122
+ if (!f(cp)) break;
123
+ l += cp > 0xFFFF ? 2 : 1;
124
+ }
125
+ while (r > l) {
126
+ let cp, step;
127
+ if (r > 1 && s.charCodeAt(r - 1) >= 0xDC00 && s.charCodeAt(r - 1) <= 0xDFFF &&
128
+ s.charCodeAt(r - 2) >= 0xD800 && s.charCodeAt(r - 2) <= 0xDBFF) {
129
+ cp = s.codePointAt(r - 2);
130
+ step = 2;
131
+ } else {
132
+ cp = s.codePointAt(r - 1);
133
+ step = 1;
134
+ }
135
+ if (!f(cp)) break;
136
+ r -= step;
137
+ }
138
+ return s.slice(l, r);
139
+ })(${s}, ${s2})`,
62
140
  TrimLeftFunc: ([s, s2]) =>
63
- `((s, f) => { let l = 0; while (l < s.length && f(s.codePointAt(l))) l++; return s.slice(l); })(${s}, ${s2})`,
141
+ `((s, f) => {
142
+ let l = 0;
143
+ while (l < s.length) {
144
+ const cp = s.codePointAt(l);
145
+ if (!f(cp)) break;
146
+ l += cp > 0xFFFF ? 2 : 1;
147
+ }
148
+ return s.slice(l);
149
+ })(${s}, ${s2})`,
64
150
  TrimRightFunc: ([s, s2]) =>
65
- `((s, f) => { let r = s.length; while (r > 0 && f(s.codePointAt(r - 1))) r--; return s.slice(0, r); })(${s}, ${s2})`,
151
+ `((s, f) => {
152
+ let r = s.length;
153
+ while (r > 0) {
154
+ let cp, step;
155
+ if (r > 1 && s.charCodeAt(r - 1) >= 0xDC00 && s.charCodeAt(r - 1) <= 0xDFFF &&
156
+ s.charCodeAt(r - 2) >= 0xD800 && s.charCodeAt(r - 2) <= 0xDBFF) {
157
+ cp = s.codePointAt(r - 2);
158
+ step = 2;
159
+ } else {
160
+ cp = s.codePointAt(r - 1);
161
+ step = 1;
162
+ }
163
+ if (!f(cp)) break;
164
+ r -= step;
165
+ }
166
+ return s.slice(0, r);
167
+ })(${s}, ${s2})`,
66
168
  IndexFunc: ([s, s2]) =>
67
169
  `((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})`,
68
170
  LastIndexFunc: ([s, s2]) =>
69
- `((s, f) => { for (let i = s.length - 1; i >= 0; i--) { const cp = s.codePointAt(i); if (f(cp)) return i; } return -1; })(${s}, ${s2})`,
171
+ `((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})`,
70
172
  NewReader: ([s]) =>
71
- `{_src: ${s}, _pos: 0, Read(p) { const n = Math.min(p.length, this._src.length - this._pos); for (let i = 0; i < n; i++) p[i] = this._src.charCodeAt(this._pos + i); this._pos += n; return [n, n === 0 ? "EOF" : null]; }, Len() { return this._src.length - this._pos; }, Reset(s) { this._src = s; this._pos = 0; }}`,
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})`,
72
174
  NewReplacer: (args) =>
73
- `((...pairs) => { const p = []; for (let i = 0; i < pairs.length; i += 2) p.push([pairs[i], pairs[i+1]]); return { _p: p, Replace(s) { let r = s; for (const [o, n] of this._p) r = r.split(o).join(n); return r; } }; })(${args.join(", ")})`,
175
+ `((...pairs) => {
176
+ const p = [];
177
+ for (let i = 0; i < pairs.length; i += 2) {
178
+ if (pairs[i] !== undefined && pairs[i+1] !== undefined) {
179
+ p.push([pairs[i], pairs[i+1]]);
180
+ }
181
+ }
182
+ return {
183
+ _p: p,
184
+ Replace(s) {
185
+ s = s ?? "";
186
+ if (!this._p || this._p.length === 0 || s === "") return s;
187
+ let out = "";
188
+ let i = 0;
189
+ while (i < s.length) {
190
+ let matched = false;
191
+ for (let k = 0; k < this._p.length; k++) {
192
+ const [oldStr, newStr] = this._p[k];
193
+ if (oldStr.length > 0 && s.startsWith(oldStr, i)) {
194
+ out += newStr;
195
+ i += oldStr.length;
196
+ matched = true;
197
+ break;
198
+ }
199
+ }
200
+ if (!matched) {
201
+ out += s[i];
202
+ i++;
203
+ }
204
+ }
205
+ return out;
206
+ }
207
+ };
208
+ })(${args.join(", ")})`,
74
209
  };
75
210
 
76
211
  /** @type {ThisType<CodeGen>} */
77
212
  export const stringsMethods = {
78
213
  _genStrings(fn, a) {
214
+ if (fn === "NewReader") this.useHelper("ioEOF");
79
215
  const args = a();
80
216
  const [s, s2] = args;
81
217
  if (STRINGS_M1[fn]) return `${s}.${STRINGS_M1[fn]}()`;
@@ -0,0 +1,13 @@
1
+ // src/backend/js/stdlib/sync.js
2
+ // CodeGen for Go sync package.
3
+
4
+ export const syncMethods = {
5
+ _genSync(fn) {
6
+ this.useHelper("sync");
7
+ if (fn === "Once") return "__sync_once()";
8
+ if (fn === "Mutex") return "__sync_mutex()";
9
+ if (fn === "RWMutex") return "__sync_rwmutex()";
10
+ if (fn === "WaitGroup") return "__sync_waitgroup()";
11
+ return undefined;
12
+ },
13
+ };
@@ -15,7 +15,7 @@ export const testingMethods = {
15
15
  },
16
16
 
17
17
  _genTestingMethodCall(method, expr) {
18
- this._usesTesting = true;
18
+ this.useHelper("testing");
19
19
  const recv = expr.func.expr;
20
20
  const t = this.genExpr(recv);
21
21
  const args = expr.args
@@ -27,7 +27,7 @@ export const testingMethods = {
27
27
  method === "Logf" ||
28
28
  method === "Skipf"
29
29
  ) {
30
- this._usesSprintf = true;
30
+ this.useHelper("sprintf");
31
31
  }
32
32
  return `${t}.${method}(${args})`;
33
33
  },