gofront 1.7.0 → 1.9.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.
- package/README.md +14 -14
- package/package.json +10 -38
- package/src/backend/js/expressions.js +2060 -389
- package/src/backend/js/index.js +586 -174
- package/src/backend/js/natives.js +383 -0
- package/src/backend/js/runtime.js +936 -1644
- package/src/backend/js/statements.js +1349 -132
- package/src/backend/js/stdlib/index.js +51 -94
- package/src/backend/js/templ.js +67 -118
- package/src/backend/js/type-info.js +436 -0
- package/src/backend/shared/fixed-float.js +20 -0
- package/src/backend/shared/fmt-types.js +80 -0
- package/src/backend/shared/host-unicode.js +36 -0
- package/src/backend/wasm/boundary.js +753 -872
- package/src/backend/wasm/emit-builtins.js +914 -237
- package/src/backend/wasm/emit-exprs.js +1456 -195
- package/src/backend/wasm/emit-maps.js +78 -380
- package/src/backend/wasm/emit-stmts.js +1659 -214
- package/src/backend/wasm/emit.js +614 -88
- package/src/backend/wasm/encode.js +43 -13
- package/src/backend/wasm/globals.js +491 -0
- package/src/backend/wasm/glue.js +2 -4
- package/src/backend/wasm/index.js +406 -3195
- package/src/backend/wasm/method-expr.js +392 -0
- package/src/backend/wasm/module-emitter.js +1615 -0
- package/src/backend/wasm/monomorph.js +164 -9
- package/src/backend/wasm/natives.js +596 -0
- package/src/backend/wasm/prescan.js +249 -0
- package/src/backend/wasm/prune-funcs.js +89 -0
- package/src/backend/wasm/stdlib/index.js +2 -37
- package/src/backend/wasm/stdlib/simd.js +293 -28
- package/src/backend/wasm/types.js +260 -27
- package/src/backend/wasm/variadic.js +93 -0
- package/src/backend/wasm/wat.js +47 -20
- package/src/{cli-core.js → cli/cli-core.js} +241 -11
- package/src/{test-runner.js → cli/test-runner.js} +188 -122
- package/src/{vendor.js → cli/vendor.js} +3 -4
- package/src/compiler.js +202 -386
- package/src/index.js +15 -169
- package/src/{lexer.js → lexer/lexer.js} +203 -62
- package/src/{templ-lexer.js → lexer/templ-lexer.js} +61 -18
- package/src/{tokens.js → lexer/tokens.js} +2 -0
- package/src/link/sourcemap.js +97 -0
- package/src/link/stdlib.js +737 -0
- package/src/link/type-names.js +148 -0
- package/src/link/wasm.js +318 -0
- package/src/lower/bounds.js +547 -0
- package/src/lower/boxing.js +1 -1
- package/src/lower/captures.js +267 -64
- package/src/lower/embedding.js +32 -3
- package/src/lower/errors-as.js +196 -0
- package/src/lower/fmt-formatters.js +905 -0
- package/src/lower/functions.js +2 -0
- package/src/lower/gen-types.js +274 -0
- package/src/lower/index.js +33 -19
- package/src/lower/json-codecs.js +1379 -0
- package/src/lower/ownership.js +47 -8
- package/src/lower/range.js +86 -1
- package/src/lower/reachability.js +150 -0
- package/src/lower/walk.js +12 -0
- package/src/parser/declarations.js +83 -15
- package/src/parser/expressions.js +110 -18
- package/src/parser/index.js +2 -1
- package/src/parser/statements.js +38 -22
- package/src/{templ-parser.js → parser/templ.js} +179 -17
- package/src/parser/types.js +70 -9
- package/src/resolver.js +21 -1
- package/src/typechecker/assignability.js +291 -19
- package/src/typechecker/const-fold.js +399 -0
- package/src/typechecker/constants.js +282 -0
- package/src/typechecker/expressions.js +1220 -183
- package/src/typechecker/index.js +659 -92
- package/src/typechecker/init-order.js +426 -0
- package/src/typechecker/operand-text.js +40 -0
- package/src/typechecker/resolve.js +613 -62
- package/src/typechecker/statements.js +212 -20
- package/src/typechecker/stdlib/excluded.js +902 -0
- package/src/typechecker/stdlib/index.js +38 -0
- package/src/typechecker/stdlib/simd.js +65 -15
- package/src/typechecker/termination.js +11 -2
- package/src/typechecker/types.js +268 -514
- package/stdlib/bytes/buffer.go +479 -0
- package/stdlib/bytes/bytes.go +1010 -0
- package/stdlib/bytes/reader.go +161 -0
- package/stdlib/cmp/cmp.go +65 -0
- package/stdlib/context/context.go +115 -0
- package/stdlib/crypto/rand/rand.go +34 -0
- package/stdlib/crypto/sha256/sha256.go +249 -0
- package/stdlib/encoding/base64/base64.go +331 -0
- package/stdlib/encoding/binary/binary.go +332 -0
- package/stdlib/encoding/hex/hex.go +140 -0
- package/stdlib/encoding/json/errors.go +31 -0
- package/stdlib/encoding/json/indent.go +71 -0
- package/stdlib/encoding/json/json.go +62 -0
- package/stdlib/encoding/json/number.go +12 -0
- package/stdlib/encoding/json/stream.go +173 -0
- package/stdlib/errors/errors.go +174 -0
- package/stdlib/fmt/errors.go +110 -0
- package/stdlib/fmt/format.go +732 -0
- package/stdlib/fmt/print.go +1232 -0
- package/stdlib/gom/gom.go +293 -0
- package/stdlib/hash/hash.go +53 -0
- package/stdlib/html/html.go +251 -0
- package/stdlib/internal/fmtrt/fmtrt.go +163 -0
- package/stdlib/internal/jsonrt/dec.go +871 -0
- package/stdlib/internal/jsonrt/enc.go +516 -0
- package/stdlib/internal/jsonrt/indent.go +135 -0
- package/stdlib/internal/jsonrt/number.go +27 -0
- package/stdlib/internal/jsonrt/reader.go +73 -0
- package/stdlib/internal/jsonrt/scanner.go +658 -0
- package/stdlib/io/io.go +502 -0
- package/stdlib/iter/iter.go +18 -0
- package/stdlib/maps/maps.go +131 -0
- package/stdlib/math/bits/bits.go +507 -0
- package/stdlib/math/extra.go +272 -0
- package/stdlib/math/math.go +439 -0
- package/stdlib/math/rand/rand.go +297 -0
- package/stdlib/os/os.go +93 -0
- package/stdlib/path/filepath/filepath.go +47 -0
- package/stdlib/path/path.go +394 -0
- package/stdlib/regexp/regexp.go +231 -0
- package/stdlib/slices/slices.go +526 -0
- package/stdlib/slices/sort.go +209 -0
- package/stdlib/slices/zsort_cmp.go +480 -0
- package/stdlib/slices/zsort_ordered.go +482 -0
- package/stdlib/sort/sort.go +500 -0
- package/stdlib/sort/zsort_func.go +480 -0
- package/stdlib/sort/zsort_iface.go +480 -0
- package/stdlib/strconv/float.go +1699 -0
- package/stdlib/strconv/isprint.go +752 -0
- package/stdlib/strconv/quote.go +526 -0
- package/stdlib/strconv/strconv.go +616 -0
- package/stdlib/strings/builder.go +95 -0
- package/stdlib/strings/reader.go +163 -0
- package/stdlib/strings/replacer.go +102 -0
- package/stdlib/strings/strings.go +807 -0
- package/stdlib/sync/sync.go +268 -0
- package/stdlib/testing/async_js.go +61 -0
- package/stdlib/testing/benchmark.go +297 -0
- package/stdlib/testing/testing.go +523 -0
- package/stdlib/time/duration.go +233 -0
- package/stdlib/time/format.go +745 -0
- package/stdlib/time/parse.go +790 -0
- package/stdlib/time/sleep_js.go +63 -0
- package/stdlib/time/time.go +834 -0
- package/stdlib/unicode/unicode.go +250 -0
- package/stdlib/unicode/utf8/utf8.go +464 -0
- package/src/backend/js/stdlib/base64.js +0 -12
- package/src/backend/js/stdlib/binary.js +0 -102
- package/src/backend/js/stdlib/bits.js +0 -46
- package/src/backend/js/stdlib/builder.js +0 -63
- package/src/backend/js/stdlib/bytes.js +0 -122
- package/src/backend/js/stdlib/errors.js +0 -44
- package/src/backend/js/stdlib/fmt.js +0 -178
- package/src/backend/js/stdlib/gom.js +0 -155
- package/src/backend/js/stdlib/hex.js +0 -17
- package/src/backend/js/stdlib/html.js +0 -20
- package/src/backend/js/stdlib/io.js +0 -137
- package/src/backend/js/stdlib/json.js +0 -972
- package/src/backend/js/stdlib/maps.js +0 -45
- package/src/backend/js/stdlib/math.js +0 -69
- package/src/backend/js/stdlib/os.js +0 -13
- package/src/backend/js/stdlib/path.js +0 -48
- package/src/backend/js/stdlib/rand.js +0 -32
- package/src/backend/js/stdlib/regexp.js +0 -54
- package/src/backend/js/stdlib/sha256.js +0 -12
- package/src/backend/js/stdlib/shared.js +0 -17
- package/src/backend/js/stdlib/simd.js +0 -24
- package/src/backend/js/stdlib/slices.js +0 -77
- package/src/backend/js/stdlib/sort.js +0 -30
- package/src/backend/js/stdlib/strconv.js +0 -74
- package/src/backend/js/stdlib/strings.js +0 -222
- package/src/backend/js/stdlib/sync.js +0 -13
- package/src/backend/js/stdlib/testing.js +0 -34
- package/src/backend/js/stdlib/time.js +0 -128
- package/src/backend/js/stdlib/unicode.js +0 -25
- package/src/backend/js/stdlib/utf8.js +0 -26
- package/src/backend/wasm/const-fold.js +0 -205
- package/src/backend/wasm/emit-stdlib.js +0 -13
- package/src/backend/wasm/stdlib/bytes.js +0 -5039
- package/src/backend/wasm/stdlib/core.js +0 -8903
- package/src/backend/wasm/stdlib/io.js +0 -2161
- package/src/backend/wasm/stdlib/packages.js +0 -2400
- package/src/backend/wasm/stdlib/pure.js +0 -720
- package/src/backend/wasm/stdlib/time.js +0 -1200
- package/src/typechecker/stdlib/core.js +0 -801
- package/src/typechecker/stdlib/extended.js +0 -914
- package/src/typechecker/stdlib.js +0 -22
- /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
- /package/src/{colors.js → cli/colors.js} +0 -0
- /package/src/{dev-server.js → cli/dev-server.js} +0 -0
- /package/src/{minifier.js → cli/minifier.js} +0 -0
- /package/src/{project-config.js → cli/project-config.js} +0 -0
- /package/src/{pwa.js → cli/pwa.js} +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// JS <-> WASM boundary: type classification, marshalling helpers (WASM IR)
|
|
3
3
|
// and the generated JS facade that exposes a wasm package to JS callers.
|
|
4
4
|
//
|
|
5
|
-
// Boundary v1 rules (
|
|
5
|
+
// Boundary v1 rules (plans/v1.5.0/wasm-mvp-plan.md, Phase 5):
|
|
6
6
|
// - primitives: bool <-> i32, narrow ints <-> i32, int/int64 <-> f64 at the
|
|
7
7
|
// boundary (i64 inside; exact for |v| <= 2^53, which the facade already
|
|
8
8
|
// enforces, and keeps BigInt boxing off hot paths), floats direct,
|
|
@@ -31,24 +31,18 @@
|
|
|
31
31
|
// module's exported linear memory; JS sees a zero-copy TypedArray over
|
|
32
32
|
// `memory.buffer`. Only TypedArrays that already alias that memory (ones
|
|
33
33
|
// handed out by this facade) may be passed back in.
|
|
34
|
-
// - `*testing.T`: the harness's JS test object, passed as an opaque
|
|
35
|
-
// externref (test methods are routed back to JS via `env.testing_*`).
|
|
36
34
|
|
|
35
|
+
import { BROWSER_GLOBALS } from "../../typechecker/types.js";
|
|
36
|
+
import { FIXED_FLOAT_JS } from "../shared/fixed-float.js";
|
|
37
37
|
import {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
HELPER_PATH_CLEAN,
|
|
43
|
-
HELPER_SHA256,
|
|
44
|
-
HELPER_SPRINTF,
|
|
45
|
-
HELPER_STRCONV,
|
|
46
|
-
} from "../js/runtime.js";
|
|
38
|
+
unicodeIsJs,
|
|
39
|
+
unicodeToLowerJs,
|
|
40
|
+
unicodeToUpperJs,
|
|
41
|
+
} from "../shared/host-unicode.js";
|
|
47
42
|
import {
|
|
48
43
|
anonIfaceName,
|
|
49
44
|
getInterfaceMethodList,
|
|
50
45
|
getSharedInfo,
|
|
51
|
-
isTestingT,
|
|
52
46
|
} from "./types.js";
|
|
53
47
|
|
|
54
48
|
// ── Type classification ──────────────────────────────────────
|
|
@@ -85,8 +79,16 @@ function toRef(info) {
|
|
|
85
79
|
return { kind: "ref", nullable: true, typeIndex: info.typeIndex };
|
|
86
80
|
}
|
|
87
81
|
|
|
82
|
+
// The facade and its wasm export names build identifiers from a struct's name,
|
|
83
|
+
// so the dot of a stdlib type (`strings.Builder`) becomes `$`.
|
|
88
84
|
function structDesc(info, ptr) {
|
|
89
|
-
return {
|
|
85
|
+
return {
|
|
86
|
+
k: "struct",
|
|
87
|
+
name: info.name.replaceAll(".", "$"),
|
|
88
|
+
ptr,
|
|
89
|
+
info,
|
|
90
|
+
wType: toRef(info),
|
|
91
|
+
};
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
function funcSigFromAst(t) {
|
|
@@ -98,6 +100,10 @@ function funcSigFromAst(t) {
|
|
|
98
100
|
? t.returnType.types
|
|
99
101
|
: [t.returnType]
|
|
100
102
|
: [],
|
|
103
|
+
// `_variadic`: already desugared to a slice by variadic.js
|
|
104
|
+
variadic: Boolean(
|
|
105
|
+
t.params?.at(-1)?.variadic || t.params?.at(-1)?._variadic,
|
|
106
|
+
),
|
|
101
107
|
};
|
|
102
108
|
}
|
|
103
109
|
|
|
@@ -105,17 +111,9 @@ function funcSigFromAst(t) {
|
|
|
105
111
|
// Returns a descriptor `{ k, ... }`; `k === "unsupported"` carries `what`.
|
|
106
112
|
function classifyType(goType, mod) {
|
|
107
113
|
if (!goType) return { k: "void" };
|
|
108
|
-
if (isTestingT(goType)) return { k: "extern", wType: "externref" };
|
|
109
114
|
|
|
110
115
|
if (goType.kind === "TypeName" || goType.kind === "Ident") {
|
|
111
116
|
if (goType.name === "error") return { k: "error", wType: "anyref" };
|
|
112
|
-
if (goType.name === "time.Duration" || goType.name === "Duration")
|
|
113
|
-
return { k: "i64", unsigned: false, wType: "i64" };
|
|
114
|
-
if (goType.name === "time.Time" || goType.name === "Time") {
|
|
115
|
-
const info = mod.getStructType("time.Time");
|
|
116
|
-
if (info)
|
|
117
|
-
return { k: "time", name: "time.Time", info, wType: toRef(info) };
|
|
118
|
-
}
|
|
119
117
|
const info = mod.getStructType(goType.name);
|
|
120
118
|
if (info) return structDesc(info, false);
|
|
121
119
|
const resolved = mod.checker?.types?.get(goType.name);
|
|
@@ -148,10 +146,7 @@ function classifyType(goType, mod) {
|
|
|
148
146
|
case "interface":
|
|
149
147
|
return classifyInterface(t, mod);
|
|
150
148
|
case "simd":
|
|
151
|
-
return
|
|
152
|
-
k: "unsupported",
|
|
153
|
-
what: "simd.F32x4 cannot cross the JS↔WASM boundary in 1.7.0 (use shared.Float32 instead)",
|
|
154
|
-
};
|
|
149
|
+
return classifySimd(t, mod);
|
|
155
150
|
default:
|
|
156
151
|
return {
|
|
157
152
|
k: "unsupported",
|
|
@@ -210,7 +205,12 @@ function classifyBasic(n) {
|
|
|
210
205
|
if (I32_INTS.has(n))
|
|
211
206
|
return { k: "i32", unsigned: UNSIGNED.has(n), wType: "i32" };
|
|
212
207
|
if (I64_INTS.has(n))
|
|
213
|
-
return {
|
|
208
|
+
return {
|
|
209
|
+
k: "i64",
|
|
210
|
+
unsigned: UNSIGNED.has(n),
|
|
211
|
+
is64: n === "int64" || n === "uint64",
|
|
212
|
+
wType: "i64",
|
|
213
|
+
};
|
|
214
214
|
const d = BASIC_DESCS[n];
|
|
215
215
|
return d ? { ...d } : { k: "unsupported", what: `type '${n}'` };
|
|
216
216
|
}
|
|
@@ -219,26 +219,31 @@ function classifyNamed(t, mod) {
|
|
|
219
219
|
if (
|
|
220
220
|
t.name === "simd.F32x4" ||
|
|
221
221
|
t.name === "F32x4" ||
|
|
222
|
+
t.name === "simd.Mask32x4" ||
|
|
223
|
+
t.name === "Mask32x4" ||
|
|
222
224
|
t.underlying?.kind === "simd"
|
|
223
225
|
) {
|
|
224
|
-
return
|
|
225
|
-
k: "unsupported",
|
|
226
|
-
what: "simd.F32x4 cannot cross the JS↔WASM boundary in 1.7.0 (use shared.Float32 instead)",
|
|
227
|
-
};
|
|
226
|
+
return classifySimd(t.underlying ?? t, mod, t.name);
|
|
228
227
|
}
|
|
229
228
|
if (t.name === "error") return { k: "error", wType: "anyref" };
|
|
230
|
-
if (t.name === "time.Duration")
|
|
231
|
-
return { k: "i64", unsigned: false, wType: "i64" };
|
|
232
|
-
if (t.name === "time.Time") {
|
|
233
|
-
const info = mod.getStructType("time.Time");
|
|
234
|
-
if (info) return { k: "time", name: "time.Time", info, wType: toRef(info) };
|
|
235
|
-
}
|
|
236
229
|
const info = mod.getStructType(t.name);
|
|
237
230
|
if (info) return structDesc(info, false);
|
|
238
231
|
if (t.underlying) return classifyType(t.underlying, mod);
|
|
239
232
|
return { k: "unsupported", what: `named type '${t.name}'` };
|
|
240
233
|
}
|
|
241
234
|
|
|
235
|
+
// F32x4 crosses as a copied 4-element Float32Array, Mask32x4 as a 4-element
|
|
236
|
+
// boolean array. Both travel through the wasm boundary as a GC array of the
|
|
237
|
+
// four i32 lane words (`arr`), converted by __v128_from/__v128_to.
|
|
238
|
+
function classifySimd(t, mod, name = "") {
|
|
239
|
+
const isMask = t.simd === "Mask32x4" || name.endsWith("Mask32x4");
|
|
240
|
+
const arr = classifyArray(
|
|
241
|
+
{ kind: "array", elem: { kind: "basic", name: "int32" }, size: 4 },
|
|
242
|
+
mod,
|
|
243
|
+
);
|
|
244
|
+
return { k: isMask ? "mask4" : "f32x4", wType: "v128", arr };
|
|
245
|
+
}
|
|
246
|
+
|
|
242
247
|
function classifyAnonStruct(t, mod) {
|
|
243
248
|
const rawFields = [];
|
|
244
249
|
if (t.fields instanceof Map) {
|
|
@@ -280,15 +285,23 @@ function classifyPointer(t, mod) {
|
|
|
280
285
|
if (base.k === "unsupported") return base;
|
|
281
286
|
if (base.k === "struct" && !base.ptr) return { ...base, ptr: true };
|
|
282
287
|
if (base.k === "anon_struct" && !base.ptr) return { ...base, ptr: true };
|
|
283
|
-
if (base.k === "time" && !base.ptr) return { ...base, ptr: true };
|
|
284
288
|
const boxInfo = mod.getBoxType(t.base);
|
|
285
|
-
const
|
|
289
|
+
const ptrInfo = mod.getPtrType ? mod.getPtrType(t.base) : null;
|
|
290
|
+
const key = `${base.k}${base.unsigned ? "_u" : ""}${base.is64 ? "_64" : ""}_${boxInfo.typeIndex}`;
|
|
286
291
|
return {
|
|
287
292
|
k: "ptr_box",
|
|
288
293
|
base,
|
|
289
294
|
boxInfo,
|
|
295
|
+
ptrInfo,
|
|
290
296
|
key,
|
|
291
|
-
wType:
|
|
297
|
+
wType: ptrInfo
|
|
298
|
+
? {
|
|
299
|
+
kind: "ref",
|
|
300
|
+
nullable: true,
|
|
301
|
+
typeIndex: ptrInfo.typeIndex,
|
|
302
|
+
typeName: ptrInfo.name,
|
|
303
|
+
}
|
|
304
|
+
: { kind: "ref", nullable: true, typeIndex: boxInfo.typeIndex },
|
|
292
305
|
};
|
|
293
306
|
}
|
|
294
307
|
|
|
@@ -352,7 +365,15 @@ function classifySlice(t, mod) {
|
|
|
352
365
|
k: "slice",
|
|
353
366
|
elem,
|
|
354
367
|
sliceInfo,
|
|
355
|
-
|
|
368
|
+
// []F32x4 and []Mask32x4 share a wasm slice type but cross differently,
|
|
369
|
+
// so they get separate helper keys.
|
|
370
|
+
key:
|
|
371
|
+
wTypeKey(sliceInfo.elemWType) +
|
|
372
|
+
(elem.k === "f32x4" || elem.k === "mask4"
|
|
373
|
+
? `_${elem.k}`
|
|
374
|
+
: elem.is64
|
|
375
|
+
? "_i64"
|
|
376
|
+
: ""),
|
|
356
377
|
wType: { kind: "ref", nullable: true, typeIndex: sliceInfo.typeIndex },
|
|
357
378
|
};
|
|
358
379
|
}
|
|
@@ -372,7 +393,7 @@ function classifyArray(t, mod) {
|
|
|
372
393
|
elem,
|
|
373
394
|
size: arraySize(t),
|
|
374
395
|
arrInfo,
|
|
375
|
-
key: wTypeKey(arrInfo.elemWType),
|
|
396
|
+
key: wTypeKey(arrInfo.elemWType) + (elem.is64 ? "_i64" : ""),
|
|
376
397
|
wType: { kind: "ref", nullable: true, typeIndex: arrInfo.typeIndex },
|
|
377
398
|
};
|
|
378
399
|
}
|
|
@@ -387,6 +408,9 @@ function classifySig(t, mod) {
|
|
|
387
408
|
}
|
|
388
409
|
|
|
389
410
|
function classifyFunc(t, mod) {
|
|
411
|
+
// A variadic func value is a slice-taking closure in wasm but a rest-param
|
|
412
|
+
// function in JS; no adapter yet, so it may not cross.
|
|
413
|
+
if (t.variadic) return { k: "unsupported", what: "variadic func value" };
|
|
390
414
|
const sig = classifySig(t, mod);
|
|
391
415
|
if (sig.k === "unsupported") return sig;
|
|
392
416
|
const closureInfo = mod.getClosureType(t);
|
|
@@ -451,7 +475,7 @@ function classifyNamedInterface(t, mod) {
|
|
|
451
475
|
const desc = {
|
|
452
476
|
k: "iface",
|
|
453
477
|
name: t.name,
|
|
454
|
-
key: t.name,
|
|
478
|
+
key: t.name.replaceAll(".", "_"),
|
|
455
479
|
wType: "anyref",
|
|
456
480
|
methods: [],
|
|
457
481
|
concrete: [],
|
|
@@ -544,19 +568,23 @@ export function collectBoundaryMeta(progs, funcDecls, mod) {
|
|
|
544
568
|
for (const p of progs) {
|
|
545
569
|
const pkgName = p.pkg?.name ?? "main";
|
|
546
570
|
const pkgTarget = p.target ?? "wasm";
|
|
571
|
+
// The tests of a package run inside the module: only the generated
|
|
572
|
+
// driver crosses the boundary.
|
|
573
|
+
if (isTestSource(p._path ?? p._filename)) continue;
|
|
547
574
|
for (const d of p.decls ?? []) {
|
|
548
575
|
if (d.kind === "TypeDecl" && d.type?.kind === "StructType") {
|
|
549
576
|
collectStructDecl(d, pkgName, pkgTarget, meta, mod);
|
|
550
577
|
} else if (d.kind === "ConstDecl" && pkgTarget === "wasm") {
|
|
551
578
|
collectConstDecl(d, pkgName, meta, errors);
|
|
552
579
|
} else if (d.kind === "VarDecl" && pkgTarget === "wasm") {
|
|
553
|
-
|
|
580
|
+
collectVarDecl(d, meta, mod);
|
|
554
581
|
}
|
|
555
582
|
}
|
|
556
583
|
}
|
|
557
584
|
|
|
558
585
|
for (const fn of funcDecls) {
|
|
559
586
|
if (fn._isClosure || fn._pkgTarget !== "wasm") continue;
|
|
587
|
+
if (isTestSource(fn._srcFile) && fn.name !== TEST_DRIVER) continue;
|
|
560
588
|
if (fn._isMethod) collectMethodMeta(fn, meta, errors, mod);
|
|
561
589
|
else collectFuncMeta(fn, meta, errors, mod);
|
|
562
590
|
}
|
|
@@ -565,12 +593,19 @@ export function collectBoundaryMeta(progs, funcDecls, mod) {
|
|
|
565
593
|
return meta;
|
|
566
594
|
}
|
|
567
595
|
|
|
596
|
+
// The driver function the test runner compiles in (src/cli/test-runner.js).
|
|
597
|
+
const TEST_DRIVER = "GofrontRunTests";
|
|
598
|
+
|
|
599
|
+
const isTestSource = (file) => Boolean(file?.endsWith("_test.go"));
|
|
600
|
+
|
|
568
601
|
function collectStructDecl(d, pkgName, pkgTarget, meta, mod) {
|
|
569
|
-
if (
|
|
602
|
+
if (meta.structByName.has(d.name)) return;
|
|
570
603
|
const info = mod.getStructType(d.name);
|
|
571
604
|
if (!info) return;
|
|
572
605
|
const s = { name: d.name, pkgName, pkgTarget, info, methods: [] };
|
|
573
|
-
|
|
606
|
+
// An unexported struct has no class of its own, but its exported methods
|
|
607
|
+
// reach JS through the exported structs that embed it.
|
|
608
|
+
if (isExported(d.name)) meta.structs.push(s);
|
|
574
609
|
meta.structByName.set(d.name, s);
|
|
575
610
|
}
|
|
576
611
|
|
|
@@ -632,17 +667,24 @@ function checkDesc(desc, where, errors) {
|
|
|
632
667
|
}
|
|
633
668
|
|
|
634
669
|
// Exported `var Buf = shared.NewFloat32(n)` globals are exposed to JS as
|
|
635
|
-
// TypedArray views (read once after instantiation; sizing is fixed).
|
|
636
|
-
|
|
670
|
+
// TypedArray views (read once after instantiation; sizing is fixed). Other
|
|
671
|
+
// exported vars of a supported boundary type are read once when the module
|
|
672
|
+
// loads, after package initialization (`var Limit = 40` is `Limit` in the
|
|
673
|
+
// facade); a var of an unsupported type is left out.
|
|
674
|
+
function collectVarDecl(d, meta, mod) {
|
|
637
675
|
for (const spec of d.decls ?? d.specs ?? [d]) {
|
|
638
676
|
const names = spec.names ?? (spec.name ? [spec.name] : []);
|
|
639
677
|
const values = spec.value ?? (spec.init ? [spec.init] : []);
|
|
640
678
|
for (let i = 0; i < names.length; i++) {
|
|
641
679
|
const name = names[i];
|
|
642
680
|
if (!isExported(name)) continue;
|
|
643
|
-
const goType =
|
|
644
|
-
|
|
645
|
-
|
|
681
|
+
const goType =
|
|
682
|
+
spec.type ?? mod.resolveGlobal(name)?.goType ?? values[i]?._type;
|
|
683
|
+
const shared = Boolean(getSharedInfo(goType, mod.checker));
|
|
684
|
+
const desc = classifyType(goType, mod);
|
|
685
|
+
if (!shared && (!desc || desc.k === "unsupported" || desc.k === "void"))
|
|
686
|
+
continue;
|
|
687
|
+
meta.vars.push({ name, desc, shared });
|
|
646
688
|
}
|
|
647
689
|
}
|
|
648
690
|
}
|
|
@@ -657,6 +699,7 @@ function collectMethodMeta(fn, meta, errors, mod) {
|
|
|
657
699
|
exportName: fn._exportName,
|
|
658
700
|
fnName: fn.name,
|
|
659
701
|
ptrRecv: fn.params[0].type?.kind === "pointer",
|
|
702
|
+
variadic: Boolean(fn.params.at(-1)?._variadic),
|
|
660
703
|
params: fn.params
|
|
661
704
|
.slice(1)
|
|
662
705
|
.map((p) => checkDesc(classifyType(p.type, mod), where, errors)),
|
|
@@ -673,6 +716,7 @@ function collectFuncMeta(fn, meta, errors, mod) {
|
|
|
673
716
|
name: fn.name,
|
|
674
717
|
exportName: fn.name,
|
|
675
718
|
pkgName: fn._pkgName,
|
|
719
|
+
variadic: Boolean(fn.params?.at(-1)?._variadic),
|
|
676
720
|
params: (fn.params ?? []).map((p) =>
|
|
677
721
|
checkDesc(classifyType(p.type, mod), where, errors),
|
|
678
722
|
),
|
|
@@ -711,8 +755,10 @@ function visitNeed(desc, needs, errors, mod) {
|
|
|
711
755
|
case "error":
|
|
712
756
|
needs.error = true;
|
|
713
757
|
return;
|
|
714
|
-
case "
|
|
715
|
-
|
|
758
|
+
case "f32x4":
|
|
759
|
+
case "mask4":
|
|
760
|
+
needs.simd = desc.arr;
|
|
761
|
+
visitNeed(desc.arr, needs, errors, mod);
|
|
716
762
|
return;
|
|
717
763
|
case "slice":
|
|
718
764
|
case "array": {
|
|
@@ -790,7 +836,7 @@ function collectNeeds(meta) {
|
|
|
790
836
|
funcs: new Map(), // key -> desc
|
|
791
837
|
ifaces: new Map(), // interface name -> desc
|
|
792
838
|
error: false,
|
|
793
|
-
|
|
839
|
+
simd: null, // array desc carrying vectors across the boundary
|
|
794
840
|
shared: false, // any shared buffer on the surface
|
|
795
841
|
};
|
|
796
842
|
const errors = [];
|
|
@@ -817,11 +863,20 @@ export function scanBoundaryImports(mod, meta) {
|
|
|
817
863
|
const needs = collectNeeds(meta);
|
|
818
864
|
meta._needs = needs;
|
|
819
865
|
for (const [key, desc] of needs.funcs) {
|
|
866
|
+
const paramDescs = desc.params ?? [];
|
|
867
|
+
const returnDescs = desc.returns ?? [];
|
|
820
868
|
mod.getOrAddFuncImport(
|
|
821
869
|
"env",
|
|
822
870
|
`__invoke$${key}`,
|
|
823
|
-
[
|
|
824
|
-
|
|
871
|
+
[
|
|
872
|
+
"externref",
|
|
873
|
+
...desc.closureInfo.paramWTypes.map((w, i) =>
|
|
874
|
+
extType(w, paramDescs[i]?.is64),
|
|
875
|
+
),
|
|
876
|
+
],
|
|
877
|
+
desc.closureInfo.returnWTypes.map((w, i) =>
|
|
878
|
+
extType(w, returnDescs[i]?.is64),
|
|
879
|
+
),
|
|
825
880
|
);
|
|
826
881
|
}
|
|
827
882
|
}
|
|
@@ -835,17 +890,25 @@ export function scanBoundaryImports(mod, meta) {
|
|
|
835
890
|
function isGcRef(wType) {
|
|
836
891
|
return wType === "anyref" || (typeof wType === "object" && wType !== null);
|
|
837
892
|
}
|
|
838
|
-
|
|
839
|
-
|
|
893
|
+
// A v128 crosses as an externref holding a GC array of its four i32 lanes;
|
|
894
|
+
// the converting helpers are registered first thing in addBoundaryHelpers.
|
|
895
|
+
const v128Conv = { from: null, to: null };
|
|
896
|
+
|
|
897
|
+
function extType(wType, is64 = false) {
|
|
898
|
+
if (wType === "v128") return "externref";
|
|
899
|
+
if (wType === "i64") return is64 ? "i64" : "f64";
|
|
840
900
|
if (isGcRef(wType)) return "externref";
|
|
841
901
|
return wType;
|
|
842
902
|
}
|
|
843
|
-
function needsConv(wTypes) {
|
|
844
|
-
return wTypes.some(
|
|
903
|
+
function needsConv(wTypes, descs = []) {
|
|
904
|
+
return wTypes.some(
|
|
905
|
+
(w, i) => (w === "i64" && !descs[i]?.is64) || w === "v128" || isGcRef(w),
|
|
906
|
+
);
|
|
845
907
|
}
|
|
846
908
|
// Instructions converting the value on the stack from its boundary form.
|
|
847
|
-
function fromExt(wType) {
|
|
848
|
-
if (wType === "
|
|
909
|
+
function fromExt(wType, is64 = false) {
|
|
910
|
+
if (wType === "v128") return [{ op: "call", funcIndex: v128Conv.from }];
|
|
911
|
+
if (wType === "i64") return is64 ? [] : [{ op: "i64.trunc_sat_f64_s" }];
|
|
849
912
|
if (wType === "anyref") return [{ op: "any.convert_extern" }];
|
|
850
913
|
if (isGcRef(wType)) {
|
|
851
914
|
const out = [{ op: "any.convert_extern" }];
|
|
@@ -856,25 +919,31 @@ function fromExt(wType) {
|
|
|
856
919
|
return [];
|
|
857
920
|
}
|
|
858
921
|
// Instructions converting the value on the stack to its boundary form.
|
|
859
|
-
function toExt(wType) {
|
|
860
|
-
if (wType === "
|
|
922
|
+
function toExt(wType, is64 = false) {
|
|
923
|
+
if (wType === "v128") return [{ op: "call", funcIndex: v128Conv.to }];
|
|
924
|
+
if (wType === "i64") return is64 ? [] : [{ op: "f64.convert_i64_s" }];
|
|
861
925
|
if (isGcRef(wType)) return [{ op: "extern.convert_any" }];
|
|
862
926
|
return [];
|
|
863
927
|
}
|
|
864
928
|
// Rewrites the values on top of the stack (types `wTypes`) with `conv`,
|
|
865
929
|
// spilling through locals starting at `localBase` when more than one.
|
|
866
|
-
function convertResults(wTypes, conv, localBase) {
|
|
867
|
-
if (wTypes.length === 0 || !needsConv(wTypes)) return [];
|
|
868
|
-
if (wTypes.length === 1) return conv(wTypes[0]);
|
|
930
|
+
function convertResults(wTypes, conv, localBase, descs = []) {
|
|
931
|
+
if (wTypes.length === 0 || !needsConv(wTypes, descs)) return [];
|
|
932
|
+
if (wTypes.length === 1) return conv(wTypes[0], descs[0]?.is64);
|
|
869
933
|
const out = [];
|
|
870
934
|
for (let i = wTypes.length - 1; i >= 0; i--)
|
|
871
935
|
out.push({ op: "local.set", index: localBase + i });
|
|
872
936
|
for (let i = 0; i < wTypes.length; i++)
|
|
873
|
-
out.push(
|
|
937
|
+
out.push(
|
|
938
|
+
{ op: "local.get", index: localBase + i },
|
|
939
|
+
...conv(wTypes[i], descs[i]?.is64),
|
|
940
|
+
);
|
|
874
941
|
return out;
|
|
875
942
|
}
|
|
876
943
|
|
|
877
944
|
function zeroInstr(wType) {
|
|
945
|
+
if (wType === "v128")
|
|
946
|
+
return { op: "v128.const", bytes: new Array(16).fill(0) };
|
|
878
947
|
if (wType === "i64") return { op: "i64.const", value: 0n };
|
|
879
948
|
if (wType === "f32" || wType === "f64")
|
|
880
949
|
return { op: `${wType}.const`, value: 0 };
|
|
@@ -926,20 +995,61 @@ export function addBoundaryHelpers(mod, meta) {
|
|
|
926
995
|
return idx;
|
|
927
996
|
};
|
|
928
997
|
const get = (i) => ({ op: "local.get", index: i });
|
|
998
|
+
if (needs.simd) {
|
|
999
|
+
const { arrInfo } = needs.simd;
|
|
1000
|
+
const arrRef = needs.simd.wType;
|
|
1001
|
+
v128Conv.to = addFunc(
|
|
1002
|
+
"__v128_to",
|
|
1003
|
+
["v128"],
|
|
1004
|
+
["externref"],
|
|
1005
|
+
[
|
|
1006
|
+
...[0, 1, 2, 3].flatMap((lane) => [
|
|
1007
|
+
get(0),
|
|
1008
|
+
{ op: "i32x4.extract_lane", lane },
|
|
1009
|
+
]),
|
|
1010
|
+
{ op: "array.new_fixed", typeIndex: arrInfo.typeIndex, size: 4 },
|
|
1011
|
+
{ op: "extern.convert_any" },
|
|
1012
|
+
],
|
|
1013
|
+
false,
|
|
1014
|
+
);
|
|
1015
|
+
v128Conv.from = addFunc(
|
|
1016
|
+
"__v128_from",
|
|
1017
|
+
["externref"],
|
|
1018
|
+
["v128"],
|
|
1019
|
+
[
|
|
1020
|
+
get(0),
|
|
1021
|
+
{ op: "any.convert_extern" },
|
|
1022
|
+
{ op: "ref.cast_null", typeIndex: arrInfo.typeIndex },
|
|
1023
|
+
{ op: "local.set", index: 1 },
|
|
1024
|
+
get(1),
|
|
1025
|
+
{ op: "i32.const", value: 0 },
|
|
1026
|
+
{ op: "array.get", typeIndex: arrInfo.typeIndex },
|
|
1027
|
+
{ op: "i32x4.splat" },
|
|
1028
|
+
...[1, 2, 3].flatMap((lane) => [
|
|
1029
|
+
get(1),
|
|
1030
|
+
{ op: "i32.const", value: lane },
|
|
1031
|
+
{ op: "array.get", typeIndex: arrInfo.typeIndex },
|
|
1032
|
+
{ op: "i32x4.replace_lane", lane },
|
|
1033
|
+
]),
|
|
1034
|
+
],
|
|
1035
|
+
false,
|
|
1036
|
+
[arrRef],
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
929
1039
|
// Parameter `i` converted from its boundary form.
|
|
930
|
-
const getIn = (i, wType) => [get(i), ...fromExt(wType)];
|
|
1040
|
+
const getIn = (i, wType, is64 = false) => [get(i), ...fromExt(wType, is64)];
|
|
931
1041
|
const EXT = "externref";
|
|
932
1042
|
|
|
933
|
-
for (const [name, { info }] of needs.structs) {
|
|
1043
|
+
for (const [name, { info, fields }] of needs.structs) {
|
|
934
1044
|
const ref = toRef(info);
|
|
935
1045
|
const ti = info.typeIndex;
|
|
936
1046
|
const fieldWTypes = info.fields.map((f) => f.wType);
|
|
937
1047
|
addFunc(
|
|
938
1048
|
`__new_${name}`,
|
|
939
|
-
fieldWTypes.map(extType),
|
|
1049
|
+
fieldWTypes.map((w, i) => extType(w, fields[i]?.desc?.is64)),
|
|
940
1050
|
[EXT],
|
|
941
1051
|
[
|
|
942
|
-
...fieldWTypes.flatMap((w, i) => getIn(i, w)),
|
|
1052
|
+
...fieldWTypes.flatMap((w, i) => getIn(i, w, fields[i]?.desc?.is64)),
|
|
943
1053
|
{ op: "struct.new", typeIndex: ti },
|
|
944
1054
|
...toExt(ref),
|
|
945
1055
|
],
|
|
@@ -969,23 +1079,24 @@ export function addBoundaryHelpers(mod, meta) {
|
|
|
969
1079
|
);
|
|
970
1080
|
for (let i = 0; i < info.fields.length; i++) {
|
|
971
1081
|
const f = info.fields[i];
|
|
1082
|
+
const is64 = Boolean(fields[i]?.desc?.is64);
|
|
972
1083
|
addFunc(
|
|
973
1084
|
`__get_${name}_${f.name}`,
|
|
974
1085
|
[EXT],
|
|
975
|
-
[extType(f.wType)],
|
|
1086
|
+
[extType(f.wType, is64)],
|
|
976
1087
|
[
|
|
977
1088
|
...getIn(0, ref),
|
|
978
1089
|
{ op: "struct.get", typeIndex: ti, fieldIndex: i },
|
|
979
|
-
...toExt(f.wType),
|
|
1090
|
+
...toExt(f.wType, is64),
|
|
980
1091
|
],
|
|
981
1092
|
);
|
|
982
1093
|
addFunc(
|
|
983
1094
|
`__set_${name}_${f.name}`,
|
|
984
|
-
[EXT, extType(f.wType)],
|
|
1095
|
+
[EXT, extType(f.wType, is64)],
|
|
985
1096
|
[],
|
|
986
1097
|
[
|
|
987
1098
|
...getIn(0, ref),
|
|
988
|
-
...getIn(1, f.wType),
|
|
1099
|
+
...getIn(1, f.wType, is64),
|
|
989
1100
|
{ op: "struct.set", typeIndex: ti, fieldIndex: i },
|
|
990
1101
|
],
|
|
991
1102
|
);
|
|
@@ -1011,25 +1122,26 @@ export function addBoundaryHelpers(mod, meta) {
|
|
|
1011
1122
|
["i32"],
|
|
1012
1123
|
[...getIn(0, ref), { op: "array.len" }],
|
|
1013
1124
|
);
|
|
1125
|
+
const is64 = Boolean(desc.elem?.is64);
|
|
1014
1126
|
addFunc(
|
|
1015
1127
|
`__array_get_${key}`,
|
|
1016
1128
|
[EXT, "i32"],
|
|
1017
|
-
[extType(arrInfo.elemWType)],
|
|
1129
|
+
[extType(arrInfo.elemWType, is64)],
|
|
1018
1130
|
[
|
|
1019
1131
|
...getIn(0, ref),
|
|
1020
1132
|
get(1),
|
|
1021
1133
|
{ op: "array.get", typeIndex: arrInfo.typeIndex },
|
|
1022
|
-
...toExt(arrInfo.elemWType),
|
|
1134
|
+
...toExt(arrInfo.elemWType, is64),
|
|
1023
1135
|
],
|
|
1024
1136
|
);
|
|
1025
1137
|
addFunc(
|
|
1026
1138
|
`__array_set_${key}`,
|
|
1027
|
-
[EXT, "i32", extType(arrInfo.elemWType)],
|
|
1139
|
+
[EXT, "i32", extType(arrInfo.elemWType, is64)],
|
|
1028
1140
|
[],
|
|
1029
1141
|
[
|
|
1030
1142
|
...getIn(0, ref),
|
|
1031
1143
|
get(1),
|
|
1032
|
-
...getIn(2, arrInfo.elemWType),
|
|
1144
|
+
...getIn(2, arrInfo.elemWType, is64),
|
|
1033
1145
|
{ op: "array.set", typeIndex: arrInfo.typeIndex },
|
|
1034
1146
|
],
|
|
1035
1147
|
);
|
|
@@ -1040,6 +1152,7 @@ export function addBoundaryHelpers(mod, meta) {
|
|
|
1040
1152
|
const ref = desc.wType;
|
|
1041
1153
|
const arrTI = sliceInfo.arrInfo.typeIndex;
|
|
1042
1154
|
const sTI = sliceInfo.typeIndex;
|
|
1155
|
+
const is64 = Boolean(desc.elem?.is64);
|
|
1043
1156
|
const elemAddr = [
|
|
1044
1157
|
...getIn(0, ref),
|
|
1045
1158
|
{ op: "struct.get", typeIndex: sTI, fieldIndex: 0 },
|
|
@@ -1071,11 +1184,11 @@ export function addBoundaryHelpers(mod, meta) {
|
|
|
1071
1184
|
addFunc(
|
|
1072
1185
|
`__slice_get_${key}`,
|
|
1073
1186
|
[EXT, "i32"],
|
|
1074
|
-
[extType(sliceInfo.elemWType)],
|
|
1187
|
+
[extType(sliceInfo.elemWType, is64)],
|
|
1075
1188
|
[
|
|
1076
1189
|
...elemAddr,
|
|
1077
1190
|
{ op: "array.get", typeIndex: arrTI },
|
|
1078
|
-
...toExt(sliceInfo.elemWType),
|
|
1191
|
+
...toExt(sliceInfo.elemWType, is64),
|
|
1079
1192
|
],
|
|
1080
1193
|
);
|
|
1081
1194
|
addFunc(
|
|
@@ -1096,11 +1209,11 @@ export function addBoundaryHelpers(mod, meta) {
|
|
|
1096
1209
|
);
|
|
1097
1210
|
addFunc(
|
|
1098
1211
|
`__slice_set_${key}`,
|
|
1099
|
-
[EXT, "i32", extType(sliceInfo.elemWType)],
|
|
1212
|
+
[EXT, "i32", extType(sliceInfo.elemWType, is64)],
|
|
1100
1213
|
[],
|
|
1101
1214
|
[
|
|
1102
1215
|
...elemAddr,
|
|
1103
|
-
...getIn(2, sliceInfo.elemWType),
|
|
1216
|
+
...getIn(2, sliceInfo.elemWType, is64),
|
|
1104
1217
|
{ op: "array.set", typeIndex: arrTI },
|
|
1105
1218
|
],
|
|
1106
1219
|
);
|
|
@@ -1110,6 +1223,8 @@ export function addBoundaryHelpers(mod, meta) {
|
|
|
1110
1223
|
const ci = desc.closureInfo;
|
|
1111
1224
|
const invokeIdx = mod.importCache.get(`env.__invoke$${key}`);
|
|
1112
1225
|
const nParams = ci.paramWTypes.length;
|
|
1226
|
+
const paramDescs = desc.params ?? [];
|
|
1227
|
+
const returnDescs = desc.returns ?? [];
|
|
1113
1228
|
// Thunk: wasm closure body calling the JS callback (boundary forms out, back in).
|
|
1114
1229
|
const thunkIdx = addFunc(
|
|
1115
1230
|
`__jsthunk$${key}`,
|
|
@@ -1118,12 +1233,17 @@ export function addBoundaryHelpers(mod, meta) {
|
|
|
1118
1233
|
[
|
|
1119
1234
|
get(0),
|
|
1120
1235
|
{ op: "extern.convert_any" },
|
|
1121
|
-
...ci.paramWTypes.flatMap((w, i) => [
|
|
1236
|
+
...ci.paramWTypes.flatMap((w, i) => [
|
|
1237
|
+
get(i + 1),
|
|
1238
|
+
...toExt(w, paramDescs[i]?.is64),
|
|
1239
|
+
]),
|
|
1122
1240
|
{ op: "call", funcIndex: invokeIdx },
|
|
1123
|
-
...convertResults(ci.returnWTypes, fromExt, nParams + 1),
|
|
1241
|
+
...convertResults(ci.returnWTypes, fromExt, nParams + 1, returnDescs),
|
|
1124
1242
|
],
|
|
1125
1243
|
false,
|
|
1126
|
-
ci.returnWTypes.length > 1
|
|
1244
|
+
ci.returnWTypes.length > 1
|
|
1245
|
+
? ci.returnWTypes.map((w, i) => extType(w, returnDescs[i]?.is64))
|
|
1246
|
+
: [],
|
|
1127
1247
|
);
|
|
1128
1248
|
mod.addRefFuncElement(thunkIdx);
|
|
1129
1249
|
addFunc(
|
|
@@ -1141,16 +1261,18 @@ export function addBoundaryHelpers(mod, meta) {
|
|
|
1141
1261
|
// JS calling a wasm closure (boundary forms in, results out).
|
|
1142
1262
|
addFunc(
|
|
1143
1263
|
`__call_fn$${key}`,
|
|
1144
|
-
[EXT, ...ci.paramWTypes.map(extType)],
|
|
1145
|
-
ci.returnWTypes.map(extType),
|
|
1264
|
+
[EXT, ...ci.paramWTypes.map((w, i) => extType(w, paramDescs[i]?.is64))],
|
|
1265
|
+
ci.returnWTypes.map((w, i) => extType(w, returnDescs[i]?.is64)),
|
|
1146
1266
|
[
|
|
1147
1267
|
...getIn(0, desc.wType),
|
|
1148
1268
|
{ op: "struct.get", typeIndex: ci.typeIndex, fieldIndex: 1 },
|
|
1149
|
-
...ci.paramWTypes.flatMap((w, i) =>
|
|
1269
|
+
...ci.paramWTypes.flatMap((w, i) =>
|
|
1270
|
+
getIn(i + 1, w, paramDescs[i]?.is64),
|
|
1271
|
+
),
|
|
1150
1272
|
...getIn(0, desc.wType),
|
|
1151
1273
|
{ op: "struct.get", typeIndex: ci.typeIndex, fieldIndex: 0 },
|
|
1152
1274
|
{ op: "call_ref", typeIndex: ci.funcTypeIndex },
|
|
1153
|
-
...convertResults(ci.returnWTypes, toExt, nParams + 1),
|
|
1275
|
+
...convertResults(ci.returnWTypes, toExt, nParams + 1, returnDescs),
|
|
1154
1276
|
],
|
|
1155
1277
|
true,
|
|
1156
1278
|
ci.returnWTypes.length > 1 ? ci.returnWTypes : [],
|
|
@@ -1168,16 +1290,20 @@ export function addBoundaryHelpers(mod, meta) {
|
|
|
1168
1290
|
if (sig?.form !== "func") return;
|
|
1169
1291
|
const paramW = sig.params;
|
|
1170
1292
|
const resultW = sig.results;
|
|
1171
|
-
|
|
1172
|
-
|
|
1293
|
+
const paramDescs = entry.params ?? [];
|
|
1294
|
+
const resultDescs = entry.returns ?? [];
|
|
1295
|
+
if (!needsConv(paramW, paramDescs) && !needsConv(resultW, resultDescs))
|
|
1296
|
+
return;
|
|
1297
|
+
// Exported names become JS identifiers: no dot of a qualified type.
|
|
1298
|
+
entry.callName = `__x_${entry.exportName}`.replaceAll(".", "_");
|
|
1173
1299
|
addFunc(
|
|
1174
1300
|
entry.callName,
|
|
1175
|
-
paramW.map(extType),
|
|
1176
|
-
resultW.map(extType),
|
|
1301
|
+
paramW.map((w, i) => extType(w, paramDescs[i]?.is64)),
|
|
1302
|
+
resultW.map((w, i) => extType(w, resultDescs[i]?.is64)),
|
|
1177
1303
|
[
|
|
1178
|
-
...paramW.flatMap((w, i) => getIn(i, w)),
|
|
1304
|
+
...paramW.flatMap((w, i) => getIn(i, w, paramDescs[i]?.is64)),
|
|
1179
1305
|
{ op: "call", funcIndex: fnIdx },
|
|
1180
|
-
...convertResults(resultW, toExt, paramW.length),
|
|
1306
|
+
...convertResults(resultW, toExt, paramW.length, resultDescs),
|
|
1181
1307
|
],
|
|
1182
1308
|
true,
|
|
1183
1309
|
resultW.length > 1 ? resultW : [],
|
|
@@ -1210,7 +1336,7 @@ export function addBoundaryHelpers(mod, meta) {
|
|
|
1210
1336
|
[get(0), get(1), { op: "struct.new", typeIndex }, ...toExt(wType)],
|
|
1211
1337
|
);
|
|
1212
1338
|
for (const v of meta.vars ?? []) {
|
|
1213
|
-
const g = mod.resolveGlobal(v.name);
|
|
1339
|
+
const g = v.shared ? mod.resolveGlobal(v.name) : null;
|
|
1214
1340
|
if (!g) continue;
|
|
1215
1341
|
addFunc(
|
|
1216
1342
|
`__var_${v.name}`,
|
|
@@ -1222,16 +1348,16 @@ export function addBoundaryHelpers(mod, meta) {
|
|
|
1222
1348
|
}
|
|
1223
1349
|
|
|
1224
1350
|
for (const [name, desc] of needs.anonStructs) {
|
|
1225
|
-
const { info } = desc;
|
|
1351
|
+
const { info, fields } = desc;
|
|
1226
1352
|
const ref = desc.wType;
|
|
1227
1353
|
const ti = info.typeIndex;
|
|
1228
1354
|
const fieldWTypes = info.fields.map((f) => f.wType);
|
|
1229
1355
|
addFunc(
|
|
1230
1356
|
`__new_${name}`,
|
|
1231
|
-
fieldWTypes.map(extType),
|
|
1357
|
+
fieldWTypes.map((w, i) => extType(w, fields[i]?.desc?.is64)),
|
|
1232
1358
|
[EXT],
|
|
1233
1359
|
[
|
|
1234
|
-
...fieldWTypes.flatMap((w, i) => getIn(i, w)),
|
|
1360
|
+
...fieldWTypes.flatMap((w, i) => getIn(i, w, fields[i]?.desc?.is64)),
|
|
1235
1361
|
{ op: "struct.new", typeIndex: ti },
|
|
1236
1362
|
...toExt(ref),
|
|
1237
1363
|
],
|
|
@@ -1248,23 +1374,24 @@ export function addBoundaryHelpers(mod, meta) {
|
|
|
1248
1374
|
);
|
|
1249
1375
|
for (let i = 0; i < info.fields.length; i++) {
|
|
1250
1376
|
const f = info.fields[i];
|
|
1377
|
+
const is64 = Boolean(fields[i]?.desc?.is64);
|
|
1251
1378
|
addFunc(
|
|
1252
1379
|
`__get_${name}_${f.name}`,
|
|
1253
1380
|
[EXT],
|
|
1254
|
-
[extType(f.wType)],
|
|
1381
|
+
[extType(f.wType, is64)],
|
|
1255
1382
|
[
|
|
1256
1383
|
...getIn(0, ref),
|
|
1257
1384
|
{ op: "struct.get", typeIndex: ti, fieldIndex: i },
|
|
1258
|
-
...toExt(f.wType),
|
|
1385
|
+
...toExt(f.wType, is64),
|
|
1259
1386
|
],
|
|
1260
1387
|
);
|
|
1261
1388
|
addFunc(
|
|
1262
1389
|
`__set_${name}_${f.name}`,
|
|
1263
|
-
[EXT, extType(f.wType)],
|
|
1390
|
+
[EXT, extType(f.wType, is64)],
|
|
1264
1391
|
[],
|
|
1265
1392
|
[
|
|
1266
1393
|
...getIn(0, ref),
|
|
1267
|
-
...getIn(1, f.wType),
|
|
1394
|
+
...getIn(1, f.wType, is64),
|
|
1268
1395
|
{ op: "struct.set", typeIndex: ti, fieldIndex: i },
|
|
1269
1396
|
],
|
|
1270
1397
|
);
|
|
@@ -1272,40 +1399,52 @@ export function addBoundaryHelpers(mod, meta) {
|
|
|
1272
1399
|
}
|
|
1273
1400
|
|
|
1274
1401
|
for (const [key, desc] of needs.boxes) {
|
|
1275
|
-
const { boxInfo } = desc;
|
|
1402
|
+
const { boxInfo, base } = desc;
|
|
1276
1403
|
const ref = desc.wType;
|
|
1277
1404
|
const ti = boxInfo.typeIndex;
|
|
1278
1405
|
const w = boxInfo.wType;
|
|
1406
|
+
const is64 = Boolean(base?.is64);
|
|
1279
1407
|
addFunc(
|
|
1280
1408
|
`__box_new_${key}`,
|
|
1281
|
-
[extType(w)],
|
|
1409
|
+
[extType(w, is64)],
|
|
1282
1410
|
[EXT],
|
|
1283
|
-
[
|
|
1411
|
+
[
|
|
1412
|
+
...getIn(0, w, is64),
|
|
1413
|
+
{ op: "struct.new", typeIndex: ti },
|
|
1414
|
+
...toExt(ref),
|
|
1415
|
+
],
|
|
1284
1416
|
);
|
|
1417
|
+
const cast = {
|
|
1418
|
+
op: "ref.cast",
|
|
1419
|
+
typeIndex: boxInfo.typeIndex,
|
|
1420
|
+
typeName: boxInfo.name,
|
|
1421
|
+
};
|
|
1285
1422
|
addFunc(
|
|
1286
1423
|
`__box_get_${key}`,
|
|
1287
1424
|
[EXT],
|
|
1288
|
-
[extType(w)],
|
|
1425
|
+
[extType(w, is64)],
|
|
1289
1426
|
[
|
|
1290
1427
|
...getIn(0, ref),
|
|
1428
|
+
cast,
|
|
1291
1429
|
{ op: "struct.get", typeIndex: ti, fieldIndex: 0 },
|
|
1292
|
-
...toExt(w),
|
|
1430
|
+
...toExt(w, is64),
|
|
1293
1431
|
],
|
|
1294
1432
|
);
|
|
1295
1433
|
addFunc(
|
|
1296
1434
|
`__box_set_${key}`,
|
|
1297
|
-
[EXT, extType(w)],
|
|
1435
|
+
[EXT, extType(w, is64)],
|
|
1298
1436
|
[],
|
|
1299
1437
|
[
|
|
1300
1438
|
...getIn(0, ref),
|
|
1301
|
-
|
|
1439
|
+
cast,
|
|
1440
|
+
...getIn(1, w, is64),
|
|
1302
1441
|
{ op: "struct.set", typeIndex: ti, fieldIndex: 0 },
|
|
1303
1442
|
],
|
|
1304
1443
|
);
|
|
1305
1444
|
}
|
|
1306
1445
|
|
|
1307
1446
|
for (const [key, desc] of needs.maps) {
|
|
1308
|
-
const { mapInfo, keySliceDesc, valSliceDesc } = desc;
|
|
1447
|
+
const { mapInfo, keySliceDesc, valSliceDesc, keyDesc, valDesc } = desc;
|
|
1309
1448
|
const makeIdx = mod.resolveFuncIndex(mapInfo.makeFuncName);
|
|
1310
1449
|
addFunc(
|
|
1311
1450
|
`__map_new_${key}`,
|
|
@@ -1316,12 +1455,16 @@ export function addBoundaryHelpers(mod, meta) {
|
|
|
1316
1455
|
const setIdx = mod.resolveFuncIndex(mapInfo.setFuncName);
|
|
1317
1456
|
addFunc(
|
|
1318
1457
|
`__map_set_${key}`,
|
|
1319
|
-
[
|
|
1458
|
+
[
|
|
1459
|
+
EXT,
|
|
1460
|
+
extType(mapInfo.keyWType, keyDesc?.is64),
|
|
1461
|
+
extType(mapInfo.valWType, valDesc?.is64),
|
|
1462
|
+
],
|
|
1320
1463
|
[],
|
|
1321
1464
|
[
|
|
1322
1465
|
...getIn(0, desc.wType),
|
|
1323
|
-
...getIn(1, mapInfo.keyWType),
|
|
1324
|
-
...getIn(2, mapInfo.valWType),
|
|
1466
|
+
...getIn(1, mapInfo.keyWType, keyDesc?.is64),
|
|
1467
|
+
...getIn(2, mapInfo.valWType, valDesc?.is64),
|
|
1325
1468
|
{ op: "call", funcIndex: setIdx },
|
|
1326
1469
|
],
|
|
1327
1470
|
);
|
|
@@ -1380,69 +1523,27 @@ export function addBoundaryHelpers(mod, meta) {
|
|
|
1380
1523
|
);
|
|
1381
1524
|
}
|
|
1382
1525
|
|
|
1383
|
-
if (needs.time) {
|
|
1384
|
-
const timeInfo = mod.getStructType("time.Time");
|
|
1385
|
-
if (timeInfo) {
|
|
1386
|
-
const timeWType = toRef(timeInfo);
|
|
1387
|
-
addFunc(
|
|
1388
|
-
"__time_make",
|
|
1389
|
-
["f64", "i32"],
|
|
1390
|
-
[EXT],
|
|
1391
|
-
[
|
|
1392
|
-
get(0),
|
|
1393
|
-
{ op: "f64.const", value: 1000000 },
|
|
1394
|
-
{ op: "f64.mul" },
|
|
1395
|
-
{ op: "i64.trunc_sat_f64_s" },
|
|
1396
|
-
get(1),
|
|
1397
|
-
{ op: "struct.new", typeIndex: timeInfo.typeIndex },
|
|
1398
|
-
...toExt(timeWType),
|
|
1399
|
-
],
|
|
1400
|
-
);
|
|
1401
|
-
addFunc(
|
|
1402
|
-
"__time_unix_milli",
|
|
1403
|
-
[EXT],
|
|
1404
|
-
["f64"],
|
|
1405
|
-
[
|
|
1406
|
-
get(0),
|
|
1407
|
-
{ op: "ref.is_null" },
|
|
1408
|
-
{ op: "if", blockType: "f64" },
|
|
1409
|
-
{ op: "f64.const", value: 0 },
|
|
1410
|
-
{ op: "else" },
|
|
1411
|
-
...getIn(0, timeWType),
|
|
1412
|
-
{ op: "struct.get", typeIndex: timeInfo.typeIndex, fieldIndex: 0 },
|
|
1413
|
-
{ op: "i64.const", value: 1000000n },
|
|
1414
|
-
{ op: "i64.div_s" },
|
|
1415
|
-
{ op: "f64.convert_i64_s" },
|
|
1416
|
-
{ op: "end" },
|
|
1417
|
-
],
|
|
1418
|
-
);
|
|
1419
|
-
addFunc(
|
|
1420
|
-
"__time_offset",
|
|
1421
|
-
[EXT],
|
|
1422
|
-
["i32"],
|
|
1423
|
-
[
|
|
1424
|
-
get(0),
|
|
1425
|
-
{ op: "ref.is_null" },
|
|
1426
|
-
{ op: "if", blockType: "i32" },
|
|
1427
|
-
{ op: "i32.const", value: 0 },
|
|
1428
|
-
{ op: "else" },
|
|
1429
|
-
...getIn(0, timeWType),
|
|
1430
|
-
{ op: "struct.get", typeIndex: timeInfo.typeIndex, fieldIndex: 1 },
|
|
1431
|
-
{ op: "end" },
|
|
1432
|
-
],
|
|
1433
|
-
);
|
|
1434
|
-
}
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
1526
|
for (const c of meta.consts ?? []) {
|
|
1438
1527
|
if (!c.dynamic) continue;
|
|
1439
1528
|
const g = mod.resolveGlobal(c.name);
|
|
1440
1529
|
if (!g) continue;
|
|
1530
|
+
const is64 = Boolean(c.desc?.is64);
|
|
1441
1531
|
addFunc(
|
|
1442
1532
|
`__const_${c.name}`,
|
|
1443
1533
|
[],
|
|
1444
|
-
[extType(c.desc.wType)],
|
|
1445
|
-
[{ op: "global.get", index: g.index }, ...toExt(c.desc.wType)],
|
|
1534
|
+
[extType(c.desc.wType, is64)],
|
|
1535
|
+
[{ op: "global.get", index: g.index }, ...toExt(c.desc.wType, is64)],
|
|
1536
|
+
);
|
|
1537
|
+
}
|
|
1538
|
+
for (const v of meta.vars ?? []) {
|
|
1539
|
+
const g = v.shared ? null : mod.resolveGlobal(v.name);
|
|
1540
|
+
if (!g) continue;
|
|
1541
|
+
const is64 = Boolean(v.desc?.is64);
|
|
1542
|
+
addFunc(
|
|
1543
|
+
`__var_${v.name}`,
|
|
1544
|
+
[],
|
|
1545
|
+
[extType(v.desc.wType, is64)],
|
|
1546
|
+
[{ op: "global.get", index: g.index }, ...toExt(v.desc.wType, is64)],
|
|
1446
1547
|
);
|
|
1447
1548
|
}
|
|
1448
1549
|
}
|
|
@@ -1511,6 +1612,13 @@ function addIfaceHelpers(desc, mod, addFunc, addTrampoline) {
|
|
|
1511
1612
|
|
|
1512
1613
|
const P = "__gfw_"; // facade helper prefix
|
|
1513
1614
|
|
|
1615
|
+
// Pieces of the strings host imports (written for use inside the templates
|
|
1616
|
+
// below, where a backslash escape must be doubled).
|
|
1617
|
+
const GO_SPACE =
|
|
1618
|
+
"[ \\t\\n\\v\\f\\r\\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]";
|
|
1619
|
+
const BAD_RUNE = "r < 0 || r > 0x10FFFF || (r >= 0xD800 && r <= 0xDFFF)";
|
|
1620
|
+
const SURROGATE = "(v >= 0xD800 && v <= 0xDFFF)";
|
|
1621
|
+
|
|
1514
1622
|
// JS expression converting JS value `v` into the wasm representation.
|
|
1515
1623
|
function inExpr(desc, v) {
|
|
1516
1624
|
switch (desc.k) {
|
|
@@ -1519,7 +1627,7 @@ function inExpr(desc, v) {
|
|
|
1519
1627
|
case "i32":
|
|
1520
1628
|
return `(${v} | 0)`;
|
|
1521
1629
|
case "i64":
|
|
1522
|
-
return `${P}i64in(${v})`;
|
|
1630
|
+
return desc.is64 ? `BigInt(${v})` : `${P}i64in(${v})`;
|
|
1523
1631
|
case "f32":
|
|
1524
1632
|
case "f64":
|
|
1525
1633
|
return `(+${v})`;
|
|
@@ -1529,8 +1637,10 @@ function inExpr(desc, v) {
|
|
|
1529
1637
|
return `(${v} === undefined ? null : ${v})`;
|
|
1530
1638
|
case "error":
|
|
1531
1639
|
return `${P}errin(${v})`;
|
|
1532
|
-
case "
|
|
1533
|
-
return
|
|
1640
|
+
case "f32x4":
|
|
1641
|
+
return `${P}f32x4in(${v})`;
|
|
1642
|
+
case "mask4":
|
|
1643
|
+
return `${P}mask4in(${v})`;
|
|
1534
1644
|
case "map":
|
|
1535
1645
|
return `${P}mapin_${desc.key}(${v})`;
|
|
1536
1646
|
case "ptr_box":
|
|
@@ -1568,13 +1678,19 @@ function outExpr(desc, v) {
|
|
|
1568
1678
|
case "i32":
|
|
1569
1679
|
return desc.unsigned ? `(${v} >>> 0)` : v;
|
|
1570
1680
|
case "i64":
|
|
1681
|
+
if (desc.is64)
|
|
1682
|
+
return desc.unsigned
|
|
1683
|
+
? `BigInt.asUintN(64, ${v})`
|
|
1684
|
+
: `BigInt.asIntN(64, ${v})`;
|
|
1571
1685
|
return desc.unsigned ? `${P}u64out(${v})` : `${P}i64out(${v})`;
|
|
1572
1686
|
case "string":
|
|
1573
1687
|
return `(${v} ?? "")`;
|
|
1574
1688
|
case "error":
|
|
1575
1689
|
return `${P}errout(${v})`;
|
|
1576
|
-
case "
|
|
1577
|
-
return
|
|
1690
|
+
case "f32x4":
|
|
1691
|
+
return `${P}f32x4out(${v})`;
|
|
1692
|
+
case "mask4":
|
|
1693
|
+
return `${P}mask4out(${v})`;
|
|
1578
1694
|
case "map":
|
|
1579
1695
|
return `${P}mapout_${desc.key}(${v})`;
|
|
1580
1696
|
case "ptr_box":
|
|
@@ -1607,14 +1723,18 @@ function zeroExpr(desc) {
|
|
|
1607
1723
|
case "bool":
|
|
1608
1724
|
return "false";
|
|
1609
1725
|
case "i32":
|
|
1610
|
-
case "i64":
|
|
1611
1726
|
case "f32":
|
|
1612
1727
|
case "f64":
|
|
1613
1728
|
return "0";
|
|
1729
|
+
case "i64":
|
|
1730
|
+
return desc.is64 ? "0n" : "0";
|
|
1614
1731
|
case "string":
|
|
1615
1732
|
return '""';
|
|
1733
|
+
case "f32x4":
|
|
1734
|
+
return "new Float32Array(4)";
|
|
1735
|
+
case "mask4":
|
|
1736
|
+
return "[false, false, false, false]";
|
|
1616
1737
|
case "error":
|
|
1617
|
-
case "time":
|
|
1618
1738
|
case "map":
|
|
1619
1739
|
case "ptr_box":
|
|
1620
1740
|
return "null";
|
|
@@ -1729,28 +1849,26 @@ function indent(text, prefix) {
|
|
|
1729
1849
|
// survive the built-in mangler: no shorthand destructuring, import keys
|
|
1730
1850
|
// quoted, helpers prefixed with `__`.
|
|
1731
1851
|
export const WASM_IMPORTS_JS = `const ${P}MAX = 9007199254740991n;
|
|
1732
|
-
function ${P}imports(stringTable, extraEnv, tag, write) {
|
|
1852
|
+
function ${P}imports(stringTable, extraEnv, tag, write, toStderr) {
|
|
1853
|
+
// Each instance numbers the references fmt_ptr_id is asked about from 1
|
|
1854
|
+
// (the table the JS body of that intrinsic uses).
|
|
1855
|
+
globalThis.__fmtptr = { m: new WeakMap(), n: 1 };
|
|
1733
1856
|
let lineBuf = "";
|
|
1734
|
-
let targs = [];
|
|
1735
|
-
let fmtStructMap = new WeakMap();
|
|
1736
1857
|
const late = { exports: null };
|
|
1737
|
-
|
|
1858
|
+
// Go separates operands with a space unconditionally, so track whether the
|
|
1859
|
+
// current line already holds an operand (an empty string leaves lineBuf empty).
|
|
1860
|
+
let started = false;
|
|
1861
|
+
const flush = () => { write(lineBuf); lineBuf = ""; started = false; };
|
|
1738
1862
|
const print = (v) => {
|
|
1739
|
-
const s = String(v);
|
|
1740
|
-
if (
|
|
1741
|
-
|
|
1742
|
-
}
|
|
1863
|
+
const s = Object.is(v, -0) ? "-0" : String(v);
|
|
1864
|
+
if (started) lineBuf += " ";
|
|
1865
|
+
started = true;
|
|
1743
1866
|
lineBuf += s;
|
|
1744
1867
|
};
|
|
1745
1868
|
const println = (v) => {
|
|
1746
|
-
|
|
1747
|
-
if (lineBuf.length > 0 && !lineBuf.endsWith(" ") && !s.startsWith(" ")) {
|
|
1748
|
-
lineBuf += " ";
|
|
1749
|
-
}
|
|
1750
|
-
lineBuf += s;
|
|
1869
|
+
print(v);
|
|
1751
1870
|
flush();
|
|
1752
1871
|
};
|
|
1753
|
-
const targ = (v) => { targs.push(v); };
|
|
1754
1872
|
let utf8Encoded = null;
|
|
1755
1873
|
let convBytes = null, convPos = 0;
|
|
1756
1874
|
const env = {
|
|
@@ -1762,7 +1880,9 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
|
|
|
1762
1880
|
} catch {
|
|
1763
1881
|
str = "[panic object]";
|
|
1764
1882
|
}
|
|
1765
|
-
|
|
1883
|
+
const err = new Error(str);
|
|
1884
|
+
err.__gfp = true;
|
|
1885
|
+
throw err;
|
|
1766
1886
|
},
|
|
1767
1887
|
"str": (i) => stringTable[i] ?? "",
|
|
1768
1888
|
"str_len": (s) => (s ? s.length : 0),
|
|
@@ -1775,7 +1895,7 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
|
|
|
1775
1895
|
"str_ge": (a, b) => ((a ?? "") >= (b ?? "") ? 1 : 0),
|
|
1776
1896
|
"str_get": (s, i) => (s ? s.charCodeAt(i) : 0),
|
|
1777
1897
|
"str_slice": (s, a, b) => (s ? s.slice(a, b) : ""),
|
|
1778
|
-
"str_from_code_point": (c) => String.fromCodePoint(c),
|
|
1898
|
+
"str_from_code_point": (c) => String.fromCodePoint(c >= 0 && c <= 0x10ffff && !(c >= 0xd800 && c <= 0xdfff) ? c : 0xfffd),
|
|
1779
1899
|
"str_code_point_at": (s, i) => (s ? s.codePointAt(i) : 0),
|
|
1780
1900
|
"str_hash": (s) => {
|
|
1781
1901
|
if (!s) return 0;
|
|
@@ -1785,28 +1905,109 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
|
|
|
1785
1905
|
}
|
|
1786
1906
|
return h;
|
|
1787
1907
|
},
|
|
1788
|
-
"str_to_upper": (s) => (s
|
|
1789
|
-
"str_to_lower": (s) => (s
|
|
1790
|
-
"str_trim_space": (s) =>
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
"
|
|
1799
|
-
"
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1908
|
+
"str_to_upper": (s) => ${P}mapCase(s ?? "", "toUpperCase"),
|
|
1909
|
+
"str_to_lower": (s) => ${P}mapCase(s ?? "", "toLowerCase"),
|
|
1910
|
+
"str_trim_space": (s) => {
|
|
1911
|
+
s = s ?? "";
|
|
1912
|
+
const sp = /${GO_SPACE}/;
|
|
1913
|
+
let i = 0, j = s.length;
|
|
1914
|
+
while (i < j && sp.test(s[i])) i++;
|
|
1915
|
+
while (j > i && sp.test(s[j - 1])) j--;
|
|
1916
|
+
return s.slice(i, j);
|
|
1917
|
+
},
|
|
1918
|
+
"str_contains": (s, sub) => ((s ?? "").includes(sub ?? "") ? 1 : 0),
|
|
1919
|
+
"str_has_prefix": (s, pre) => ((s ?? "").startsWith(pre ?? "") ? 1 : 0),
|
|
1920
|
+
"str_has_suffix": (s, suf) => ((s ?? "").endsWith(suf ?? "") ? 1 : 0),
|
|
1921
|
+
"os_write": (fd, s) => {
|
|
1922
|
+
if (fd === 2 && toStderr && typeof process !== "undefined" && process.stderr?.write) { process.stderr.write(s ?? ""); return; }
|
|
1923
|
+
lineBuf += s ?? "";
|
|
1924
|
+
const nl = lineBuf.lastIndexOf("\\n");
|
|
1925
|
+
if (nl >= 0) { write(lineBuf.slice(0, nl)); lineBuf = lineBuf.slice(nl + 1); }
|
|
1926
|
+
},
|
|
1927
|
+
"time_now_ms": () => (typeof globalThis.__GOFRONT_CLOCK === "function" ? globalThis.__GOFRONT_CLOCK() : Date.now()),
|
|
1928
|
+
"time_timezone_offset": (ms) => (-new Date(ms).getTimezoneOffset() * 60) | 0,
|
|
1929
|
+
"os_exit": (code) => {
|
|
1930
|
+
if (lineBuf !== "") { write(lineBuf); lineBuf = ""; }
|
|
1931
|
+
if (typeof process !== "undefined" && process.exit) process.exit(code);
|
|
1932
|
+
throw new Error("os.Exit(" + code + ")");
|
|
1933
|
+
},
|
|
1934
|
+
"os_getenv": (k) => (typeof process !== "undefined" && process.env ? process.env : (globalThis.__os_env ??= {}))[k ?? ""] ?? "",
|
|
1935
|
+
"os_setenv": (k, v) => { (typeof process !== "undefined" && process.env ? process.env : (globalThis.__os_env ??= {}))[k ?? ""] = v ?? ""; },
|
|
1936
|
+
"unicode_is": (r, k) => { ${unicodeIsJs("r", "k")} },
|
|
1937
|
+
"unicode_to_upper": (r) => { ${unicodeToUpperJs("r")} },
|
|
1938
|
+
"unicode_to_lower": (r) => { ${unicodeToLowerJs("r")} },
|
|
1939
|
+
"testing_nanotime": () => performance.now() * 1e6,
|
|
1940
|
+
"testing_match": (p, s) => new RegExp(p ?? "").test(s ?? "") ? 1 : 0,
|
|
1941
|
+
"fmt_ptr_id": (o) => {
|
|
1942
|
+
if (o === null || o === undefined || (typeof o !== "object" && typeof o !== "function")) return 0;
|
|
1943
|
+
const t = globalThis.__fmtptr;
|
|
1944
|
+
let id = t.m.get(o);
|
|
1945
|
+
if (!id) { id = t.n++; t.m.set(o, id); }
|
|
1946
|
+
return id;
|
|
1947
|
+
},
|
|
1948
|
+
"crypto_rand_u32": () => {
|
|
1949
|
+
const c = typeof crypto !== "undefined" ? crypto : globalThis.crypto;
|
|
1950
|
+
return c?.getRandomValues ? c.getRandomValues(new Uint32Array(1))[0] | 0 : (Math.random() * 4294967296) | 0;
|
|
1951
|
+
},
|
|
1952
|
+
"strconv_shortest": (f) => f.toExponential(),
|
|
1953
|
+
"strconv_fixed": ${FIXED_FLOAT_JS},
|
|
1954
|
+
"strconv_parse_decimal": (s) => Number((s ?? "").replaceAll("_", "")),
|
|
1955
|
+
"str_index": (s, sub) => (s ?? "").indexOf(sub ?? ""),
|
|
1956
|
+
"str_last_index": (s, sub) => (s ?? "").lastIndexOf(sub ?? ""),
|
|
1957
|
+
"str_index_byte": (s, b) => (s ?? "").indexOf(String.fromCharCode(b)),
|
|
1958
|
+
"str_last_index_byte": (s, b) => (s ?? "").lastIndexOf(String.fromCharCode(b)),
|
|
1959
|
+
"str_index_rune": (s, r) => (${BAD_RUNE} ? -1 : (s ?? "").indexOf(String.fromCodePoint(r))),
|
|
1960
|
+
"str_contains_rune": (s, r) => (!(${BAD_RUNE}) && (s ?? "").includes(String.fromCodePoint(r)) ? 1 : 0),
|
|
1961
|
+
"str_index_any": (s, cs) => {
|
|
1962
|
+
s = s ?? "";
|
|
1963
|
+
let m = -1;
|
|
1964
|
+
for (const c of (cs ?? "")) {
|
|
1965
|
+
const v = c.codePointAt(0);
|
|
1966
|
+
if (${SURROGATE}) continue;
|
|
1967
|
+
const i = s.indexOf(c);
|
|
1968
|
+
if (i >= 0 && (m < 0 || i < m)) m = i;
|
|
1969
|
+
}
|
|
1970
|
+
return m;
|
|
1971
|
+
},
|
|
1972
|
+
"str_last_index_any": (s, cs) => {
|
|
1973
|
+
s = s ?? "";
|
|
1974
|
+
let m = -1;
|
|
1975
|
+
for (const c of (cs ?? "")) {
|
|
1976
|
+
const v = c.codePointAt(0);
|
|
1977
|
+
if (${SURROGATE}) continue;
|
|
1978
|
+
const i = s.lastIndexOf(c);
|
|
1979
|
+
if (i > m) m = i;
|
|
1980
|
+
}
|
|
1981
|
+
return m;
|
|
1982
|
+
},
|
|
1983
|
+
"str_contains_any": (s, cs) => {
|
|
1984
|
+
s = s ?? "";
|
|
1985
|
+
for (const c of (cs ?? "")) {
|
|
1986
|
+
const v = c.codePointAt(0);
|
|
1987
|
+
if (!${SURROGATE} && s.includes(c)) return 1;
|
|
1988
|
+
}
|
|
1989
|
+
return 0;
|
|
1803
1990
|
},
|
|
1991
|
+
"str_count": (s, t) => {
|
|
1992
|
+
s = s ?? ""; t = t ?? "";
|
|
1993
|
+
if (t === "") return [...s].length + 1;
|
|
1994
|
+
let n = 0;
|
|
1995
|
+
for (let i = s.indexOf(t); i !== -1; i = s.indexOf(t, i + t.length)) n++;
|
|
1996
|
+
return n;
|
|
1997
|
+
},
|
|
1998
|
+
"str_replace": (s, o, w, n) => ${P}replace(s ?? "", o ?? "", w ?? "", n),
|
|
1999
|
+
"str_replace_all": (s, o, w) => ${P}replace(s ?? "", o ?? "", w ?? "", -1),
|
|
2000
|
+
"str_trim": (s, c) => ${P}trim(s ?? "", c ?? "", true, true),
|
|
2001
|
+
"str_trim_left": (s, c) => ${P}trim(s ?? "", c ?? "", true, false),
|
|
2002
|
+
"str_trim_right": (s, c) => ${P}trim(s ?? "", c ?? "", false, true),
|
|
2003
|
+
"str_trim_prefix": (s, p) => ((s ?? "").startsWith(p ?? "") ? (s ?? "").slice((p ?? "").length) : (s ?? "")),
|
|
2004
|
+
"str_trim_suffix": (s, p) => ((s ?? "").endsWith(p ?? "") ? (s ?? "").slice(0, (s ?? "").length - (p ?? "").length) : (s ?? "")),
|
|
1804
2005
|
"str_from_i64": (n) => String(n),
|
|
1805
2006
|
"str_from_i32": (n) => String(n),
|
|
1806
|
-
"str_from_f64": (f) => String(f),
|
|
2007
|
+
"str_from_f64": (f) => (Object.is(f, -0) ? "-0" : String(f)),
|
|
1807
2008
|
"is_string": (v) => (typeof v === "string" ? 1 : 0),
|
|
1808
2009
|
"str_to_bytes": (s) => {
|
|
1809
|
-
utf8Encoded =
|
|
2010
|
+
utf8Encoded = ${P}u8enc(s ?? "");
|
|
1810
2011
|
return utf8Encoded.length;
|
|
1811
2012
|
},
|
|
1812
2013
|
"str_byte_at": (i) => (utf8Encoded ? (utf8Encoded[i] ?? 0) : 0),
|
|
@@ -1826,636 +2027,127 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
|
|
|
1826
2027
|
},
|
|
1827
2028
|
"print_i32": print, "print_i64": print, "print_f32": print, "print_f64": print,
|
|
1828
2029
|
"print_str": print, "print_any": print,
|
|
2030
|
+
"print_u64": (v) => print(BigInt.asUintN(64, v)),
|
|
2031
|
+
"println_u64": (v) => println(BigInt.asUintN(64, v)),
|
|
2032
|
+
"print_reset": () => { started = false; },
|
|
1829
2033
|
"print_bool": (b) => print(b !== 0 ? "true" : "false"),
|
|
1830
2034
|
"println_i32": println, "println_i64": println, "println_f32": println, "println_f64": println,
|
|
1831
2035
|
"println_str": println, "println_any": println,
|
|
1832
2036
|
"println_bool": (b) => println(b !== 0 ? "true" : "false"),
|
|
1833
2037
|
"println_empty": flush,
|
|
1834
|
-
"fmt_new_array": () => [],
|
|
1835
|
-
"fmt_array_push": (arr, v) => { arr.push(v); },
|
|
1836
|
-
"fmt_new_map": () => new Map(),
|
|
1837
|
-
"fmt_map_set": (m, k, v) => { m.set(k, v); },
|
|
1838
|
-
"fmt_new_struct": (ref, isPtr) => {
|
|
1839
|
-
if (ref !== null && ref !== undefined && typeof ref === "object" && fmtStructMap.has(ref)) {
|
|
1840
|
-
return fmtStructMap.get(ref);
|
|
1841
|
-
}
|
|
1842
|
-
const o = {};
|
|
1843
|
-
Object.defineProperty(o, "__struct", { value: true, configurable: true });
|
|
1844
|
-
if (isPtr !== 0) Object.defineProperty(o, "__p", { value: true, configurable: true });
|
|
1845
|
-
if (ref !== null && ref !== undefined && typeof ref === "object") fmtStructMap.set(ref, o);
|
|
1846
|
-
return o;
|
|
1847
|
-
},
|
|
1848
|
-
"fmt_struct_set": (o, k, v) => { o[typeof k === "number" ? stringTable[k] : k] = v; },
|
|
1849
|
-
"fmt_box_i32": (v) => v,
|
|
1850
|
-
"fmt_box_i64": (v) => (v > ${P}MAX || v < -${P}MAX ? v : Number(v)),
|
|
1851
|
-
"fmt_box_f64": (v) => v,
|
|
1852
|
-
"fmt_box_bool": (v) => v !== 0,
|
|
1853
|
-
"fmt_new_box": () => ({ value: null, __p: true, __box: true }),
|
|
1854
|
-
"testing_arg_i32": targ, "testing_arg_f32": targ, "testing_arg_f64": targ,
|
|
1855
|
-
"testing_arg_str": targ, "testing_arg_any": targ,
|
|
1856
|
-
"testing_arg_i64": (v) => targ(v > ${P}MAX || v < -${P}MAX ? v : Number(v)),
|
|
1857
|
-
"testing_arg_bool": (v) => targ(v !== 0),
|
|
1858
|
-
"testing_call": (t, i) => { const a = targs; targs = []; fmtStructMap = new WeakMap(); t[stringTable[i]](...a); },
|
|
1859
|
-
"testing_name": (t) => t.Name(),
|
|
1860
|
-
"testing_flag": (t, i) => (t[stringTable[i]]() ? 1 : 0),
|
|
1861
2038
|
};
|
|
1862
|
-
// Stdlib imports (strings/strconv/utf8/fmt/t.Run) are a separate block so
|
|
1863
|
-
// modules that never call them do not ship it (see WASM_STDLIB_JS).
|
|
1864
|
-
if (typeof ${P}stdlib === "function") {
|
|
1865
|
-
Object.assign(env, ${P}stdlib(stringTable, {
|
|
1866
|
-
"take": () => { const a = targs; targs = []; fmtStructMap = new WeakMap(); return a; },
|
|
1867
|
-
"printf": (s) => {
|
|
1868
|
-
lineBuf += s;
|
|
1869
|
-
const nl = lineBuf.lastIndexOf("\\n");
|
|
1870
|
-
if (nl >= 0) { write(lineBuf.slice(0, nl)); lineBuf = lineBuf.slice(nl + 1); }
|
|
1871
|
-
},
|
|
1872
|
-
"late": late,
|
|
1873
|
-
}));
|
|
1874
|
-
}
|
|
1875
2039
|
Object.assign(env, extraEnv);
|
|
2040
|
+
// A host import that throws raises a Go panic: the module's panic stack
|
|
2041
|
+
// gets the message (as env.panic does through __push_panic) before the
|
|
2042
|
+
// error unwinds, so recover() in a calling frame sees it. Errors already
|
|
2043
|
+
// pushed (env.panic, nested calls into the module) are marked and pass
|
|
2044
|
+
// through; traps and wasm exceptions are not host panics.
|
|
2045
|
+
const hostPanic = (e) => {
|
|
2046
|
+
if (e !== null && typeof e === "object") {
|
|
2047
|
+
if (e.__gfp === true) return;
|
|
2048
|
+
if (typeof WebAssembly !== "undefined" && (e instanceof WebAssembly.Exception || e instanceof WebAssembly.RuntimeError)) return;
|
|
2049
|
+
try { e.__gfp = true; } catch {}
|
|
2050
|
+
}
|
|
2051
|
+
const push = late.exports && late.exports.__push_panic;
|
|
2052
|
+
if (push) push(String(e !== null && typeof e === "object" && e.message !== undefined ? e.message : e));
|
|
2053
|
+
};
|
|
2054
|
+
for (const k of Object.keys(env)) {
|
|
2055
|
+
const fn = env[k];
|
|
2056
|
+
if (typeof fn !== "function" || k === "panic" || k === "panicTag") continue;
|
|
2057
|
+
env[k] = function () {
|
|
2058
|
+
try { return fn.apply(this, arguments); } catch (e) { hostPanic(e); throw e; }
|
|
2059
|
+
};
|
|
2060
|
+
}
|
|
1876
2061
|
const m = {};
|
|
1877
|
-
for (const k of ["sin", "cos", "tan", "asin", "acos", "atan", "atan2", "pow", "exp", "log", "log2", "log10", "cbrt", "hypot", "random", "sinh", "cosh", "tanh"]) m[k] = Math[k];
|
|
2062
|
+
for (const k of ["sin", "cos", "tan", "asin", "acos", "atan", "atan2", "pow", "exp", "log", "log2", "log10", "cbrt", "hypot", "random", "sinh", "cosh", "tanh", "log1p", "expm1"]) m[k] = Math[k];
|
|
1878
2063
|
m.mod = (x, y) => x % y;
|
|
1879
2064
|
// Go rounds half away from zero; Math.round rounds half toward +Infinity.
|
|
1880
2065
|
m.round = (x) => Math.sign(x) * Math.round(Math.abs(x));
|
|
1881
2066
|
// Exports are needed by imports that call back into the module (t.Run).
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
${HELPER_DURATION}
|
|
1892
|
-
${HELPER_SHA256}
|
|
1893
|
-
${HELPER_HEX}
|
|
1894
|
-
${HELPER_BASE64}
|
|
1895
|
-
function ${P}stdlib(stringTable, io) {
|
|
1896
|
-
let parts = [];
|
|
1897
|
-
let cutBefore = "", cutAfter = "";
|
|
1898
|
-
let strconvErr = null, strconvErrKind = 0;
|
|
1899
|
-
let decodeSize = 0;
|
|
1900
|
-
let replacerTarget = "", replacerPairs = [];
|
|
1901
|
-
let splitDir = "", splitFile = "";
|
|
1902
|
-
let joinParts = [];
|
|
1903
|
-
let matchHasErr = 0;
|
|
1904
|
-
let trimStart = 0, trimEnd = 0;
|
|
1905
|
-
let sscanTokens = [];
|
|
1906
|
-
let sscanHasErr = 0, sscanErrMsg = "";
|
|
1907
|
-
let timeParseErr = null, timeParseOffset = 0;
|
|
1908
|
-
let rawBytes = new Uint8Array(0);
|
|
1909
|
-
let rawPos = 0;
|
|
1910
|
-
let hexErr = null;
|
|
1911
|
-
let base64Err = null;
|
|
1912
|
-
let customBase64Encodings = [];
|
|
1913
|
-
return {
|
|
1914
|
-
"fmt_sscan_init": (s, mode) => {
|
|
1915
|
-
sscanHasErr = 0;
|
|
1916
|
-
sscanErrMsg = "";
|
|
1917
|
-
const str = s ?? "";
|
|
1918
|
-
sscanTokens = (mode === 1 ? str.split("\\n")[0] : str).trim().split(/\\s+/).filter(Boolean);
|
|
1919
|
-
return sscanTokens.length;
|
|
1920
|
-
},
|
|
1921
|
-
"fmt_sscanf_init": (s, fmt) => {
|
|
1922
|
-
sscanHasErr = 0;
|
|
1923
|
-
sscanErrMsg = "";
|
|
1924
|
-
const str = s ?? "";
|
|
1925
|
-
const fmtStr = fmt ?? "";
|
|
1926
|
-
let si = 0, fi = 0;
|
|
1927
|
-
sscanTokens = [];
|
|
1928
|
-
while (fi < fmtStr.length) {
|
|
1929
|
-
if (/\\s/.test(fmtStr[fi])) {
|
|
1930
|
-
while (fi < fmtStr.length && /\\s/.test(fmtStr[fi])) fi++;
|
|
1931
|
-
while (si < str.length && /\\s/.test(str[si])) si++;
|
|
1932
|
-
continue;
|
|
1933
|
-
}
|
|
1934
|
-
if (fmtStr[fi] === "%") {
|
|
1935
|
-
if (fi + 1 < fmtStr.length && fmtStr[fi + 1] === "%") {
|
|
1936
|
-
if (str[si] !== "%") {
|
|
1937
|
-
sscanHasErr = 1;
|
|
1938
|
-
sscanErrMsg = "input does not match format";
|
|
1939
|
-
break;
|
|
1940
|
-
}
|
|
1941
|
-
si++;
|
|
1942
|
-
fi += 2;
|
|
1943
|
-
continue;
|
|
1944
|
-
}
|
|
1945
|
-
const vm = fmtStr.slice(fi).match(/^%[vdsfqtxXbcoeEfFgG]/);
|
|
1946
|
-
if (vm) {
|
|
1947
|
-
const verb = vm[0];
|
|
1948
|
-
fi += verb.length;
|
|
1949
|
-
if (verb !== "%c") {
|
|
1950
|
-
while (si < str.length && /\\s/.test(str[si])) si++;
|
|
1951
|
-
}
|
|
1952
|
-
if (si >= str.length) break;
|
|
1953
|
-
let tokRe = /^\\S+/;
|
|
1954
|
-
if (verb === "%d" || verb === "%i") tokRe = /^[+-]?\\d+/;
|
|
1955
|
-
else if (verb === "%f" || verb === "%F" || verb === "%e" || verb === "%E" || verb === "%g" || verb === "%G") tokRe = /^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?/;
|
|
1956
|
-
else if (verb === "%t") tokRe = /^(?:true|false|1|0|t|f|T|F|TRUE|FALSE)/;
|
|
1957
|
-
else if (verb === "%x" || verb === "%X") tokRe = /^[+-]?(?:0x)?[0-9a-fA-F]+/;
|
|
1958
|
-
else if (verb === "%c") tokRe = /^[\\s\\S]/;
|
|
1959
|
-
const m = str.slice(si).match(tokRe);
|
|
1960
|
-
if (!m) {
|
|
1961
|
-
sscanHasErr = 1;
|
|
1962
|
-
sscanErrMsg = "input does not match format";
|
|
1963
|
-
break;
|
|
1964
|
-
}
|
|
1965
|
-
sscanTokens.push(m[0]);
|
|
1966
|
-
si += m[0].length;
|
|
1967
|
-
continue;
|
|
1968
|
-
}
|
|
1969
|
-
}
|
|
1970
|
-
if (si >= str.length) break;
|
|
1971
|
-
if (str[si] !== fmtStr[fi]) {
|
|
1972
|
-
sscanHasErr = 1;
|
|
1973
|
-
sscanErrMsg = "input does not match format";
|
|
1974
|
-
break;
|
|
1975
|
-
}
|
|
1976
|
-
si++;
|
|
1977
|
-
fi++;
|
|
1978
|
-
}
|
|
1979
|
-
return sscanTokens.length;
|
|
1980
|
-
},
|
|
1981
|
-
"fmt_sscan_has_err": () => sscanHasErr,
|
|
1982
|
-
"fmt_sscan_err_msg": () => sscanErrMsg || "syntax error",
|
|
1983
|
-
"fmt_sscan_token_str": (i) => sscanTokens[i] ?? "",
|
|
1984
|
-
"fmt_sscan_token_i64": (i) => {
|
|
1985
|
-
if (i >= sscanTokens.length) { sscanHasErr = 1; sscanErrMsg = "unexpected EOF"; return 0n; }
|
|
1986
|
-
const t = sscanTokens[i];
|
|
1987
|
-
if (/^[+-]?(?:0[xX])?[0-9a-fA-F]+$/.test(t)) {
|
|
1988
|
-
try {
|
|
1989
|
-
if (/^[+-]?[0-9a-fA-F]+$/.test(t) && /[a-fA-F]/.test(t)) {
|
|
1990
|
-
return BigInt("0x" + t);
|
|
1991
|
-
}
|
|
1992
|
-
return BigInt(t);
|
|
1993
|
-
} catch {}
|
|
1994
|
-
}
|
|
1995
|
-
if (t.length === 1) {
|
|
1996
|
-
return BigInt(t.charCodeAt(0));
|
|
1997
|
-
}
|
|
1998
|
-
sscanHasErr = 1;
|
|
1999
|
-
sscanErrMsg = "expected integer";
|
|
2000
|
-
return 0n;
|
|
2001
|
-
},
|
|
2002
|
-
"fmt_sscan_token_f64": (i) => {
|
|
2003
|
-
if (i >= sscanTokens.length) { sscanHasErr = 1; sscanErrMsg = "unexpected EOF"; return 0; }
|
|
2004
|
-
const t = sscanTokens[i];
|
|
2005
|
-
const n = Number(t);
|
|
2006
|
-
if (Number.isNaN(n) || !/^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?$/.test(t)) {
|
|
2007
|
-
sscanHasErr = 1;
|
|
2008
|
-
sscanErrMsg = "expected float";
|
|
2009
|
-
return 0;
|
|
2010
|
-
}
|
|
2011
|
-
return n;
|
|
2012
|
-
},
|
|
2013
|
-
"fmt_sscan_token_bool": (i) => {
|
|
2014
|
-
if (i >= sscanTokens.length) { sscanHasErr = 1; sscanErrMsg = "unexpected EOF"; return 0; }
|
|
2015
|
-
const t = sscanTokens[i]?.toLowerCase();
|
|
2016
|
-
if (t === "true" || t === "1" || t === "t") return 1;
|
|
2017
|
-
if (t === "false" || t === "0" || t === "f") return 0;
|
|
2018
|
-
sscanHasErr = 1;
|
|
2019
|
-
sscanErrMsg = "expected boolean";
|
|
2020
|
-
return 0;
|
|
2021
|
-
},
|
|
2022
|
-
"testing_run": (t, name, fn) => (t.Run(name, (sub) => { io.late.exports.__testing_run_cb(fn, sub); }) ? 1 : 0),
|
|
2023
|
-
"fmt_sprintf": (f) => __sprintf(f ?? "", ...io.take()),
|
|
2024
|
-
"fmt_printf": (f) => { io.printf(__sprintf(f ?? "", ...io.take())); },
|
|
2025
|
-
"fmt_format_v": (v) => { const res = __sprintf("%v", v); io.take(); return res; },
|
|
2026
|
-
"str_trim": (s, c) => ${P}trim(s ?? "", c ?? "", true, true),
|
|
2027
|
-
"str_trim_left": (s, c) => ${P}trim(s ?? "", c ?? "", true, false),
|
|
2028
|
-
"str_trim_right": (s, c) => ${P}trim(s ?? "", c ?? "", false, true),
|
|
2029
|
-
"str_trim_prefix": (s, p) => (s && p && s.startsWith(p) ? s.slice(p.length) : (s ?? "")),
|
|
2030
|
-
"str_trim_suffix": (s, p) => (s && p && s.endsWith(p) ? s.slice(0, s.length - p.length) : (s ?? "")),
|
|
2031
|
-
"str_cut": (s, sep) => {
|
|
2032
|
-
const i = (s ?? "").indexOf(sep ?? "");
|
|
2033
|
-
if (i < 0) { cutBefore = s ?? ""; cutAfter = ""; return 0; }
|
|
2034
|
-
cutBefore = (s ?? "").slice(0, i);
|
|
2035
|
-
cutAfter = (s ?? "").slice(i + (sep ?? "").length);
|
|
2036
|
-
return 1;
|
|
2037
|
-
},
|
|
2038
|
-
"str_cut_before": () => cutBefore,
|
|
2039
|
-
"str_cut_after": () => cutAfter,
|
|
2040
|
-
"str_cut_prefix": (s, p) => {
|
|
2041
|
-
s = s ?? ""; p = p ?? "";
|
|
2042
|
-
if (s.startsWith(p)) { cutAfter = s.slice(p.length); return 1; }
|
|
2043
|
-
cutAfter = s; return 0;
|
|
2044
|
-
},
|
|
2045
|
-
"str_cut_suffix": (s, suf) => {
|
|
2046
|
-
s = s ?? ""; suf = suf ?? "";
|
|
2047
|
-
if (s.endsWith(suf)) { cutBefore = s.slice(0, s.length - suf.length); return 1; }
|
|
2048
|
-
cutBefore = s; return 0;
|
|
2049
|
-
},
|
|
2050
|
-
"str_replace": (s, o, n, cnt) => {
|
|
2051
|
-
s = s ?? ""; o = o ?? ""; n = n ?? "";
|
|
2052
|
-
if (cnt < 0) return s.replaceAll(o, n);
|
|
2053
|
-
let out = "", from = 0;
|
|
2054
|
-
for (let k = 0; k < cnt; k++) {
|
|
2055
|
-
const at = s.indexOf(o, from);
|
|
2056
|
-
if (at < 0) break;
|
|
2057
|
-
out += s.slice(from, at) + n;
|
|
2058
|
-
from = at + o.length;
|
|
2059
|
-
if (o === "") { if (from >= s.length) break; out += s[from]; from++; }
|
|
2060
|
-
}
|
|
2061
|
-
return out + s.slice(from);
|
|
2062
|
-
},
|
|
2063
|
-
"str_contains_rune": (s, r) => ((s ?? "").includes(String.fromCodePoint(r)) ? 1 : 0),
|
|
2064
|
-
"str_contains_any": (s, cs) => { for (const c of (cs ?? "")) if ((s ?? "").includes(c)) return 1; return 0; },
|
|
2065
|
-
"str_index_byte": (s, b) => (s ?? "").indexOf(String.fromCharCode(b)),
|
|
2066
|
-
"str_index_rune": (s, r) => (s ?? "").indexOf(String.fromCodePoint(r)),
|
|
2067
|
-
"str_index_any": (s, cs) => { s = s ?? ""; let best = -1; for (const c of (cs ?? "")) { const i = s.indexOf(c); if (i >= 0 && (best < 0 || i < best)) best = i; } return best; },
|
|
2068
|
-
"str_last_index_byte": (s, b) => (s ?? "").lastIndexOf(String.fromCharCode(b)),
|
|
2069
|
-
"str_last_index_any": (s, cs) => {
|
|
2070
|
-
s = s ?? ""; let best = -1;
|
|
2071
|
-
for (const c of (cs ?? "")) {
|
|
2072
|
-
const i = s.lastIndexOf(c);
|
|
2073
|
-
if (i > best) best = i;
|
|
2074
|
-
}
|
|
2075
|
-
return best;
|
|
2076
|
-
},
|
|
2077
|
-
"str_title": (s) => (s ?? "").replace(/(^|[^\\p{L}\\p{N}_'])(\\p{Ll})/gu, (m, a, b) => a + b.toUpperCase()),
|
|
2078
|
-
"str_split": (s, sep) => { parts = (s ?? "").split(sep ?? ""); return parts.length; },
|
|
2079
|
-
"str_split_n": (s, sep, n) => {
|
|
2080
|
-
s = s ?? ""; sep = sep ?? "";
|
|
2081
|
-
if (n === 0) return -1;
|
|
2082
|
-
if (n < 0) { parts = sep === "" ? [...s] : s.split(sep); return parts.length; }
|
|
2083
|
-
if (sep === "") {
|
|
2084
|
-
const chars = [...s];
|
|
2085
|
-
if (chars.length <= n) { parts = chars; return parts.length; }
|
|
2086
|
-
const r = chars.slice(0, n - 1);
|
|
2087
|
-
r.push(chars.slice(n - 1).join(""));
|
|
2088
|
-
parts = r;
|
|
2089
|
-
return parts.length;
|
|
2090
|
-
}
|
|
2091
|
-
const r = [];
|
|
2092
|
-
let cur = s;
|
|
2093
|
-
for (let i = 1; i < n && cur.length; i++) {
|
|
2094
|
-
const j = cur.indexOf(sep);
|
|
2095
|
-
if (j < 0) break;
|
|
2096
|
-
r.push(cur.slice(0, j));
|
|
2097
|
-
cur = cur.slice(j + sep.length);
|
|
2098
|
-
}
|
|
2099
|
-
r.push(cur);
|
|
2100
|
-
parts = r;
|
|
2101
|
-
return parts.length;
|
|
2102
|
-
},
|
|
2103
|
-
"str_split_after": (s, sep) => {
|
|
2104
|
-
s = s ?? ""; sep = sep ?? "";
|
|
2105
|
-
if (sep === "") { parts = [...s]; return parts.length; }
|
|
2106
|
-
const r = [];
|
|
2107
|
-
let cur = s;
|
|
2108
|
-
while (true) {
|
|
2109
|
-
const j = cur.indexOf(sep);
|
|
2110
|
-
if (j < 0) { r.push(cur); break; }
|
|
2111
|
-
r.push(cur.slice(0, j + sep.length));
|
|
2112
|
-
cur = cur.slice(j + sep.length);
|
|
2113
|
-
}
|
|
2114
|
-
parts = r;
|
|
2115
|
-
return parts.length;
|
|
2116
|
-
},
|
|
2117
|
-
"str_split_after_n": (s, sep, n) => {
|
|
2118
|
-
s = s ?? ""; sep = sep ?? "";
|
|
2119
|
-
if (n === 0) return -1;
|
|
2120
|
-
if (sep === "") {
|
|
2121
|
-
const chars = [...s];
|
|
2122
|
-
if (n < 0 || chars.length <= n) parts = chars;
|
|
2123
|
-
else {
|
|
2124
|
-
const r = chars.slice(0, n - 1);
|
|
2125
|
-
r.push(chars.slice(n - 1).join(""));
|
|
2126
|
-
parts = r;
|
|
2127
|
-
}
|
|
2128
|
-
return parts.length;
|
|
2129
|
-
}
|
|
2130
|
-
const r = [];
|
|
2131
|
-
let cur = s;
|
|
2132
|
-
for (let i = 1; (n < 0 || i < n) && cur.length; i++) {
|
|
2133
|
-
const j = cur.indexOf(sep);
|
|
2134
|
-
if (j < 0) break;
|
|
2135
|
-
r.push(cur.slice(0, j + sep.length));
|
|
2136
|
-
cur = cur.slice(j + sep.length);
|
|
2137
|
-
}
|
|
2138
|
-
r.push(cur);
|
|
2139
|
-
parts = r;
|
|
2140
|
-
return parts.length;
|
|
2141
|
-
},
|
|
2142
|
-
"str_fields": (s) => { parts = (s ?? "").split(/\\s+/).filter((p) => p !== ""); return parts.length; },
|
|
2143
|
-
"str_part": (i) => parts[i] ?? "",
|
|
2144
|
-
"str_replacer_init": (s) => {
|
|
2145
|
-
replacerTarget = s ?? "";
|
|
2146
|
-
replacerPairs = [];
|
|
2147
|
-
},
|
|
2148
|
-
"str_replacer_add": (o, n) => {
|
|
2149
|
-
replacerPairs.push([o ?? "", n ?? ""]);
|
|
2150
|
-
},
|
|
2151
|
-
"str_replacer_run": () => {
|
|
2152
|
-
let s = replacerTarget;
|
|
2153
|
-
if (!replacerPairs.length || s === "") return s;
|
|
2154
|
-
let out = "";
|
|
2155
|
-
let i = 0;
|
|
2156
|
-
while (i < s.length) {
|
|
2157
|
-
let matched = false;
|
|
2158
|
-
for (let k = 0; k < replacerPairs.length; k++) {
|
|
2159
|
-
const [oldStr, newStr] = replacerPairs[k];
|
|
2160
|
-
if (oldStr.length > 0 && s.startsWith(oldStr, i)) {
|
|
2161
|
-
out += newStr;
|
|
2162
|
-
i += oldStr.length;
|
|
2163
|
-
matched = true;
|
|
2164
|
-
break;
|
|
2165
|
-
}
|
|
2166
|
-
}
|
|
2167
|
-
if (!matched) {
|
|
2168
|
-
out += s[i];
|
|
2169
|
-
i++;
|
|
2170
|
-
}
|
|
2171
|
-
}
|
|
2172
|
-
return out;
|
|
2173
|
-
},
|
|
2174
|
-
"strconv_atoi": (s) => {
|
|
2175
|
-
const r = __strconv_atoi(s ?? "");
|
|
2176
|
-
strconvErr = r[1];
|
|
2177
|
-
strconvErrKind = r[1] ? (r[1].includes("invalid syntax") ? 1 : r[1].includes("value out of range") ? 2 : 0) : 0;
|
|
2178
|
-
return r[0];
|
|
2179
|
-
},
|
|
2180
|
-
"strconv_parse_int": (s, base, bits) => {
|
|
2181
|
-
const r = __strconv_parse_int(s ?? "", base, bits);
|
|
2182
|
-
strconvErr = r[1];
|
|
2183
|
-
strconvErrKind = r[1] ? (r[1].includes("invalid syntax") ? 1 : r[1].includes("value out of range") ? 2 : 0) : 0;
|
|
2184
|
-
return r[0];
|
|
2185
|
-
},
|
|
2186
|
-
"strconv_parse_float": (s) => {
|
|
2187
|
-
const r = __strconv_parse_float(s ?? "");
|
|
2188
|
-
strconvErr = r[1];
|
|
2189
|
-
strconvErrKind = r[1] ? (r[1].includes("invalid syntax") ? 1 : r[1].includes("value out of range") ? 2 : 0) : 0;
|
|
2190
|
-
return r[0];
|
|
2191
|
-
},
|
|
2192
|
-
"strconv_parse_bool": (s) => {
|
|
2193
|
-
const r = __strconv_parse_bool(s ?? "");
|
|
2194
|
-
strconvErr = r[1];
|
|
2195
|
-
strconvErrKind = r[1] ? (r[1].includes("invalid syntax") ? 1 : r[1].includes("value out of range") ? 2 : 0) : 0;
|
|
2196
|
-
return r[0] ? 1 : 0;
|
|
2197
|
-
},
|
|
2198
|
-
"strconv_err": () => strconvErr,
|
|
2199
|
-
"strconv_err_kind": () => strconvErrKind,
|
|
2200
|
-
"strconv_format_float": (f, fmtc, prec, _bits) => __strconv_format_float(f, fmtc, prec),
|
|
2201
|
-
"strconv_format_int": (n, base) => n.toString(base),
|
|
2202
|
-
"strconv_format_uint": (n, base) => BigInt.asUintN(64, BigInt(n)).toString(base),
|
|
2203
|
-
"strconv_parse_uint": (s, base, bits) => {
|
|
2204
|
-
const r = __strconv_parse_uint(s ?? "", base, bits);
|
|
2205
|
-
strconvErr = r[1];
|
|
2206
|
-
strconvErrKind = r[1] ? (r[1].includes("invalid syntax") ? 1 : r[1].includes("value out of range") ? 2 : 0) : 0;
|
|
2207
|
-
return r[0];
|
|
2208
|
-
},
|
|
2209
|
-
"strconv_quote": (s) => JSON.stringify(s ?? ""),
|
|
2210
|
-
"strconv_unquote": (s) => {
|
|
2211
|
-
s = s ?? "";
|
|
2212
|
-
if (s.startsWith("\`") && s.endsWith("\`") && s.length >= 2) {
|
|
2213
|
-
if (s.slice(1, -1).includes("\`")) {
|
|
2214
|
-
strconvErr = "invalid syntax";
|
|
2215
|
-
strconvErrKind = 1;
|
|
2216
|
-
return "";
|
|
2217
|
-
}
|
|
2218
|
-
strconvErr = null;
|
|
2219
|
-
strconvErrKind = 0;
|
|
2220
|
-
return s.slice(1, -1);
|
|
2221
|
-
}
|
|
2222
|
-
try {
|
|
2223
|
-
const v = JSON.parse(s);
|
|
2224
|
-
if (typeof v !== "string") {
|
|
2225
|
-
strconvErr = "invalid syntax";
|
|
2226
|
-
strconvErrKind = 1;
|
|
2227
|
-
return "";
|
|
2228
|
-
}
|
|
2229
|
-
strconvErr = null;
|
|
2230
|
-
strconvErrKind = 0;
|
|
2231
|
-
return v;
|
|
2232
|
-
} catch (e) {
|
|
2233
|
-
strconvErr = "invalid syntax";
|
|
2234
|
-
strconvErrKind = 1;
|
|
2235
|
-
return "";
|
|
2236
|
-
}
|
|
2237
|
-
},
|
|
2238
|
-
"utf8_rune_count": (s) => { let n = 0; for (const _ of (s ?? "")) n++; return n; },
|
|
2239
|
-
"utf8_rune_len": (r) => (r < 0 || r > 0x10ffff || (r >= 0xd800 && r <= 0xdfff) ? -1 : r < 0x80 ? 1 : r < 0x800 ? 2 : r < 0x10000 ? 3 : 4),
|
|
2240
|
-
"utf8_valid_string": (s) => (s ?? "").isWellFormed ? ((s ?? "").isWellFormed() ? 1 : 0) : (/[\\uD800-\\uDFFF]/.test((s ?? "").replace(/[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]/g, "")) ? 0 : 1),
|
|
2241
|
-
"utf8_decode_rune": (s, last) => {
|
|
2242
|
-
s = s ?? "";
|
|
2243
|
-
if (s.length === 0) { decodeSize = 0; return 0xfffd; }
|
|
2244
|
-
let cp;
|
|
2245
|
-
if (last) { const tail = s.codePointAt(s.length - 1); cp = (tail >= 0xdc00 && tail <= 0xdfff && s.length >= 2) ? s.codePointAt(s.length - 2) : tail; }
|
|
2246
|
-
else cp = s.codePointAt(0);
|
|
2247
|
-
if (cp >= 0xd800 && cp <= 0xdfff) { decodeSize = 1; return 0xfffd; }
|
|
2248
|
-
// Size is in UTF-16 code units, matching the JS backend (strings are JS strings).
|
|
2249
|
-
decodeSize = cp > 0xffff ? 2 : 1;
|
|
2250
|
-
return cp;
|
|
2251
|
-
},
|
|
2252
|
-
"utf8_decode_size": () => decodeSize,
|
|
2253
|
-
"utf8_full_rune": (s) => { s = s ?? ""; if (s.length === 0) return 0; const c = s.charCodeAt(0); return (c >= 0xd800 && c <= 0xdbff && s.length < 2) ? 0 : 1; },
|
|
2254
|
-
// unicode
|
|
2255
|
-
"unicode_is_letter": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{L}/u.test(c)) ? 1 : 0; },
|
|
2256
|
-
"unicode_is_digit": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{Nd}/u.test(c)) ? 1 : 0; },
|
|
2257
|
-
"unicode_is_space": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\s/u.test(c)) ? 1 : 0; },
|
|
2258
|
-
"unicode_is_upper": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{Lu}/u.test(c)) ? 1 : 0; },
|
|
2259
|
-
"unicode_is_lower": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{Ll}/u.test(c)) ? 1 : 0; },
|
|
2260
|
-
"unicode_is_punct": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{P}/u.test(c)) ? 1 : 0; },
|
|
2261
|
-
"unicode_is_control": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{Cc}/u.test(c)) ? 1 : 0; },
|
|
2262
|
-
"unicode_is_print": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && !/\\p{Cc}/u.test(c)) ? 1 : 0; },
|
|
2263
|
-
"unicode_is_graphic": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && !/\\p{Cc}/u.test(c)) ? 1 : 0; },
|
|
2264
|
-
"unicode_to_upper": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return c ? (c.toUpperCase().codePointAt(0) ?? r) : r; },
|
|
2265
|
-
"unicode_to_lower": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return c ? (c.toLowerCase().codePointAt(0) ?? r) : r; },
|
|
2266
|
-
// html
|
|
2267
|
-
"html_escape": (s) => (s ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'"),
|
|
2268
|
-
"html_unescape": (s) => (s ?? "").replace(/&(?:quot|apos|#39|#34|gt|lt|amp|#(?:[0-9]+|[xX][0-9a-fA-F]+));/g, (m) => {
|
|
2269
|
-
switch (m) {
|
|
2270
|
-
case """: case """: return '"';
|
|
2271
|
-
case "'": case "'": return "'";
|
|
2272
|
-
case ">": return ">";
|
|
2273
|
-
case "<": return "<";
|
|
2274
|
-
case "&": return "&";
|
|
2275
|
-
default: {
|
|
2276
|
-
if (m.startsWith("&#")) {
|
|
2277
|
-
const raw = m.slice(2, -1);
|
|
2278
|
-
const cp = (raw.startsWith("x") || raw.startsWith("X")) ? parseInt(raw.slice(1), 16) : parseInt(raw, 10);
|
|
2279
|
-
return (cp >= 0 && cp <= 0x10FFFF && !(cp >= 0xD800 && cp <= 0xDFFF)) ? String.fromCodePoint(cp) : "\\uFFFD";
|
|
2280
|
-
}
|
|
2281
|
-
return m;
|
|
2282
|
-
}
|
|
2283
|
-
}
|
|
2284
|
-
}),
|
|
2285
|
-
// path
|
|
2286
|
-
"path_base": (p) => { if (!p) return "."; const stripped = p.replace(/\\/+$/, ""); if (!stripped) return "/"; const i = stripped.lastIndexOf("/"); return i < 0 ? stripped : stripped.slice(i + 1) || "/"; },
|
|
2287
|
-
"path_dir": (p) => { const s = p ?? ""; const i = s.lastIndexOf("/"); if (i < 0) return "."; const res = s.slice(0, i); return res === "" ? "/" : __pathClean(res); },
|
|
2288
|
-
"path_ext": (p) => { const s = p ?? ""; const b = s.slice(s.lastIndexOf("/") + 1); const i = b.lastIndexOf("."); return i <= 0 ? "" : b.slice(i); },
|
|
2289
|
-
"path_is_abs": (p) => (p ?? "").startsWith("/") ? 1 : 0,
|
|
2290
|
-
"path_clean": (p) => __pathClean(p ?? ""),
|
|
2291
|
-
"path_split": (p) => {
|
|
2292
|
-
const s = p ?? "";
|
|
2293
|
-
const i = s.lastIndexOf("/");
|
|
2294
|
-
if (i < 0) { splitDir = ""; splitFile = s; }
|
|
2295
|
-
else { splitDir = s.slice(0, i + 1); splitFile = s.slice(i + 1); }
|
|
2296
|
-
},
|
|
2297
|
-
"path_split_dir": () => splitDir,
|
|
2298
|
-
"path_split_file": () => splitFile,
|
|
2299
|
-
"path_join_init": () => { joinParts = []; },
|
|
2300
|
-
"path_join_push": (s) => { if (s) joinParts.push(s); },
|
|
2301
|
-
"path_join_run": () => {
|
|
2302
|
-
if (joinParts.length === 0) return "";
|
|
2303
|
-
const res = __pathClean(joinParts.join("/"));
|
|
2304
|
-
joinParts = [];
|
|
2305
|
-
return res;
|
|
2306
|
-
},
|
|
2307
|
-
"path_match": (pat, name) => {
|
|
2308
|
-
const r = __pathMatch(pat ?? "", name ?? "");
|
|
2309
|
-
matchHasErr = r[1] ? 1 : 0;
|
|
2310
|
-
return r[0] ? 1 : 0;
|
|
2311
|
-
},
|
|
2312
|
-
"path_match_has_err": () => matchHasErr,
|
|
2313
|
-
// bytes trim
|
|
2314
|
-
"bytes_trim": (s, cutset, left, right) => {
|
|
2315
|
-
const cs = new Set(Array.from(cutset ?? ""));
|
|
2316
|
-
const chars = Array.from(s ?? "");
|
|
2317
|
-
let a = 0, b = chars.length;
|
|
2318
|
-
if (left) while (a < b && cs.has(chars[a])) a++;
|
|
2319
|
-
if (right) while (b > a && cs.has(chars[b - 1])) b--;
|
|
2320
|
-
const enc = new TextEncoder();
|
|
2321
|
-
trimStart = a === 0 ? 0 : enc.encode(chars.slice(0, a).join("")).length;
|
|
2322
|
-
trimEnd = b === chars.length ? enc.encode(s ?? "").length : enc.encode(chars.slice(0, b).join("")).length;
|
|
2323
|
-
},
|
|
2324
|
-
"bytes_trim_start": () => trimStart,
|
|
2325
|
-
"bytes_trim_end": () => trimEnd,
|
|
2326
|
-
// time
|
|
2327
|
-
"time_now_ms": () => (typeof globalThis.__GOFRONT_CLOCK === "function" ? globalThis.__GOFRONT_CLOCK() : Date.now()),
|
|
2328
|
-
"time_timezone_offset": (ms) => -new Date(ms).getTimezoneOffset() * 60,
|
|
2329
|
-
"time_duration_string": (d) => __durationString(d),
|
|
2330
|
-
"time_parse": (layout, val) => {
|
|
2331
|
-
try {
|
|
2332
|
-
const d = new Date(val ?? "");
|
|
2333
|
-
if (isNaN(d.getTime())) throw 0;
|
|
2334
|
-
timeParseErr = null;
|
|
2335
|
-
timeParseOffset = -d.getTimezoneOffset() * 60;
|
|
2336
|
-
return d.getTime();
|
|
2337
|
-
} catch {
|
|
2338
|
-
timeParseErr = "parsing time: cannot parse";
|
|
2339
|
-
timeParseOffset = 0;
|
|
2340
|
-
return 0;
|
|
2341
|
-
}
|
|
2342
|
-
},
|
|
2343
|
-
"time_parse_err": () => timeParseErr,
|
|
2344
|
-
"time_parse_offset": () => timeParseOffset,
|
|
2345
|
-
"time_format": (y, m, d, hr, min, sec, wd, ms, offsetSec, layout) => {
|
|
2346
|
-
const DAYS = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];
|
|
2347
|
-
const MONTHS = ["January","February","March","April","May","June","July","August","September","October","November","December"];
|
|
2348
|
-
const pad = (n, w = 2) => String(n).padStart(w, "0");
|
|
2349
|
-
return (layout ?? "").replace(/2006|06|Monday|Mon|January|Jan|01|02|15|PM|pm|03|04|05|Z07:00|\\.(?:0+|9+)|1|2|3|4|5/g, t => {
|
|
2350
|
-
if (t.startsWith(".")) {
|
|
2351
|
-
const isZeroPad = t.includes("0");
|
|
2352
|
-
const maxLen = t.length - 1;
|
|
2353
|
-
let frac = pad(ms % 1000, 3);
|
|
2354
|
-
if (maxLen > 3) frac = frac.padEnd(maxLen, "0");
|
|
2355
|
-
else frac = frac.slice(0, maxLen);
|
|
2356
|
-
if (!isZeroPad) {
|
|
2357
|
-
frac = frac.replace(/0+$/, "");
|
|
2358
|
-
return frac.length > 0 ? "." + frac : "";
|
|
2359
|
-
}
|
|
2360
|
-
return "." + frac;
|
|
2361
|
-
}
|
|
2362
|
-
switch (t) {
|
|
2363
|
-
case "2006": return String(y);
|
|
2364
|
-
case "06": return String(y).slice(-2);
|
|
2365
|
-
case "Monday": return DAYS[wd];
|
|
2366
|
-
case "Mon": return DAYS[wd].slice(0, 3);
|
|
2367
|
-
case "January": return MONTHS[m - 1];
|
|
2368
|
-
case "Jan": return MONTHS[m - 1].slice(0, 3);
|
|
2369
|
-
case "01": return pad(m);
|
|
2370
|
-
case "1": return String(m);
|
|
2371
|
-
case "02": return pad(d);
|
|
2372
|
-
case "2": return String(d);
|
|
2373
|
-
case "15": return pad(hr);
|
|
2374
|
-
case "PM": return hr < 12 ? "AM" : "PM";
|
|
2375
|
-
case "pm": return hr < 12 ? "am" : "pm";
|
|
2376
|
-
case "03": return pad(hr % 12 || 12);
|
|
2377
|
-
case "3": return String(hr % 12 || 12);
|
|
2378
|
-
case "04": return pad(min);
|
|
2379
|
-
case "4": return String(min);
|
|
2380
|
-
case "05": return pad(sec);
|
|
2381
|
-
case "5": return String(sec);
|
|
2382
|
-
case "Z07:00": {
|
|
2383
|
-
const o = Math.floor(offsetSec / 60);
|
|
2384
|
-
return o === 0 ? "Z" : (o > 0 ? "+" : "-") + pad(Math.floor(Math.abs(o) / 60)) + ":" + pad(Math.abs(o) % 60);
|
|
2385
|
-
}
|
|
2386
|
-
default: return t;
|
|
2387
|
-
}
|
|
2388
|
-
});
|
|
2389
|
-
},
|
|
2390
|
-
"raw_bytes_init": (len) => {
|
|
2391
|
-
rawBytes = new Uint8Array(len);
|
|
2392
|
-
rawPos = 0;
|
|
2393
|
-
},
|
|
2394
|
-
"raw_bytes_push": (b) => {
|
|
2395
|
-
if (rawPos < rawBytes.length) {
|
|
2396
|
-
rawBytes[rawPos++] = b & 0xff;
|
|
2397
|
-
}
|
|
2398
|
-
},
|
|
2399
|
-
"raw_bytes_len": () => rawBytes.length,
|
|
2400
|
-
"raw_bytes_get": (i) => rawBytes[i] ?? 0,
|
|
2401
|
-
"crypto_rand_fill": (len) => {
|
|
2402
|
-
rawBytes = new Uint8Array(len);
|
|
2403
|
-
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
2404
|
-
const CHUNK = 65536;
|
|
2405
|
-
for (let offset = 0; offset < len; offset += CHUNK) {
|
|
2406
|
-
crypto.getRandomValues(rawBytes.subarray(offset, Math.min(offset + CHUNK, len)));
|
|
2407
|
-
}
|
|
2408
|
-
} else {
|
|
2409
|
-
for (let i = 0; i < len; i++) rawBytes[i] = Math.floor(Math.random() * 256);
|
|
2410
|
-
}
|
|
2411
|
-
return len;
|
|
2412
|
-
},
|
|
2413
|
-
"crypto_sha256_sum": () => {
|
|
2414
|
-
const d = __sha256.Sum256(rawBytes);
|
|
2415
|
-
rawBytes = new Uint8Array(d);
|
|
2416
|
-
return 32;
|
|
2417
|
-
},
|
|
2418
|
-
"hex_encode_to_string": () => {
|
|
2419
|
-
return __hex_encode_to_string(rawBytes);
|
|
2420
|
-
},
|
|
2421
|
-
"hex_decode_string": (s) => {
|
|
2422
|
-
const [res, err] = __hex_decode_string(s ?? "");
|
|
2423
|
-
hexErr = err ? (err.message ?? String(err)) : null;
|
|
2424
|
-
rawBytes = new Uint8Array(res);
|
|
2425
|
-
return rawBytes.length;
|
|
2426
|
-
},
|
|
2427
|
-
"hex_get_err": () => hexErr,
|
|
2428
|
-
"base64_new_encoding": (alphabet) => {
|
|
2429
|
-
const id = customBase64Encodings.length + 4;
|
|
2430
|
-
customBase64Encodings.push(__base64_make(alphabet ?? "", "="));
|
|
2431
|
-
return id;
|
|
2432
|
-
},
|
|
2433
|
-
"base64_encode_to_string": (mode) => {
|
|
2434
|
-
const enc = mode === 1 ? __base64_url : mode === 2 ? __base64_raw_std : mode === 3 ? __base64_raw_url : mode >= 4 ? customBase64Encodings[mode - 4] : __base64_std;
|
|
2435
|
-
return (enc ?? __base64_std).EncodeToString(rawBytes);
|
|
2436
|
-
},
|
|
2437
|
-
"base64_decode_string": (mode, s) => {
|
|
2438
|
-
const enc = mode === 1 ? __base64_url : mode === 2 ? __base64_raw_std : mode === 3 ? __base64_raw_url : mode >= 4 ? customBase64Encodings[mode - 4] : __base64_std;
|
|
2439
|
-
const [res, err] = (enc ?? __base64_std).DecodeString(s ?? "");
|
|
2440
|
-
base64Err = err ? (err.message ?? String(err)) : null;
|
|
2441
|
-
rawBytes = new Uint8Array(res);
|
|
2442
|
-
return rawBytes.length;
|
|
2443
|
-
},
|
|
2444
|
-
"base64_get_err": () => base64Err,
|
|
2067
|
+
// A partial last line (print / fmt.Print without a newline) is written when
|
|
2068
|
+
// the program ends; Node gets it without the newline console.log adds.
|
|
2069
|
+
const flushPartial = () => {
|
|
2070
|
+
if (lineBuf === "") return;
|
|
2071
|
+
const s = lineBuf;
|
|
2072
|
+
lineBuf = "";
|
|
2073
|
+
started = false;
|
|
2074
|
+
if (typeof process !== "undefined" && process?.stdout?.write) process.stdout.write(s);
|
|
2075
|
+
else write(s);
|
|
2445
2076
|
};
|
|
2077
|
+
return { "env": env, "Math": m, "__bind": (ex) => { late.exports = ex; }, "__flush": flushPartial };
|
|
2446
2078
|
}
|
|
2079
|
+
// string -> bytes: UTF-8, except that a lone surrogate U+DC80-U+DCFF (a raw
|
|
2080
|
+
// byte from a \\xNN escape in the source) encodes as that single byte.
|
|
2081
|
+
function ${P}u8enc(s) {
|
|
2082
|
+
const e = new TextEncoder();
|
|
2083
|
+
if (s.isWellFormed()) return e.encode(s);
|
|
2084
|
+
const out = [];
|
|
2085
|
+
for (let i = 0; i < s.length; i++) {
|
|
2086
|
+
const c = s.charCodeAt(i);
|
|
2087
|
+
if (c >= 0xdc80 && c <= 0xdcff && !(i > 0 && (s.charCodeAt(i - 1) & 0xfc00) === 0xd800)) { out.push(c - 0xdc00); continue; }
|
|
2088
|
+
const cp = s.codePointAt(i);
|
|
2089
|
+
if (cp > 0xffff) i++;
|
|
2090
|
+
for (const b of e.encode(String.fromCodePoint(cp))) out.push(b);
|
|
2091
|
+
}
|
|
2092
|
+
return Uint8Array.from(out);
|
|
2093
|
+
}
|
|
2094
|
+
// strings.ToUpper/ToLower: letter by letter through the first code point of
|
|
2095
|
+
// the host's mapping, as unicode.ToUpper/ToLower do; ASCII text maps whole.
|
|
2096
|
+
function ${P}mapCase(s, method) {
|
|
2097
|
+
if (/^[\\x00-\\x7f]*$/.test(s)) return s[method]();
|
|
2098
|
+
return Array.from(s, (c) => {
|
|
2099
|
+
const m = c[method]();
|
|
2100
|
+
const r = String.fromCodePoint(m.codePointAt(0));
|
|
2101
|
+
// A letter whose upper case spans several code points (ß) stays as is.
|
|
2102
|
+
return method === "toUpperCase" && m.length > r.length ? c : r;
|
|
2103
|
+
}).join("");
|
|
2104
|
+
}
|
|
2105
|
+
// Code-point-aware Trim/TrimLeft/TrimRight of strings.
|
|
2447
2106
|
function ${P}trim(s, cutset, left, right) {
|
|
2448
|
-
|
|
2449
|
-
const
|
|
2450
|
-
let
|
|
2451
|
-
if (left) while (
|
|
2452
|
-
|
|
2453
|
-
|
|
2107
|
+
if (s === "" || cutset === "") return s;
|
|
2108
|
+
const set = new Set(cutset);
|
|
2109
|
+
let i = 0, j = s.length;
|
|
2110
|
+
if (left) while (i < j) {
|
|
2111
|
+
const c = s.codePointAt(i);
|
|
2112
|
+
if (!set.has(String.fromCodePoint(c))) break;
|
|
2113
|
+
i += c > 0xFFFF ? 2 : 1;
|
|
2114
|
+
}
|
|
2115
|
+
if (right) while (j > i) {
|
|
2116
|
+
let k = j - 1;
|
|
2117
|
+
const lo = s.charCodeAt(k);
|
|
2118
|
+
if (lo >= 0xDC00 && lo <= 0xDFFF && k > i) {
|
|
2119
|
+
const hi = s.charCodeAt(k - 1);
|
|
2120
|
+
if (hi >= 0xD800 && hi <= 0xDBFF) k--;
|
|
2121
|
+
}
|
|
2122
|
+
if (!set.has(s.slice(k, j))) break;
|
|
2123
|
+
j = k;
|
|
2124
|
+
}
|
|
2125
|
+
return s.slice(i, j);
|
|
2126
|
+
}
|
|
2127
|
+
// strings.Replace: an empty old string matches before every code point and at
|
|
2128
|
+
// the end; the replacement is literal.
|
|
2129
|
+
function ${P}replace(s, o, w, n) {
|
|
2130
|
+
if (o === w || n === 0) return s;
|
|
2131
|
+
if (o !== "") {
|
|
2132
|
+
if (n < 0) return s.replaceAll(o, () => w);
|
|
2133
|
+
let r = "", i = 0;
|
|
2134
|
+
for (let k = n; k > 0; k--) {
|
|
2135
|
+
const j = s.indexOf(o, i);
|
|
2136
|
+
if (j < 0) break;
|
|
2137
|
+
r += s.slice(i, j) + w;
|
|
2138
|
+
i = j + o.length;
|
|
2139
|
+
}
|
|
2140
|
+
return r + s.slice(i);
|
|
2141
|
+
}
|
|
2142
|
+
const cs = [...s];
|
|
2143
|
+
const m = n < 0 || n > cs.length + 1 ? cs.length + 1 : n;
|
|
2144
|
+
let r = "";
|
|
2145
|
+
for (let k = 0; k < m; k++) r += (k > 0 ? cs[k - 1] : "") + w;
|
|
2146
|
+
return r + cs.slice(m - 1).join("");
|
|
2454
2147
|
}`;
|
|
2455
2148
|
|
|
2456
2149
|
// Runtime loader emitted at the top of the facade.
|
|
2457
|
-
const wasmLoaderJs =
|
|
2458
|
-
${stdlibEnv ? WASM_STDLIB_JS : ""}
|
|
2150
|
+
const wasmLoaderJs = `${WASM_IMPORTS_JS}
|
|
2459
2151
|
async function ${P}fetch(url) {
|
|
2460
2152
|
const res = await fetch(url);
|
|
2461
2153
|
if (!res.ok) throw new Error("GoFront: failed to fetch " + url + " (" + res.status + ")");
|
|
@@ -2472,17 +2164,19 @@ async function ${P}instantiate(imports) {
|
|
|
2472
2164
|
}
|
|
2473
2165
|
return WebAssembly.instantiate(await ${P}fetch(url), imports);
|
|
2474
2166
|
}
|
|
2167
|
+
let ${P}flushOut = () => {};
|
|
2475
2168
|
async function ${P}load(stringTable, extraEnv) {
|
|
2476
2169
|
if (typeof WebAssembly === "undefined" || typeof WebAssembly.Tag !== "function") {
|
|
2477
2170
|
throw new Error("GoFront: this runtime lacks the WebAssembly GC / exception support required by app.wasm");
|
|
2478
2171
|
}
|
|
2479
2172
|
const tag = new WebAssembly.Tag({ "parameters": ["externref"] });
|
|
2480
|
-
const imports = ${P}imports(stringTable, extraEnv, tag, (s) => console.log(s));
|
|
2173
|
+
const imports = ${P}imports(stringTable, extraEnv, tag, (s) => console.log(s), true);
|
|
2481
2174
|
// Panics arrive as plain JS Errors thrown by env.panic (also from the
|
|
2482
2175
|
// start function while package-level initializers run), so exports are
|
|
2483
2176
|
// returned raw: no try/catch wrapper, which keeps JS→wasm calls inlinable.
|
|
2484
2177
|
const result = await ${P}instantiate(imports);
|
|
2485
2178
|
imports.__bind(result.instance.exports);
|
|
2179
|
+
${P}flushOut = imports.__flush;
|
|
2486
2180
|
return result.instance.exports;
|
|
2487
2181
|
}
|
|
2488
2182
|
// Go int/int64 cross the boundary as f64 (exact within the safe-integer range).
|
|
@@ -2536,32 +2230,63 @@ function ${P}idxview(n, getAt, setAt) {
|
|
|
2536
2230
|
}`;
|
|
2537
2231
|
|
|
2538
2232
|
// Generates the JS facade for the linked wasm module.
|
|
2233
|
+
// A facade binding for a Go name that shadows a browser global carries the
|
|
2234
|
+
// same `$` suffix as JS output uses (see jsBindingName in backend/js), so the
|
|
2235
|
+
// flat bundle keeps reaching the real global.
|
|
2236
|
+
function facadeName(name) {
|
|
2237
|
+
return BROWSER_GLOBALS.has(name) ? `${name}$` : name;
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2539
2240
|
export function generateFacade(
|
|
2540
2241
|
meta,
|
|
2541
|
-
{ stringTable = [], callMain = false
|
|
2242
|
+
{ stringTable = [], callMain = false } = {},
|
|
2542
2243
|
) {
|
|
2543
2244
|
const needs = meta._needs ?? collectNeeds(meta);
|
|
2544
|
-
const out = [wasmLoaderJs
|
|
2245
|
+
const out = [wasmLoaderJs];
|
|
2545
2246
|
|
|
2546
2247
|
// JS callback imports (registered before instantiation; bodies run after).
|
|
2547
2248
|
out.push(`const ${P}env = {};`);
|
|
2548
2249
|
for (const [key, desc] of needs.funcs) {
|
|
2549
2250
|
const names = desc.params.map((_, i) => `a${i}`);
|
|
2550
|
-
const
|
|
2551
|
-
isPtrBoth(d) ? `${P}view_${d.name}(${names[i]})` : outExpr(d, names[i]),
|
|
2552
|
-
);
|
|
2553
|
-
const call = `fn(${args.join(", ")})`;
|
|
2251
|
+
const hasPtrBox = desc.params.some((d) => d.k === "ptr_box");
|
|
2554
2252
|
let body;
|
|
2555
|
-
if (
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2253
|
+
if (hasPtrBox) {
|
|
2254
|
+
const argVars = desc.params.map((_, i) => `__arg${i}`);
|
|
2255
|
+
const decls = desc.params.map(
|
|
2256
|
+
(d, i) =>
|
|
2257
|
+
`const __arg${i} = ${isPtrBoth(d) ? `${P}view_${d.name}(${names[i]})` : outExpr(d, names[i])};`,
|
|
2258
|
+
);
|
|
2259
|
+
const call = `fn(${argVars.join(", ")})`;
|
|
2260
|
+
const writebacks = desc.params
|
|
2261
|
+
.map((d, i) =>
|
|
2262
|
+
d.k === "ptr_box"
|
|
2263
|
+
? `if (__arg${i} && __arg${i}.value !== undefined) __w.__box_set_${d.key}(${names[i]}, ${inExpr(d.base, `__arg${i}.value`)});`
|
|
2264
|
+
: "",
|
|
2265
|
+
)
|
|
2266
|
+
.filter(Boolean);
|
|
2267
|
+
if (desc.returns.length === 0) {
|
|
2268
|
+
body = `${decls.join(" ")} ${call}; ${writebacks.join(" ")}`;
|
|
2269
|
+
} else if (desc.returns.length === 1) {
|
|
2270
|
+
body = `${decls.join(" ")} const __r = ${call}; ${writebacks.join(" ")} return ${inExpr(desc.returns[0], "__r")};`;
|
|
2271
|
+
} else {
|
|
2272
|
+
body = `${decls.join(" ")} const __r = ${call}; ${writebacks.join(" ")} return [${desc.returns.map((d, i) => inExpr(d, `__r[${i}]`)).join(", ")}];`;
|
|
2273
|
+
}
|
|
2274
|
+
} else {
|
|
2275
|
+
const args = desc.params.map((d, i) =>
|
|
2276
|
+
isPtrBoth(d) ? `${P}view_${d.name}(${names[i]})` : outExpr(d, names[i]),
|
|
2277
|
+
);
|
|
2278
|
+
const call = `fn(${args.join(", ")})`;
|
|
2279
|
+
if (desc.returns.length === 0) body = `${call};`;
|
|
2280
|
+
else if (desc.returns.length === 1)
|
|
2281
|
+
body = `return ${inExpr(desc.returns[0], call)};`;
|
|
2282
|
+
else
|
|
2283
|
+
body = `const __r = ${call}; return [${desc.returns
|
|
2284
|
+
.map((d, i) => inExpr(d, `__r[${i}]`))
|
|
2285
|
+
.join(", ")}];`;
|
|
2286
|
+
}
|
|
2562
2287
|
const sig = ["fn", ...names].join(", ");
|
|
2563
2288
|
out.push(
|
|
2564
|
-
`${P}env["__invoke$${key}"] = (${sig}) => { try { ${body} } catch (__e) { if (__w && __w.__push_panic) __w.__push_panic(__e.message ?? String(__e)); throw __e; } };`,
|
|
2289
|
+
`${P}env["__invoke$${key}"] = (${sig}) => { try { ${body} } catch (__e) { if (__w && __w.__push_panic && !(__e && __e.__gfp === true)) { __w.__push_panic(__e.message ?? String(__e)); try { __e.__gfp = true; } catch {} } throw __e; } };`,
|
|
2565
2290
|
);
|
|
2566
2291
|
}
|
|
2567
2292
|
out.push(
|
|
@@ -2572,9 +2297,11 @@ export function generateFacade(
|
|
|
2572
2297
|
if (info.pkgTarget === "both") genBothStruct(out, name, fields);
|
|
2573
2298
|
else {
|
|
2574
2299
|
const methods = meta.structByName.get(name)?.methods ?? [];
|
|
2575
|
-
|
|
2300
|
+
const promoted = promotedMethods(info, fields, methods, meta);
|
|
2301
|
+
genHandleStruct(out, name, fields, methods, promoted);
|
|
2576
2302
|
}
|
|
2577
2303
|
}
|
|
2304
|
+
genGenericClasses(out, needs, meta._mod);
|
|
2578
2305
|
|
|
2579
2306
|
for (const [name, desc] of needs.anonStructs) {
|
|
2580
2307
|
out.push(`function ${P}to_${name}(o) {
|
|
@@ -2596,12 +2323,17 @@ ${desc.fields.map((f) => `\to.${f.name} = ${outExpr(f.desc, `__w.__get_${name}_$
|
|
|
2596
2323
|
for (const [key, desc] of needs.boxes) {
|
|
2597
2324
|
out.push(`function ${P}boxin_${key}(b) {
|
|
2598
2325
|
if (b == null) return null;
|
|
2326
|
+
if (b.__ref) return b.__ref;
|
|
2599
2327
|
const v = b.value !== undefined ? b.value : ${zeroExpr(desc.base)};
|
|
2600
2328
|
return __w.__box_new_${key}(${inExpr(desc.base, "v")});
|
|
2601
2329
|
}
|
|
2602
2330
|
function ${P}boxout_${key}(r) {
|
|
2603
2331
|
if (r == null) return null;
|
|
2604
|
-
return {
|
|
2332
|
+
return {
|
|
2333
|
+
__ref: r,
|
|
2334
|
+
get value() { return ${outExpr(desc.base, `__w.__box_get_${key}(r)`)}; },
|
|
2335
|
+
set value(v) { __w.__box_set_${key}(r, ${inExpr(desc.base, "v")}); }
|
|
2336
|
+
};
|
|
2605
2337
|
}
|
|
2606
2338
|
function ${P}boxback_${key}(r, b) {
|
|
2607
2339
|
if (r == null || b == null) return;
|
|
@@ -2610,6 +2342,27 @@ function ${P}boxback_${key}(r, b) {
|
|
|
2610
2342
|
}
|
|
2611
2343
|
|
|
2612
2344
|
for (const [key, desc] of needs.slices) {
|
|
2345
|
+
if (desc.elem.k === "f32x4") {
|
|
2346
|
+
// []F32x4 travels as one Float32Array of 4n lanes.
|
|
2347
|
+
out.push(`function ${P}slin_${key}(arr) {
|
|
2348
|
+
if (arr == null) return __w.__slice_nil_${key}();
|
|
2349
|
+
const n = arr.length >> 2;
|
|
2350
|
+
const s = __w.__slice_new_${key}(n);
|
|
2351
|
+
for (let i = 0; i < n; i++) __w.__slice_set_${key}(s, i, ${P}f32x4in(arr.subarray ? arr.subarray(4 * i, 4 * i + 4) : arr.slice(4 * i, 4 * i + 4)));
|
|
2352
|
+
return s;
|
|
2353
|
+
}
|
|
2354
|
+
function ${P}slout_${key}(s) {
|
|
2355
|
+
if (s == null || __w.__slice_is_nil_${key}(s)) return null;
|
|
2356
|
+
const n = __w.__slice_len_${key}(s);
|
|
2357
|
+
const out = new Float32Array(4 * n);
|
|
2358
|
+
for (let i = 0; i < n; i++) out.set(${P}f32x4out(__w.__slice_get_${key}(s, i)), 4 * i);
|
|
2359
|
+
return out;
|
|
2360
|
+
}
|
|
2361
|
+
function ${P}slview_${key}(s) {
|
|
2362
|
+
return ${P}slout_${key}(s);
|
|
2363
|
+
}`);
|
|
2364
|
+
continue;
|
|
2365
|
+
}
|
|
2613
2366
|
out.push(`function ${P}slin_${key}(arr) {
|
|
2614
2367
|
if (arr == null) return __w.__slice_nil_${key}();
|
|
2615
2368
|
const n = arr.length;
|
|
@@ -2630,6 +2383,15 @@ function ${P}slview_${key}(s) {
|
|
|
2630
2383
|
} `);
|
|
2631
2384
|
}
|
|
2632
2385
|
|
|
2386
|
+
if (needs.maps.size > 0) {
|
|
2387
|
+
// GoFront JS code reads a map as a plain object (`m["a"]`, `len(m)`), so
|
|
2388
|
+
// each entry is also an own property of the returned Map unless that name
|
|
2389
|
+
// is one the Map already has.
|
|
2390
|
+
out.push(`function ${P}mapprop(m, k, v) {
|
|
2391
|
+
const p = String(k);
|
|
2392
|
+
if (!(p in m)) m[p] = v;
|
|
2393
|
+
}`);
|
|
2394
|
+
}
|
|
2633
2395
|
for (const [key, desc] of needs.maps) {
|
|
2634
2396
|
out.push(`function ${P}mapin_${key}(m) {
|
|
2635
2397
|
if (m == null) return null;
|
|
@@ -2649,12 +2411,41 @@ function ${P}mapout_${key}(m) {
|
|
|
2649
2411
|
if (ks) {
|
|
2650
2412
|
for (let i = 0; i < ks.length; i++) {
|
|
2651
2413
|
out.set(ks[i], vs[i]);
|
|
2414
|
+
${P}mapprop(out, ks[i], vs[i]);
|
|
2652
2415
|
}
|
|
2653
2416
|
}
|
|
2654
2417
|
return out;
|
|
2655
2418
|
}`);
|
|
2656
2419
|
}
|
|
2657
2420
|
|
|
2421
|
+
if (needs.simd) {
|
|
2422
|
+
const k = needs.simd.key;
|
|
2423
|
+
out.push(`function ${P}v4in(words) {
|
|
2424
|
+
const a = __w.__array_new_${k}(4);
|
|
2425
|
+
for (let i = 0; i < 4; i++) __w.__array_set_${k}(a, i, words[i]);
|
|
2426
|
+
return a;
|
|
2427
|
+
}
|
|
2428
|
+
function ${P}v4out(a) {
|
|
2429
|
+
const words = new Int32Array(4);
|
|
2430
|
+
for (let i = 0; i < 4; i++) words[i] = __w.__array_get_${k}(a, i);
|
|
2431
|
+
return words;
|
|
2432
|
+
}
|
|
2433
|
+
function ${P}f32x4in(v) {
|
|
2434
|
+
return ${P}v4in(new Int32Array(new Float32Array(v ?? 4).buffer));
|
|
2435
|
+
}
|
|
2436
|
+
function ${P}f32x4out(a) {
|
|
2437
|
+
return new Float32Array(${P}v4out(a).buffer);
|
|
2438
|
+
}
|
|
2439
|
+
function ${P}mask4in(v) {
|
|
2440
|
+
const w = new Int32Array(4);
|
|
2441
|
+
if (v) for (let i = 0; i < 4; i++) w[i] = v[i] ? -1 : 0;
|
|
2442
|
+
return ${P}v4in(w);
|
|
2443
|
+
}
|
|
2444
|
+
function ${P}mask4out(a) {
|
|
2445
|
+
return Array.from(${P}v4out(a), (w) => w !== 0);
|
|
2446
|
+
}`);
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2658
2449
|
for (const [key, desc] of needs.arrays) {
|
|
2659
2450
|
out.push(`function ${P}arrzero_${key}(n) {
|
|
2660
2451
|
const out = new Array(n);
|
|
@@ -2748,28 +2539,6 @@ function ${P}errin(e) {
|
|
|
2748
2539
|
}`);
|
|
2749
2540
|
}
|
|
2750
2541
|
|
|
2751
|
-
if (needs.time) {
|
|
2752
|
-
out.push(`function ${P}timeout(r) {
|
|
2753
|
-
if (r == null) return null;
|
|
2754
|
-
const ms = __w.__time_unix_milli ? __w.__time_unix_milli(r) : 0;
|
|
2755
|
-
const off = __w.__time_offset ? __w.__time_offset(r) : 0;
|
|
2756
|
-
return { _d: new Date(ms), _offset: off };
|
|
2757
|
-
}
|
|
2758
|
-
function ${P}timeout_ptr(r) {
|
|
2759
|
-
return ${P}timeout(r);
|
|
2760
|
-
}
|
|
2761
|
-
function ${P}timein(v) {
|
|
2762
|
-
if (v == null) return null;
|
|
2763
|
-
const d = v._d ? v._d : (v instanceof Date ? v : new Date(v));
|
|
2764
|
-
const ms = d.getTime();
|
|
2765
|
-
const off = typeof v?._offset === "number" ? v._offset : -d.getTimezoneOffset() * 60;
|
|
2766
|
-
return __w.__time_make ? __w.__time_make(ms, off) : null;
|
|
2767
|
-
}
|
|
2768
|
-
function ${P}timein_ptr(v) {
|
|
2769
|
-
return ${P}timein(v);
|
|
2770
|
-
}`);
|
|
2771
|
-
}
|
|
2772
|
-
|
|
2773
2542
|
if (needs.shared) {
|
|
2774
2543
|
// Zero-copy TypedArray views over the exported memory. Memory never
|
|
2775
2544
|
// grows after the start function (allocation is startup-only), but the
|
|
@@ -2794,15 +2563,17 @@ function ${P}shin(v, Ctor) {
|
|
|
2794
2563
|
for (const c of meta.consts) {
|
|
2795
2564
|
if (c.dynamic) {
|
|
2796
2565
|
out.push(
|
|
2797
|
-
`const ${c.name} = ${outExpr(c.desc, `__w.__const_${c.name}()`)};`,
|
|
2566
|
+
`const ${facadeName(c.name)} = ${outExpr(c.desc, `__w.__const_${c.name}()`)};`,
|
|
2798
2567
|
);
|
|
2799
2568
|
} else {
|
|
2800
|
-
out.push(`const ${c.name} = ${c.value};`);
|
|
2569
|
+
out.push(`const ${facadeName(c.name)} = ${c.value};`);
|
|
2801
2570
|
}
|
|
2802
2571
|
}
|
|
2803
2572
|
for (const v of meta.vars ?? [])
|
|
2804
2573
|
out.push(
|
|
2805
|
-
|
|
2574
|
+
v.shared
|
|
2575
|
+
? `const ${facadeName(v.name)} = ${P}shout(__w.__var_${v.name}(), ${v.desc.ctor});`
|
|
2576
|
+
: `const ${facadeName(v.name)} = ${outExpr(v.desc, `__w.__var_${v.name}()`)};`,
|
|
2806
2577
|
);
|
|
2807
2578
|
|
|
2808
2579
|
for (const f of meta.funcs) {
|
|
@@ -2811,13 +2582,22 @@ function ${P}shin(v, Ctor) {
|
|
|
2811
2582
|
f.params,
|
|
2812
2583
|
f.returns,
|
|
2813
2584
|
);
|
|
2814
|
-
out.push(
|
|
2585
|
+
out.push(
|
|
2586
|
+
`function ${facadeName(f.name)}(${restParams(names, f.variadic)}) {\n${body}\n}`,
|
|
2587
|
+
);
|
|
2815
2588
|
}
|
|
2816
2589
|
|
|
2817
|
-
if (callMain) out.push(
|
|
2590
|
+
if (callMain) out.push(`try { __w.main(); } finally { ${P}flushOut(); }`);
|
|
2818
2591
|
return bindExports(out.join("\n"));
|
|
2819
2592
|
}
|
|
2820
2593
|
|
|
2594
|
+
// A variadic Go function is a slice parameter in wasm; JS callers pass the
|
|
2595
|
+
// trailing arguments spread, so the facade collects them with a rest param.
|
|
2596
|
+
function restParams(names, variadic) {
|
|
2597
|
+
if (!variadic || names.length === 0) return names.join(", ");
|
|
2598
|
+
return [...names.slice(0, -1), `...${names.at(-1)}`].join(", ");
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2821
2601
|
// Rewrites `__w.name(...)` into calls through module-level `const` bindings.
|
|
2822
2602
|
// V8 only inlines a JS→wasm call (and so passes f32/f64 arguments unboxed)
|
|
2823
2603
|
// when the callee is a compile-time constant; a property load off the exports
|
|
@@ -2858,8 +2638,26 @@ function genBothStruct(out, name, fields) {
|
|
|
2858
2638
|
const got = `__w.__get_${name}_${f.name}(this.__ref)`;
|
|
2859
2639
|
return `"${f.name}": { "enumerable": true, "get"() { return ${viewExpr(f.desc, got)}; }, "set"(v) { __w.__set_${name}_${f.name}(this.__ref, ${inExpr(f.desc, "v")}); } }`;
|
|
2860
2640
|
});
|
|
2641
|
+
// The one conversion `time.Time` has beyond the ordinary struct rules: JS
|
|
2642
|
+
// code may pass a Date (or an object holding one in `_d`), and a Time
|
|
2643
|
+
// handed to JS also reads as a Date through `_d`.
|
|
2644
|
+
const isTime = name === "Time$$time";
|
|
2645
|
+
if (isTime)
|
|
2646
|
+
out.push(`function ${P}time_in(o) {
|
|
2647
|
+
const d = o instanceof Date ? o : o?._d instanceof Date ? o._d : null;
|
|
2648
|
+
if (d === null) return o;
|
|
2649
|
+
const ms = d.getTime();
|
|
2650
|
+
const off = typeof o._offset === "number" ? o._offset : -d.getTimezoneOffset() * 60;
|
|
2651
|
+
const sec = Math.floor(ms / 1000);
|
|
2652
|
+
return new Time$$time(BigInt(sec) + 62135596800n, (ms - sec * 1000) * 1000000, new Location$$time("", off, 0));
|
|
2653
|
+
}
|
|
2654
|
+
function ${P}time_out(t) {
|
|
2655
|
+
Object.defineProperty(t, "_d", { configurable: true, get() { return new Date(Number(this.sec - 62135596800n) * 1000 + Math.floor(this.nsec / 1000000)); } });
|
|
2656
|
+
Object.defineProperty(t, "_offset", { configurable: true, get() { return this.loc ? this.loc.offset : 0; } });
|
|
2657
|
+
return t;
|
|
2658
|
+
}`);
|
|
2861
2659
|
out.push(`function ${P}to_${name}(o, isValue, m) {
|
|
2862
|
-
if (o == null) return isValue ? __w.__zero_${name}() : null;
|
|
2660
|
+
${isTime ? `\to = ${P}time_in(o);\n` : ""} if (o == null) return isValue ? __w.__zero_${name}() : null;
|
|
2863
2661
|
if (m) { const hit = m.get(o); if (hit) return hit; }
|
|
2864
2662
|
const r = __w.__new_${name}(${toArgs.join(", ")});
|
|
2865
2663
|
if (m) m.set(o, r);
|
|
@@ -2867,7 +2665,7 @@ function genBothStruct(out, name, fields) {
|
|
|
2867
2665
|
}
|
|
2868
2666
|
function ${P}from_${name}(r) {
|
|
2869
2667
|
if (r == null) return null;
|
|
2870
|
-
return new ${name}(${fromArgs.join(", ")});
|
|
2668
|
+
return ${isTime ? `${P}time_out(new ${name}(${fromArgs.join(", ")}))` : `new ${name}(${fromArgs.join(", ")})`};
|
|
2871
2669
|
}
|
|
2872
2670
|
// Live views share one prototype and are cached per wasm object so hot paths
|
|
2873
2671
|
// (\`h.Pos.X = 1\` in a frame loop) do not allocate a wrapper per access. The
|
|
@@ -2926,7 +2724,84 @@ function ${P}ifin_${key}(v) {
|
|
|
2926
2724
|
}`);
|
|
2927
2725
|
}
|
|
2928
2726
|
|
|
2929
|
-
|
|
2727
|
+
// Methods a struct gets from its embedded structs, shallowest first (a name
|
|
2728
|
+
// found twice at one depth is ambiguous and left out). Each is forwarded
|
|
2729
|
+
// through the embedded field's getter: `{ name, via, params, variadic }`.
|
|
2730
|
+
function promotedMethods(info, fields, methods, meta) {
|
|
2731
|
+
const taken = new Set([
|
|
2732
|
+
...methods.map((m) => m.name),
|
|
2733
|
+
...fields.map((f) => f.name),
|
|
2734
|
+
]);
|
|
2735
|
+
const promoted = [];
|
|
2736
|
+
const seen = new Set([info]);
|
|
2737
|
+
let level = [{ info, via: "" }];
|
|
2738
|
+
while (level.length > 0) {
|
|
2739
|
+
const next = [];
|
|
2740
|
+
const found = new Map();
|
|
2741
|
+
for (const { info: owner, via } of level) {
|
|
2742
|
+
for (const e of owner.embeds ?? []) {
|
|
2743
|
+
const typeName = e.typeName ?? e.name;
|
|
2744
|
+
const path = `${via}.${e.name}`;
|
|
2745
|
+
const inner = meta._mod.getStructType(typeName);
|
|
2746
|
+
const innerMethods =
|
|
2747
|
+
meta.structByName.get(typeName.replaceAll(".", "$"))?.methods ?? [];
|
|
2748
|
+
for (const m of innerMethods) {
|
|
2749
|
+
if (!found.has(m.name)) found.set(m.name, []);
|
|
2750
|
+
found.get(m.name).push({
|
|
2751
|
+
name: m.name,
|
|
2752
|
+
via: path,
|
|
2753
|
+
params: m.params,
|
|
2754
|
+
variadic: m.variadic,
|
|
2755
|
+
});
|
|
2756
|
+
}
|
|
2757
|
+
if (inner && !seen.has(inner)) {
|
|
2758
|
+
seen.add(inner);
|
|
2759
|
+
next.push({ info: inner, via: path });
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
for (const [n, c] of found) {
|
|
2764
|
+
if (!taken.has(n) && c.length === 1) promoted.push(c[0]);
|
|
2765
|
+
taken.add(n);
|
|
2766
|
+
}
|
|
2767
|
+
level = next;
|
|
2768
|
+
}
|
|
2769
|
+
return promoted;
|
|
2770
|
+
}
|
|
2771
|
+
|
|
2772
|
+
// A generic struct is built in a js main as `new Box(V, [...zeroFactories,
|
|
2773
|
+
// "int"])`; the facade only has the instances (`Box$int`). `Box` picks the
|
|
2774
|
+
// instance named by the last type-argument entry (the printed type arguments).
|
|
2775
|
+
function genGenericClasses(out, needs, mod) {
|
|
2776
|
+
const groups = new Map();
|
|
2777
|
+
for (const [name, { info }] of needs.structs) {
|
|
2778
|
+
if (info.pkgTarget === "both") continue;
|
|
2779
|
+
const goName = mod?.checker?.types?.get(info.name)?._goName;
|
|
2780
|
+
const m = goName ? /^([^[]+)\[(.*)\]$/s.exec(goName) : null;
|
|
2781
|
+
if (!m) continue;
|
|
2782
|
+
if (!groups.has(m[1])) groups.set(m[1], new Map());
|
|
2783
|
+
// The js caller keys instances by the unqualified printed arguments.
|
|
2784
|
+
groups
|
|
2785
|
+
.get(m[1])
|
|
2786
|
+
.set(m[2].replaceAll(" ", "").replace(/(?<![\w.])main\./g, ""), name);
|
|
2787
|
+
}
|
|
2788
|
+
for (const [base, instances] of groups) {
|
|
2789
|
+
const table = [...instances]
|
|
2790
|
+
.map(([id, cls]) => `${JSON.stringify(id)}: ${cls}`)
|
|
2791
|
+
.join(", ");
|
|
2792
|
+
out.push(`class ${facadeName(base)} {
|
|
2793
|
+
constructor(...a) {
|
|
2794
|
+
const tz = a.at(-1);
|
|
2795
|
+
const id = Array.isArray(tz) && typeof tz.at(-1) === "string" ? tz.at(-1).replaceAll(" ", "") : undefined;
|
|
2796
|
+
const C = id === undefined ? ${instances.size === 1 ? `${[...instances.values()][0]}` : "undefined"} : { ${table} }[id];
|
|
2797
|
+
if (!C) throw new TypeError("GoFront: no ${base} instance for the type arguments " + id + " in the wasm package");
|
|
2798
|
+
return new C(...(Array.isArray(tz) ? a.slice(0, -1) : a));
|
|
2799
|
+
}
|
|
2800
|
+
}`);
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2804
|
+
function genHandleStruct(out, name, fields, methods, promoted = []) {
|
|
2930
2805
|
// `$`-suffixed params keep the mangler from renaming the matching getters.
|
|
2931
2806
|
const ctorParams = fields.map((f) => `${f.name}$ = ${zeroExpr(f.desc)}`);
|
|
2932
2807
|
const ctorArgs = fields.map((f) => inExpr(f.desc, `${f.name}$`));
|
|
@@ -2974,7 +2849,13 @@ class ${name} {
|
|
|
2974
2849
|
[recv],
|
|
2975
2850
|
);
|
|
2976
2851
|
lines.push(
|
|
2977
|
-
`\t${m.name}(${names
|
|
2852
|
+
`\t${m.name}(${restParams(names, m.variadic)}) {\n${indent(body, "\t")}\n\t}`,
|
|
2853
|
+
);
|
|
2854
|
+
}
|
|
2855
|
+
for (const m of promoted) {
|
|
2856
|
+
const names = m.params.map((_, i) => `a${i}`);
|
|
2857
|
+
lines.push(
|
|
2858
|
+
`\t${m.name}(${restParams(names, m.variadic)}) { return this${m.via}.${m.name}(${restParams(names, m.variadic)}); }`,
|
|
2978
2859
|
);
|
|
2979
2860
|
}
|
|
2980
2861
|
lines.push("}");
|