gofront 1.5.1 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +174 -466
- package/package.json +16 -20
- package/src/backend/js/expressions.js +288 -128
- package/src/backend/js/index.js +138 -56
- package/src/backend/js/runtime.js +1055 -53
- package/src/backend/js/source-map.js +32 -0
- package/src/backend/js/statements.js +151 -83
- package/src/backend/js/stdlib/base64.js +12 -0
- package/src/backend/js/stdlib/binary.js +102 -0
- package/src/backend/js/stdlib/bits.js +15 -1
- package/src/backend/js/stdlib/builder.js +28 -9
- package/src/backend/js/stdlib/bytes.js +75 -18
- package/src/backend/js/stdlib/errors.js +22 -3
- package/src/backend/js/stdlib/fmt.js +95 -20
- package/src/backend/js/stdlib/hex.js +17 -0
- package/src/backend/js/stdlib/html.js +1 -1
- package/src/backend/js/stdlib/index.js +19 -0
- package/src/backend/js/stdlib/io.js +105 -3
- package/src/backend/js/stdlib/maps.js +1 -1
- package/src/backend/js/stdlib/math.js +13 -1
- package/src/backend/js/stdlib/path.js +20 -6
- package/src/backend/js/stdlib/rand.js +10 -5
- package/src/backend/js/stdlib/sha256.js +12 -0
- package/src/backend/js/stdlib/shared.js +17 -0
- package/src/backend/js/stdlib/slices.js +53 -27
- package/src/backend/js/stdlib/sort.js +1 -1
- package/src/backend/js/stdlib/strconv.js +52 -11
- package/src/backend/js/stdlib/strings.js +152 -16
- package/src/backend/js/stdlib/sync.js +13 -0
- package/src/backend/js/stdlib/testing.js +2 -2
- package/src/backend/js/stdlib/time.js +93 -22
- package/src/backend/js/stdlib/unicode.js +2 -2
- package/src/backend/js/templ.js +1 -1
- package/src/backend/wasm/boundary.js +1677 -58
- package/src/backend/wasm/const-fold.js +205 -0
- package/src/backend/wasm/emit-builtins.js +1230 -0
- package/src/backend/wasm/emit-exprs.js +2096 -0
- package/src/backend/wasm/emit-maps.js +1999 -0
- package/src/backend/wasm/emit-shared.js +519 -0
- package/src/backend/wasm/emit-stdlib.js +13 -0
- package/src/backend/wasm/emit-stmts.js +2466 -0
- package/src/backend/wasm/emit.js +656 -5031
- package/src/backend/wasm/encode.js +259 -85
- package/src/backend/wasm/glue.js +19 -5
- package/src/backend/wasm/index.js +2432 -86
- package/src/backend/wasm/monomorph.js +711 -0
- package/src/backend/wasm/optimize.js +236 -0
- package/src/backend/wasm/stdlib/bytes.js +5039 -0
- package/src/backend/wasm/stdlib/core.js +8903 -0
- package/src/backend/wasm/stdlib/index.js +40 -0
- package/src/backend/wasm/stdlib/io.js +2161 -0
- package/src/backend/wasm/stdlib/packages.js +2400 -0
- package/src/backend/wasm/stdlib/pure.js +720 -0
- package/src/backend/wasm/stdlib/time.js +1200 -0
- package/src/backend/wasm/types.js +312 -36
- package/src/backend/wasm/wat.js +49 -2
- package/src/cli-core.js +276 -73
- package/src/compiler.js +162 -25
- package/src/dts-parser.js +79 -126
- package/src/index.js +88 -121
- package/src/lexer.js +80 -29
- package/src/lower/boxing.js +4 -6
- package/src/lower/captures.js +12 -23
- package/src/lower/escape.js +4 -9
- package/src/lower/functions.js +277 -5
- package/src/lower/globals.js +7 -15
- package/src/lower/index.js +5 -2
- package/src/lower/ownership.js +35 -19
- package/src/lower/range.js +2 -5
- package/src/lower/walk.js +25 -0
- package/src/minifier.js +20 -68
- package/src/parser/declarations.js +3 -6
- package/src/parser/expressions.js +3 -8
- package/src/parser/index.js +18 -3
- package/src/parser/statements.js +25 -38
- package/src/parser/types.js +1 -2
- package/src/resolver.js +3 -27
- package/src/templ-lexer.js +29 -63
- package/src/templ-parser.js +22 -45
- package/src/test-runner.js +20 -0
- package/src/typechecker/assignability.js +123 -14
- package/src/typechecker/expressions.js +244 -4
- package/src/typechecker/index.js +27 -1
- package/src/typechecker/resolve.js +71 -12
- package/src/typechecker/statements.js +0 -7
- package/src/typechecker/stdlib/core.js +292 -103
- package/src/typechecker/stdlib/extended.js +471 -4
- package/src/typechecker/stdlib/shared.js +53 -0
- package/src/typechecker/stdlib.js +2 -0
- package/src/typechecker/types.js +545 -8
- package/src/vendor.js +2 -2
|
@@ -20,12 +20,36 @@
|
|
|
20
20
|
// TypedArray inputs accepted). No copy-back for slices.
|
|
21
21
|
// - func values: JS callbacks wrapped into wasm closures via thunks; wasm
|
|
22
22
|
// closures exposed to JS as callable functions.
|
|
23
|
-
// -
|
|
23
|
+
// - named non-empty interfaces (Task H5.2): wasm values reach JS as their
|
|
24
|
+
// handle class / live view when the dynamic type is an exported `*T`,
|
|
25
|
+
// otherwise as a facade whose methods call the wasm dispatchers. Only
|
|
26
|
+
// wasm-owned values (`__ref`) go back in; JS-implemented values are
|
|
27
|
+
// rejected (no cross-boundary itab proxies).
|
|
28
|
+
// - maps, anonymous interfaces, error, pointers to non-structs: rejected
|
|
24
29
|
// with a compile error (planned for a later boundary version).
|
|
30
|
+
// - `gofront/shared` buffers (Task H6.1): `(base, len)` views over the
|
|
31
|
+
// module's exported linear memory; JS sees a zero-copy TypedArray over
|
|
32
|
+
// `memory.buffer`. Only TypedArrays that already alias that memory (ones
|
|
33
|
+
// handed out by this facade) may be passed back in.
|
|
25
34
|
// - `*testing.T`: the harness's JS test object, passed as an opaque
|
|
26
35
|
// externref (test methods are routed back to JS via `env.testing_*`).
|
|
27
36
|
|
|
28
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
HELPER_BASE64,
|
|
39
|
+
HELPER_DURATION,
|
|
40
|
+
HELPER_ERROR,
|
|
41
|
+
HELPER_HEX,
|
|
42
|
+
HELPER_PATH_CLEAN,
|
|
43
|
+
HELPER_SHA256,
|
|
44
|
+
HELPER_SPRINTF,
|
|
45
|
+
HELPER_STRCONV,
|
|
46
|
+
} from "../js/runtime.js";
|
|
47
|
+
import {
|
|
48
|
+
anonIfaceName,
|
|
49
|
+
getInterfaceMethodList,
|
|
50
|
+
getSharedInfo,
|
|
51
|
+
isTestingT,
|
|
52
|
+
} from "./types.js";
|
|
29
53
|
|
|
30
54
|
// ── Type classification ──────────────────────────────────────
|
|
31
55
|
|
|
@@ -79,11 +103,19 @@ function funcSigFromAst(t) {
|
|
|
79
103
|
|
|
80
104
|
// Classifies a Go type (AST node or checker type) for boundary marshalling.
|
|
81
105
|
// Returns a descriptor `{ k, ... }`; `k === "unsupported"` carries `what`.
|
|
82
|
-
|
|
106
|
+
function classifyType(goType, mod) {
|
|
83
107
|
if (!goType) return { k: "void" };
|
|
84
108
|
if (isTestingT(goType)) return { k: "extern", wType: "externref" };
|
|
85
109
|
|
|
86
110
|
if (goType.kind === "TypeName" || goType.kind === "Ident") {
|
|
111
|
+
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
|
+
}
|
|
87
119
|
const info = mod.getStructType(goType.name);
|
|
88
120
|
if (info) return structDesc(info, false);
|
|
89
121
|
const resolved = mod.checker?.types?.get(goType.name);
|
|
@@ -95,6 +127,8 @@ export function classifyType(goType, mod) {
|
|
|
95
127
|
switch (t.kind) {
|
|
96
128
|
case "named":
|
|
97
129
|
return classifyNamed(t, mod);
|
|
130
|
+
case "shared":
|
|
131
|
+
return classifyShared(t, mod);
|
|
98
132
|
case "untyped":
|
|
99
133
|
return classifyBasic(t.base);
|
|
100
134
|
case "basic":
|
|
@@ -105,10 +139,14 @@ export function classifyType(goType, mod) {
|
|
|
105
139
|
return classifySlice(t, mod);
|
|
106
140
|
case "array":
|
|
107
141
|
return classifyArray(t, mod);
|
|
142
|
+
case "map":
|
|
143
|
+
return classifyMap(t, mod);
|
|
144
|
+
case "struct":
|
|
145
|
+
return classifyStruct(t, mod);
|
|
108
146
|
case "func":
|
|
109
147
|
return classifyFunc(t, mod);
|
|
110
148
|
case "interface":
|
|
111
|
-
return classifyInterface(t);
|
|
149
|
+
return classifyInterface(t, mod);
|
|
112
150
|
default:
|
|
113
151
|
return {
|
|
114
152
|
k: "unsupported",
|
|
@@ -118,11 +156,6 @@ export function classifyType(goType, mod) {
|
|
|
118
156
|
}
|
|
119
157
|
|
|
120
158
|
const UNSUPPORTED_KINDS = {
|
|
121
|
-
map: "map",
|
|
122
|
-
MapType: "map",
|
|
123
|
-
struct: "anonymous struct",
|
|
124
|
-
StructType: "anonymous struct",
|
|
125
|
-
InterfaceType: "interface",
|
|
126
159
|
ChanType: "chan",
|
|
127
160
|
chan: "chan",
|
|
128
161
|
};
|
|
@@ -137,13 +170,21 @@ function normalizeAstType(t) {
|
|
|
137
170
|
return { kind: "slice", elem: t.elem };
|
|
138
171
|
case "ArrayType":
|
|
139
172
|
return { kind: "array", elem: t.elem, size: t.size };
|
|
173
|
+
case "MapType":
|
|
174
|
+
return { kind: "map", key: t.key, val: t.value ?? t.elem };
|
|
140
175
|
case "FuncType":
|
|
141
176
|
return funcSigFromAst(t);
|
|
142
177
|
case "InterfaceType":
|
|
143
178
|
return {
|
|
144
179
|
kind: "interface",
|
|
145
180
|
name: t.name,
|
|
146
|
-
methods:
|
|
181
|
+
methods: t.methods,
|
|
182
|
+
};
|
|
183
|
+
case "StructType":
|
|
184
|
+
return {
|
|
185
|
+
kind: "struct",
|
|
186
|
+
name: t.name,
|
|
187
|
+
fields: t.fields,
|
|
147
188
|
};
|
|
148
189
|
default:
|
|
149
190
|
return t;
|
|
@@ -157,7 +198,7 @@ const BASIC_DESCS = {
|
|
|
157
198
|
float64: { k: "f64", wType: "f64" },
|
|
158
199
|
string: { k: "string", wType: "externref" },
|
|
159
200
|
any: { k: "any", wType: "anyref" },
|
|
160
|
-
error: { k: "
|
|
201
|
+
error: { k: "error", wType: "anyref" },
|
|
161
202
|
};
|
|
162
203
|
|
|
163
204
|
function classifyBasic(n) {
|
|
@@ -170,17 +211,122 @@ function classifyBasic(n) {
|
|
|
170
211
|
}
|
|
171
212
|
|
|
172
213
|
function classifyNamed(t, mod) {
|
|
214
|
+
if (t.name === "error") return { k: "error", wType: "anyref" };
|
|
215
|
+
if (t.name === "time.Duration")
|
|
216
|
+
return { k: "i64", unsigned: false, wType: "i64" };
|
|
217
|
+
if (t.name === "time.Time") {
|
|
218
|
+
const info = mod.getStructType("time.Time");
|
|
219
|
+
if (info) return { k: "time", name: "time.Time", info, wType: toRef(info) };
|
|
220
|
+
}
|
|
173
221
|
const info = mod.getStructType(t.name);
|
|
174
222
|
if (info) return structDesc(info, false);
|
|
175
223
|
if (t.underlying) return classifyType(t.underlying, mod);
|
|
176
224
|
return { k: "unsupported", what: `named type '${t.name}'` };
|
|
177
225
|
}
|
|
178
226
|
|
|
227
|
+
function classifyAnonStruct(t, mod) {
|
|
228
|
+
const rawFields = [];
|
|
229
|
+
if (t.fields instanceof Map) {
|
|
230
|
+
for (const [k, v] of t.fields.entries())
|
|
231
|
+
rawFields.push({ name: k, goType: v });
|
|
232
|
+
} else if (Array.isArray(t.fields)) {
|
|
233
|
+
for (const f of t.fields) {
|
|
234
|
+
const names = f.names ?? (f.name ? [f.name] : []);
|
|
235
|
+
for (const n of names) rawFields.push({ name: n, goType: f.type });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const info = mod.getAnonStructType(rawFields);
|
|
239
|
+
const fields = [];
|
|
240
|
+
for (const f of info.fields) {
|
|
241
|
+
const fd = classifyType(f.goType, mod);
|
|
242
|
+
if (fd.k === "unsupported") return fd;
|
|
243
|
+
fields.push({ name: f.name, desc: fd });
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
k: "anon_struct",
|
|
247
|
+
name: info.name,
|
|
248
|
+
key: info.name,
|
|
249
|
+
info,
|
|
250
|
+
fields,
|
|
251
|
+
wType: toRef(info),
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function classifyStruct(t, mod) {
|
|
256
|
+
if (t.name) {
|
|
257
|
+
const info = mod.getStructType(t.name);
|
|
258
|
+
if (info) return structDesc(info, false);
|
|
259
|
+
}
|
|
260
|
+
return classifyAnonStruct(t, mod);
|
|
261
|
+
}
|
|
262
|
+
|
|
179
263
|
function classifyPointer(t, mod) {
|
|
180
264
|
const base = classifyType(t.base, mod);
|
|
265
|
+
if (base.k === "unsupported") return base;
|
|
181
266
|
if (base.k === "struct" && !base.ptr) return { ...base, ptr: true };
|
|
182
|
-
|
|
183
|
-
|
|
267
|
+
if (base.k === "anon_struct" && !base.ptr) return { ...base, ptr: true };
|
|
268
|
+
if (base.k === "time" && !base.ptr) return { ...base, ptr: true };
|
|
269
|
+
const boxInfo = mod.getBoxType(t.base);
|
|
270
|
+
const key = `${base.k}${base.unsigned ? "_u" : ""}_${boxInfo.typeIndex}`;
|
|
271
|
+
return {
|
|
272
|
+
k: "ptr_box",
|
|
273
|
+
base,
|
|
274
|
+
boxInfo,
|
|
275
|
+
key,
|
|
276
|
+
wType: { kind: "ref", nullable: true, typeIndex: boxInfo.typeIndex },
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function isAllowedMapKey(kDesc) {
|
|
281
|
+
if (kDesc.k === "string" || kDesc.k === "bool") return true;
|
|
282
|
+
if (
|
|
283
|
+
kDesc.k === "i32" ||
|
|
284
|
+
kDesc.k === "i64" ||
|
|
285
|
+
kDesc.k === "f32" ||
|
|
286
|
+
kDesc.k === "f64"
|
|
287
|
+
)
|
|
288
|
+
return true;
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function classifyMap(t, mod) {
|
|
293
|
+
const keyType = t.key;
|
|
294
|
+
const valType = t.val ?? t.value ?? t.elem;
|
|
295
|
+
const keyDesc = classifyType(keyType, mod);
|
|
296
|
+
if (keyDesc.k === "unsupported") return keyDesc;
|
|
297
|
+
const valDesc = classifyType(valType, mod);
|
|
298
|
+
if (valDesc.k === "unsupported") return valDesc;
|
|
299
|
+
if (!isAllowedMapKey(keyDesc)) {
|
|
300
|
+
return {
|
|
301
|
+
k: "unsupported",
|
|
302
|
+
what: `map with unsupported key type '${keyType?.name ?? keyDesc.k}'`,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
const mapInfo = mod.getMapType(keyType, valType);
|
|
306
|
+
const keySliceDesc = classifySlice({ kind: "slice", elem: keyType }, mod);
|
|
307
|
+
const valSliceDesc = classifySlice({ kind: "slice", elem: valType }, mod);
|
|
308
|
+
const key = `m_${mapInfo.suffix}`;
|
|
309
|
+
return {
|
|
310
|
+
k: "map",
|
|
311
|
+
key,
|
|
312
|
+
mapInfo,
|
|
313
|
+
keyDesc,
|
|
314
|
+
valDesc,
|
|
315
|
+
keySliceDesc,
|
|
316
|
+
valSliceDesc,
|
|
317
|
+
wType: mapInfo.wType,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function classifyShared(t, mod) {
|
|
322
|
+
const info = getSharedInfo(t, mod.checker);
|
|
323
|
+
return {
|
|
324
|
+
k: "shared",
|
|
325
|
+
key: info.key,
|
|
326
|
+
ctor: info.ctor,
|
|
327
|
+
bytes: info.bytes,
|
|
328
|
+
wType: mod.getSharedType().wType,
|
|
329
|
+
};
|
|
184
330
|
}
|
|
185
331
|
|
|
186
332
|
function classifySlice(t, mod) {
|
|
@@ -216,31 +362,104 @@ function classifyArray(t, mod) {
|
|
|
216
362
|
};
|
|
217
363
|
}
|
|
218
364
|
|
|
219
|
-
function
|
|
365
|
+
function classifySig(t, mod) {
|
|
220
366
|
const params = (t.params ?? []).map((p) => classifyType(p, mod));
|
|
221
367
|
const returns = (t.returns ?? [])
|
|
222
368
|
.map((r) => classifyType(r, mod))
|
|
223
369
|
.filter((d) => d.k !== "void");
|
|
224
370
|
const bad = [...params, ...returns].find((d) => d.k === "unsupported");
|
|
225
|
-
|
|
371
|
+
return bad ?? { params, returns };
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function classifyFunc(t, mod) {
|
|
375
|
+
const sig = classifySig(t, mod);
|
|
376
|
+
if (sig.k === "unsupported") return sig;
|
|
226
377
|
const closureInfo = mod.getClosureType(t);
|
|
227
378
|
return {
|
|
228
379
|
k: "func",
|
|
229
|
-
params,
|
|
230
|
-
returns,
|
|
380
|
+
params: sig.params,
|
|
381
|
+
returns: sig.returns,
|
|
231
382
|
closureInfo,
|
|
232
383
|
key: `c${closureInfo.typeIndex}`,
|
|
233
384
|
wType: { kind: "ref", nullable: true, typeIndex: closureInfo.typeIndex },
|
|
234
385
|
};
|
|
235
386
|
}
|
|
236
387
|
|
|
237
|
-
function classifyInterface(t) {
|
|
238
|
-
if (t.name === "error") return { k: "
|
|
239
|
-
|
|
240
|
-
return {
|
|
241
|
-
|
|
242
|
-
|
|
388
|
+
function classifyInterface(t, mod) {
|
|
389
|
+
if (t.name === "error") return { k: "error", wType: "anyref" };
|
|
390
|
+
const mList = getInterfaceMethodList(t);
|
|
391
|
+
if (mList.length === 0) return { k: "any", wType: "anyref" };
|
|
392
|
+
mod._boundaryIfaces ??= new Map();
|
|
393
|
+
if (t.name && t.methods instanceof Map) {
|
|
394
|
+
return mod._boundaryIfaces.get(t.name) ?? classifyNamedInterface(t, mod);
|
|
395
|
+
}
|
|
396
|
+
return classifyAnonInterface(t, mod);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function classifyAnonInterface(t, mod) {
|
|
400
|
+
const key = anonIfaceName(t, mod.checker, mod);
|
|
401
|
+
if (mod._boundaryIfaces.has(key)) return mod._boundaryIfaces.get(key);
|
|
402
|
+
const mList = getInterfaceMethodList(t);
|
|
403
|
+
const desc = {
|
|
404
|
+
k: "iface",
|
|
405
|
+
name: key,
|
|
406
|
+
key,
|
|
407
|
+
wType: "anyref",
|
|
408
|
+
methods: [],
|
|
409
|
+
concrete: [],
|
|
410
|
+
isAnon: true,
|
|
243
411
|
};
|
|
412
|
+
mod._boundaryIfaces.set(key, desc);
|
|
413
|
+
for (const m of mList) {
|
|
414
|
+
const sig = classifySig(
|
|
415
|
+
{ kind: "func", params: m.params, returns: m.returns },
|
|
416
|
+
mod,
|
|
417
|
+
);
|
|
418
|
+
if (sig.k === "unsupported") {
|
|
419
|
+
const err = {
|
|
420
|
+
k: "unsupported",
|
|
421
|
+
what: `${sig.what} (in anonymous interface method ${m.name})`,
|
|
422
|
+
};
|
|
423
|
+
mod._boundaryIfaces.set(key, err);
|
|
424
|
+
return err;
|
|
425
|
+
}
|
|
426
|
+
desc.methods.push({
|
|
427
|
+
name: m.name,
|
|
428
|
+
dispatch: `__dispatch_${key}_${m.name}`,
|
|
429
|
+
...sig,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
return desc;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function classifyNamedInterface(t, mod) {
|
|
436
|
+
const desc = {
|
|
437
|
+
k: "iface",
|
|
438
|
+
name: t.name,
|
|
439
|
+
key: t.name,
|
|
440
|
+
wType: "anyref",
|
|
441
|
+
methods: [],
|
|
442
|
+
concrete: [],
|
|
443
|
+
};
|
|
444
|
+
// Cached before the walk so self-referencing method signatures terminate.
|
|
445
|
+
mod._boundaryIfaces.set(t.name, desc);
|
|
446
|
+
for (const [mName, sigType] of t.methods) {
|
|
447
|
+
const sig = classifySig(sigType, mod);
|
|
448
|
+
if (sig.k === "unsupported") {
|
|
449
|
+
const err = {
|
|
450
|
+
k: "unsupported",
|
|
451
|
+
what: `${sig.what} (in method ${t.name}.${mName})`,
|
|
452
|
+
};
|
|
453
|
+
mod._boundaryIfaces.set(t.name, err);
|
|
454
|
+
return err;
|
|
455
|
+
}
|
|
456
|
+
desc.methods.push({
|
|
457
|
+
name: mName,
|
|
458
|
+
dispatch: `__dispatch_${t.name}_${mName}`,
|
|
459
|
+
...sig,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
return desc;
|
|
244
463
|
}
|
|
245
464
|
|
|
246
465
|
// ── Export metadata ──────────────────────────────────────────
|
|
@@ -267,7 +486,14 @@ function literalConst(spec, i) {
|
|
|
267
486
|
}
|
|
268
487
|
if (node?.kind !== "BasicLit") return null;
|
|
269
488
|
switch (node.litKind) {
|
|
270
|
-
case "INT":
|
|
489
|
+
case "INT": {
|
|
490
|
+
const raw = String(node.value);
|
|
491
|
+
const clean = /^0[0-7]+$/.test(raw) ? `0o${raw.slice(1)}` : raw;
|
|
492
|
+
const bi = BigInt(`${sign}${clean}`);
|
|
493
|
+
return bi > 9007199254740991n || bi < -9007199254740991n
|
|
494
|
+
? `${sign}${clean}n`
|
|
495
|
+
: `${sign}${clean}`;
|
|
496
|
+
}
|
|
271
497
|
case "FLOAT":
|
|
272
498
|
return `${sign}${node.value}`;
|
|
273
499
|
case "STRING":
|
|
@@ -291,6 +517,7 @@ export function collectBoundaryMeta(progs, funcDecls, mod) {
|
|
|
291
517
|
structs: [],
|
|
292
518
|
funcs: [],
|
|
293
519
|
consts: [],
|
|
520
|
+
vars: [], // exported package-level shared buffers of wasm packages
|
|
294
521
|
structByName: new Map(),
|
|
295
522
|
_mod: mod,
|
|
296
523
|
};
|
|
@@ -304,6 +531,8 @@ export function collectBoundaryMeta(progs, funcDecls, mod) {
|
|
|
304
531
|
collectStructDecl(d, pkgName, pkgTarget, meta, mod);
|
|
305
532
|
} else if (d.kind === "ConstDecl" && pkgTarget === "wasm") {
|
|
306
533
|
collectConstDecl(d, pkgName, meta, errors);
|
|
534
|
+
} else if (d.kind === "VarDecl" && pkgTarget === "wasm") {
|
|
535
|
+
collectSharedVarDecl(d, meta, mod);
|
|
307
536
|
}
|
|
308
537
|
}
|
|
309
538
|
}
|
|
@@ -333,14 +562,48 @@ function collectConstDecl(d, pkgName, meta, errors) {
|
|
|
333
562
|
const name = spec.names[i];
|
|
334
563
|
if (!isExported(name)) continue;
|
|
335
564
|
const value = literalConst(spec, i);
|
|
336
|
-
if (value !== null)
|
|
337
|
-
|
|
565
|
+
if (value !== null) {
|
|
566
|
+
meta.consts.push({ name, value, dynamic: false });
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
const folded = meta._mod.constCache?.get(name);
|
|
570
|
+
if (folded) {
|
|
571
|
+
let val = null;
|
|
572
|
+
if (folded.litKind === "INT") {
|
|
573
|
+
const s = String(folded.value);
|
|
574
|
+
const clean = /^0[0-7]+$/.test(s) ? `0o${s.slice(1)}` : s;
|
|
575
|
+
const bi = BigInt(clean);
|
|
576
|
+
val =
|
|
577
|
+
bi > 9007199254740991n || bi < -9007199254740991n
|
|
578
|
+
? `${clean}n`
|
|
579
|
+
: clean;
|
|
580
|
+
} else if (folded.litKind === "FLOAT") {
|
|
581
|
+
val = folded.value;
|
|
582
|
+
} else if (folded.litKind === "STRING") {
|
|
583
|
+
val = JSON.stringify(String(folded.value));
|
|
584
|
+
} else if (folded.litKind === "BOOL") {
|
|
585
|
+
val = folded.value === "true" ? "true" : "false";
|
|
586
|
+
}
|
|
587
|
+
if (val !== null) {
|
|
588
|
+
meta.consts.push({ name, value: val, dynamic: false });
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
const glob = meta._mod.resolveGlobal(name);
|
|
593
|
+
const rawType = spec.type ?? spec.value?.[i]?._type ?? glob?.goType;
|
|
594
|
+
let desc = rawType ? classifyType(rawType, meta._mod) : null;
|
|
595
|
+
if (!desc || desc.k === "void") {
|
|
596
|
+
if (glob?.type) {
|
|
597
|
+
desc = { k: glob.type, wType: glob.type };
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
if (!desc || desc.k === "unsupported") {
|
|
338
601
|
errors.push(
|
|
339
|
-
unsupportedMsg(
|
|
340
|
-
`${pkgName}.${name}`,
|
|
341
|
-
"a constant with a non-literal value",
|
|
342
|
-
),
|
|
602
|
+
unsupportedMsg(`${pkgName}.${name}`, desc?.what ?? "unsupported"),
|
|
343
603
|
);
|
|
604
|
+
} else {
|
|
605
|
+
meta.consts.push({ name, dynamic: true, desc });
|
|
606
|
+
}
|
|
344
607
|
}
|
|
345
608
|
}
|
|
346
609
|
}
|
|
@@ -350,6 +613,22 @@ function checkDesc(desc, where, errors) {
|
|
|
350
613
|
return desc;
|
|
351
614
|
}
|
|
352
615
|
|
|
616
|
+
// Exported `var Buf = shared.NewFloat32(n)` globals are exposed to JS as
|
|
617
|
+
// TypedArray views (read once after instantiation; sizing is fixed).
|
|
618
|
+
function collectSharedVarDecl(d, meta, mod) {
|
|
619
|
+
for (const spec of d.decls ?? d.specs ?? [d]) {
|
|
620
|
+
const names = spec.names ?? (spec.name ? [spec.name] : []);
|
|
621
|
+
const values = spec.value ?? (spec.init ? [spec.init] : []);
|
|
622
|
+
for (let i = 0; i < names.length; i++) {
|
|
623
|
+
const name = names[i];
|
|
624
|
+
if (!isExported(name)) continue;
|
|
625
|
+
const goType = spec.type ?? values[i]?._type;
|
|
626
|
+
if (!getSharedInfo(goType, mod.checker)) continue;
|
|
627
|
+
meta.vars.push({ name, desc: classifyType(goType, mod) });
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
353
632
|
function collectMethodMeta(fn, meta, errors, mod) {
|
|
354
633
|
if (!isExported(fn._methodName)) return;
|
|
355
634
|
const s = meta.structByName.get(fn._recvTypeName);
|
|
@@ -358,6 +637,7 @@ function collectMethodMeta(fn, meta, errors, mod) {
|
|
|
358
637
|
s.methods.push({
|
|
359
638
|
name: fn._methodName,
|
|
360
639
|
exportName: fn._exportName,
|
|
640
|
+
fnName: fn.name,
|
|
361
641
|
ptrRecv: fn.params[0].type?.kind === "pointer",
|
|
362
642
|
params: fn.params
|
|
363
643
|
.slice(1)
|
|
@@ -405,6 +685,17 @@ function visitNeed(desc, needs, errors, mod) {
|
|
|
405
685
|
case "struct":
|
|
406
686
|
visitStructNeed(desc, needs, errors, mod);
|
|
407
687
|
return;
|
|
688
|
+
case "anon_struct":
|
|
689
|
+
if (needs.anonStructs.has(desc.name)) return;
|
|
690
|
+
needs.anonStructs.set(desc.name, desc);
|
|
691
|
+
for (const f of desc.fields) visitNeed(f.desc, needs, errors, mod);
|
|
692
|
+
return;
|
|
693
|
+
case "error":
|
|
694
|
+
needs.error = true;
|
|
695
|
+
return;
|
|
696
|
+
case "time":
|
|
697
|
+
needs.time = true;
|
|
698
|
+
return;
|
|
408
699
|
case "slice":
|
|
409
700
|
case "array": {
|
|
410
701
|
const map = desc.k === "slice" ? needs.slices : needs.arrays;
|
|
@@ -413,16 +704,55 @@ function visitNeed(desc, needs, errors, mod) {
|
|
|
413
704
|
visitNeed(desc.elem, needs, errors, mod);
|
|
414
705
|
return;
|
|
415
706
|
}
|
|
707
|
+
case "map":
|
|
708
|
+
if (needs.maps.has(desc.key)) return;
|
|
709
|
+
needs.maps.set(desc.key, desc);
|
|
710
|
+
visitNeed(desc.keyDesc, needs, errors, mod);
|
|
711
|
+
visitNeed(desc.valDesc, needs, errors, mod);
|
|
712
|
+
visitNeed(desc.keySliceDesc, needs, errors, mod);
|
|
713
|
+
visitNeed(desc.valSliceDesc, needs, errors, mod);
|
|
714
|
+
return;
|
|
715
|
+
case "ptr_box":
|
|
716
|
+
if (needs.boxes.has(desc.key)) return;
|
|
717
|
+
needs.boxes.set(desc.key, desc);
|
|
718
|
+
visitNeed(desc.base, needs, errors, mod);
|
|
719
|
+
return;
|
|
416
720
|
case "func":
|
|
417
721
|
if (needs.funcs.has(desc.key)) return;
|
|
418
722
|
needs.funcs.set(desc.key, desc);
|
|
419
723
|
visitSignatureNeeds(desc, needs, errors, mod);
|
|
420
724
|
return;
|
|
725
|
+
case "iface":
|
|
726
|
+
if (needs.ifaces.has(desc.key)) return;
|
|
727
|
+
needs.ifaces.set(desc.key, desc);
|
|
728
|
+
for (const m of desc.methods) visitSignatureNeeds(m, needs, errors, mod);
|
|
729
|
+
return;
|
|
730
|
+
case "shared":
|
|
731
|
+
needs.shared = true;
|
|
732
|
+
return;
|
|
421
733
|
default:
|
|
422
734
|
return;
|
|
423
735
|
}
|
|
424
736
|
}
|
|
425
737
|
|
|
738
|
+
// Exported `*T` types (already on the boundary surface) whose JS class exposes
|
|
739
|
+
// every interface method: such values reach JS as the handle / live view
|
|
740
|
+
// itself, so type switches and identity still work. `__itag` matches by
|
|
741
|
+
// `ref.test`, so a struct is only chosen for values that really hold it.
|
|
742
|
+
function concreteCandidates(desc, needs, meta, mod) {
|
|
743
|
+
const out = [];
|
|
744
|
+
for (const [name, { info }] of needs.structs) {
|
|
745
|
+
const both = info.pkgTarget === "both";
|
|
746
|
+
const names = both
|
|
747
|
+
? [...(mod.checker?.types?.get(name)?.underlying?.methods?.keys() ?? [])]
|
|
748
|
+
: (meta.structByName.get(name)?.methods ?? []).map((m) => m.name);
|
|
749
|
+
const have = new Set(names);
|
|
750
|
+
if (!desc.methods.every((m) => have.has(m.name))) continue;
|
|
751
|
+
out.push({ name, typeIndex: info.typeIndex, both });
|
|
752
|
+
}
|
|
753
|
+
return out;
|
|
754
|
+
}
|
|
755
|
+
|
|
426
756
|
function visitSignatureNeeds(sig, needs, errors, mod) {
|
|
427
757
|
for (const p of sig.params) visitNeed(p, needs, errors, mod);
|
|
428
758
|
for (const r of sig.returns) visitNeed(r, needs, errors, mod);
|
|
@@ -434,9 +764,16 @@ function collectNeeds(meta) {
|
|
|
434
764
|
const mod = meta._mod;
|
|
435
765
|
const needs = {
|
|
436
766
|
structs: new Map(), // name -> { info, fields: [{ name, desc }] }
|
|
767
|
+
anonStructs: new Map(), // name -> desc
|
|
437
768
|
slices: new Map(), // key -> desc
|
|
438
769
|
arrays: new Map(), // key -> desc
|
|
770
|
+
maps: new Map(), // key -> desc
|
|
771
|
+
boxes: new Map(), // key -> desc
|
|
439
772
|
funcs: new Map(), // key -> desc
|
|
773
|
+
ifaces: new Map(), // interface name -> desc
|
|
774
|
+
error: false,
|
|
775
|
+
time: false,
|
|
776
|
+
shared: false, // any shared buffer on the surface
|
|
440
777
|
};
|
|
441
778
|
const errors = [];
|
|
442
779
|
for (const s of meta.structs) {
|
|
@@ -445,7 +782,13 @@ function collectNeeds(meta) {
|
|
|
445
782
|
for (const m of s.methods) visitSignatureNeeds(m, needs, errors, mod);
|
|
446
783
|
}
|
|
447
784
|
for (const f of meta.funcs) visitSignatureNeeds(f, needs, errors, mod);
|
|
785
|
+
for (const v of meta.vars ?? []) visitNeed(v.desc, needs, errors, mod);
|
|
786
|
+
for (const c of meta.consts ?? []) {
|
|
787
|
+
if (c.dynamic) visitNeed(c.desc, needs, errors, mod);
|
|
788
|
+
}
|
|
448
789
|
if (errors.length > 0) throw new Error([...new Set(errors)].join("\n"));
|
|
790
|
+
for (const desc of needs.ifaces.values())
|
|
791
|
+
desc.concrete = concreteCandidates(desc, needs, meta, mod);
|
|
449
792
|
return needs;
|
|
450
793
|
}
|
|
451
794
|
|
|
@@ -800,7 +1143,8 @@ export function addBoundaryHelpers(mod, meta) {
|
|
|
800
1143
|
// get a trampoline (`__x_<export>`) with the boundary signature; the facade
|
|
801
1144
|
// calls that instead of the raw export.
|
|
802
1145
|
const addTrampoline = (entry) => {
|
|
803
|
-
|
|
1146
|
+
// Methods are keyed `T.M` in funcMap but exported as `T_M`.
|
|
1147
|
+
const fnIdx = mod.funcMap.get(entry.fnName ?? entry.exportName);
|
|
804
1148
|
if (fnIdx === undefined) return;
|
|
805
1149
|
const sig = typeAt(mod, mod.funcs[fnIdx - importFuncCount]?.typeIndex);
|
|
806
1150
|
if (sig?.form !== "func") return;
|
|
@@ -823,6 +1167,326 @@ export function addBoundaryHelpers(mod, meta) {
|
|
|
823
1167
|
};
|
|
824
1168
|
for (const s of meta.structs) for (const m of s.methods) addTrampoline(m);
|
|
825
1169
|
for (const f of meta.funcs) addTrampoline(f);
|
|
1170
|
+
for (const desc of needs.ifaces.values())
|
|
1171
|
+
addIfaceHelpers(desc, mod, addFunc, addTrampoline);
|
|
1172
|
+
|
|
1173
|
+
if (needs.shared) {
|
|
1174
|
+
const { wType, typeIndex } = mod.getSharedType();
|
|
1175
|
+
addFunc(
|
|
1176
|
+
"__shared_base",
|
|
1177
|
+
[EXT],
|
|
1178
|
+
["i32"],
|
|
1179
|
+
[...getIn(0, wType), { op: "struct.get", typeIndex, fieldIndex: 0 }],
|
|
1180
|
+
);
|
|
1181
|
+
addFunc(
|
|
1182
|
+
"__shared_len",
|
|
1183
|
+
[EXT],
|
|
1184
|
+
["i32"],
|
|
1185
|
+
[...getIn(0, wType), { op: "struct.get", typeIndex, fieldIndex: 1 }],
|
|
1186
|
+
);
|
|
1187
|
+
// JS-side views (e.g. a `subarray`) go back in as a fresh (base, len).
|
|
1188
|
+
addFunc(
|
|
1189
|
+
"__shared_make",
|
|
1190
|
+
["i32", "i32"],
|
|
1191
|
+
[EXT],
|
|
1192
|
+
[get(0), get(1), { op: "struct.new", typeIndex }, ...toExt(wType)],
|
|
1193
|
+
);
|
|
1194
|
+
for (const v of meta.vars ?? []) {
|
|
1195
|
+
const g = mod.resolveGlobal(v.name);
|
|
1196
|
+
if (!g) continue;
|
|
1197
|
+
addFunc(
|
|
1198
|
+
`__var_${v.name}`,
|
|
1199
|
+
[],
|
|
1200
|
+
[EXT],
|
|
1201
|
+
[{ op: "global.get", index: g.index }, ...toExt(wType)],
|
|
1202
|
+
);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
for (const [name, desc] of needs.anonStructs) {
|
|
1207
|
+
const { info } = desc;
|
|
1208
|
+
const ref = desc.wType;
|
|
1209
|
+
const ti = info.typeIndex;
|
|
1210
|
+
const fieldWTypes = info.fields.map((f) => f.wType);
|
|
1211
|
+
addFunc(
|
|
1212
|
+
`__new_${name}`,
|
|
1213
|
+
fieldWTypes.map(extType),
|
|
1214
|
+
[EXT],
|
|
1215
|
+
[
|
|
1216
|
+
...fieldWTypes.flatMap((w, i) => getIn(i, w)),
|
|
1217
|
+
{ op: "struct.new", typeIndex: ti },
|
|
1218
|
+
...toExt(ref),
|
|
1219
|
+
],
|
|
1220
|
+
);
|
|
1221
|
+
addFunc(
|
|
1222
|
+
`__zero_${name}`,
|
|
1223
|
+
[],
|
|
1224
|
+
[EXT],
|
|
1225
|
+
[
|
|
1226
|
+
...fieldWTypes.map((w) => zeroInstr(w)),
|
|
1227
|
+
{ op: "struct.new", typeIndex: ti },
|
|
1228
|
+
...toExt(ref),
|
|
1229
|
+
],
|
|
1230
|
+
);
|
|
1231
|
+
for (let i = 0; i < info.fields.length; i++) {
|
|
1232
|
+
const f = info.fields[i];
|
|
1233
|
+
addFunc(
|
|
1234
|
+
`__get_${name}_${f.name}`,
|
|
1235
|
+
[EXT],
|
|
1236
|
+
[extType(f.wType)],
|
|
1237
|
+
[
|
|
1238
|
+
...getIn(0, ref),
|
|
1239
|
+
{ op: "struct.get", typeIndex: ti, fieldIndex: i },
|
|
1240
|
+
...toExt(f.wType),
|
|
1241
|
+
],
|
|
1242
|
+
);
|
|
1243
|
+
addFunc(
|
|
1244
|
+
`__set_${name}_${f.name}`,
|
|
1245
|
+
[EXT, extType(f.wType)],
|
|
1246
|
+
[],
|
|
1247
|
+
[
|
|
1248
|
+
...getIn(0, ref),
|
|
1249
|
+
...getIn(1, f.wType),
|
|
1250
|
+
{ op: "struct.set", typeIndex: ti, fieldIndex: i },
|
|
1251
|
+
],
|
|
1252
|
+
);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
for (const [key, desc] of needs.boxes) {
|
|
1257
|
+
const { boxInfo } = desc;
|
|
1258
|
+
const ref = desc.wType;
|
|
1259
|
+
const ti = boxInfo.typeIndex;
|
|
1260
|
+
const w = boxInfo.wType;
|
|
1261
|
+
addFunc(
|
|
1262
|
+
`__box_new_${key}`,
|
|
1263
|
+
[extType(w)],
|
|
1264
|
+
[EXT],
|
|
1265
|
+
[...getIn(0, w), { op: "struct.new", typeIndex: ti }, ...toExt(ref)],
|
|
1266
|
+
);
|
|
1267
|
+
addFunc(
|
|
1268
|
+
`__box_get_${key}`,
|
|
1269
|
+
[EXT],
|
|
1270
|
+
[extType(w)],
|
|
1271
|
+
[
|
|
1272
|
+
...getIn(0, ref),
|
|
1273
|
+
{ op: "struct.get", typeIndex: ti, fieldIndex: 0 },
|
|
1274
|
+
...toExt(w),
|
|
1275
|
+
],
|
|
1276
|
+
);
|
|
1277
|
+
addFunc(
|
|
1278
|
+
`__box_set_${key}`,
|
|
1279
|
+
[EXT, extType(w)],
|
|
1280
|
+
[],
|
|
1281
|
+
[
|
|
1282
|
+
...getIn(0, ref),
|
|
1283
|
+
...getIn(1, w),
|
|
1284
|
+
{ op: "struct.set", typeIndex: ti, fieldIndex: 0 },
|
|
1285
|
+
],
|
|
1286
|
+
);
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
for (const [key, desc] of needs.maps) {
|
|
1290
|
+
const { mapInfo, keySliceDesc, valSliceDesc } = desc;
|
|
1291
|
+
const makeIdx = mod.resolveFuncIndex(mapInfo.makeFuncName);
|
|
1292
|
+
addFunc(
|
|
1293
|
+
`__map_new_${key}`,
|
|
1294
|
+
["i32"],
|
|
1295
|
+
[EXT],
|
|
1296
|
+
[get(0), { op: "call", funcIndex: makeIdx }, ...toExt(desc.wType)],
|
|
1297
|
+
);
|
|
1298
|
+
const setIdx = mod.resolveFuncIndex(mapInfo.setFuncName);
|
|
1299
|
+
addFunc(
|
|
1300
|
+
`__map_set_${key}`,
|
|
1301
|
+
[EXT, extType(mapInfo.keyWType), extType(mapInfo.valWType)],
|
|
1302
|
+
[],
|
|
1303
|
+
[
|
|
1304
|
+
...getIn(0, desc.wType),
|
|
1305
|
+
...getIn(1, mapInfo.keyWType),
|
|
1306
|
+
...getIn(2, mapInfo.valWType),
|
|
1307
|
+
{ op: "call", funcIndex: setIdx },
|
|
1308
|
+
],
|
|
1309
|
+
);
|
|
1310
|
+
const keysIdx = mod.resolveFuncIndex(mapInfo.keysFuncName);
|
|
1311
|
+
addFunc(
|
|
1312
|
+
`__map_keys_${key}`,
|
|
1313
|
+
[EXT],
|
|
1314
|
+
[EXT],
|
|
1315
|
+
[
|
|
1316
|
+
...getIn(0, desc.wType),
|
|
1317
|
+
{ op: "call", funcIndex: keysIdx },
|
|
1318
|
+
...toExt(keySliceDesc.wType),
|
|
1319
|
+
],
|
|
1320
|
+
);
|
|
1321
|
+
const valuesIdx = mod.resolveFuncIndex(mapInfo.valuesFuncName);
|
|
1322
|
+
addFunc(
|
|
1323
|
+
`__map_values_${key}`,
|
|
1324
|
+
[EXT],
|
|
1325
|
+
[EXT],
|
|
1326
|
+
[
|
|
1327
|
+
...getIn(0, desc.wType),
|
|
1328
|
+
{ op: "call", funcIndex: valuesIdx },
|
|
1329
|
+
...toExt(valSliceDesc.wType),
|
|
1330
|
+
],
|
|
1331
|
+
);
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
if (needs.error) {
|
|
1335
|
+
const errDispatcherIdx = mod.funcMap.get("__dispatch_error_Error");
|
|
1336
|
+
if (errDispatcherIdx !== undefined) {
|
|
1337
|
+
addFunc(
|
|
1338
|
+
"__error_msg",
|
|
1339
|
+
[EXT],
|
|
1340
|
+
[EXT],
|
|
1341
|
+
[
|
|
1342
|
+
get(0),
|
|
1343
|
+
{ op: "ref.is_null" },
|
|
1344
|
+
{ op: "if", blockType: "externref" },
|
|
1345
|
+
{ op: "ref.null", heapType: "extern" },
|
|
1346
|
+
{ op: "else" },
|
|
1347
|
+
...getIn(0, "anyref"),
|
|
1348
|
+
{ op: "call", funcIndex: errDispatcherIdx },
|
|
1349
|
+
...toExt("externref"),
|
|
1350
|
+
{ op: "end" },
|
|
1351
|
+
],
|
|
1352
|
+
);
|
|
1353
|
+
}
|
|
1354
|
+
const errCandidates = mod
|
|
1355
|
+
.findInterfaceCandidates(null, "Error")
|
|
1356
|
+
.map((c) => c.testTypeIndex);
|
|
1357
|
+
addFunc(
|
|
1358
|
+
"__is_error",
|
|
1359
|
+
[EXT],
|
|
1360
|
+
["i32"],
|
|
1361
|
+
refTestChain(errCandidates, () => 1),
|
|
1362
|
+
);
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
if (needs.time) {
|
|
1366
|
+
const timeInfo = mod.getStructType("time.Time");
|
|
1367
|
+
if (timeInfo) {
|
|
1368
|
+
const timeWType = toRef(timeInfo);
|
|
1369
|
+
addFunc(
|
|
1370
|
+
"__time_make",
|
|
1371
|
+
["f64", "i32"],
|
|
1372
|
+
[EXT],
|
|
1373
|
+
[
|
|
1374
|
+
get(0),
|
|
1375
|
+
{ op: "f64.const", value: 1000000 },
|
|
1376
|
+
{ op: "f64.mul" },
|
|
1377
|
+
{ op: "i64.trunc_sat_f64_s" },
|
|
1378
|
+
get(1),
|
|
1379
|
+
{ op: "struct.new", typeIndex: timeInfo.typeIndex },
|
|
1380
|
+
...toExt(timeWType),
|
|
1381
|
+
],
|
|
1382
|
+
);
|
|
1383
|
+
addFunc(
|
|
1384
|
+
"__time_unix_milli",
|
|
1385
|
+
[EXT],
|
|
1386
|
+
["f64"],
|
|
1387
|
+
[
|
|
1388
|
+
get(0),
|
|
1389
|
+
{ op: "ref.is_null" },
|
|
1390
|
+
{ op: "if", blockType: "f64" },
|
|
1391
|
+
{ op: "f64.const", value: 0 },
|
|
1392
|
+
{ op: "else" },
|
|
1393
|
+
...getIn(0, timeWType),
|
|
1394
|
+
{ op: "struct.get", typeIndex: timeInfo.typeIndex, fieldIndex: 0 },
|
|
1395
|
+
{ op: "i64.const", value: 1000000n },
|
|
1396
|
+
{ op: "i64.div_s" },
|
|
1397
|
+
{ op: "f64.convert_i64_s" },
|
|
1398
|
+
{ op: "end" },
|
|
1399
|
+
],
|
|
1400
|
+
);
|
|
1401
|
+
addFunc(
|
|
1402
|
+
"__time_offset",
|
|
1403
|
+
[EXT],
|
|
1404
|
+
["i32"],
|
|
1405
|
+
[
|
|
1406
|
+
get(0),
|
|
1407
|
+
{ op: "ref.is_null" },
|
|
1408
|
+
{ op: "if", blockType: "i32" },
|
|
1409
|
+
{ op: "i32.const", value: 0 },
|
|
1410
|
+
{ op: "else" },
|
|
1411
|
+
...getIn(0, timeWType),
|
|
1412
|
+
{ op: "struct.get", typeIndex: timeInfo.typeIndex, fieldIndex: 1 },
|
|
1413
|
+
{ op: "end" },
|
|
1414
|
+
],
|
|
1415
|
+
);
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
for (const c of meta.consts ?? []) {
|
|
1420
|
+
if (!c.dynamic) continue;
|
|
1421
|
+
const g = mod.resolveGlobal(c.name);
|
|
1422
|
+
if (!g) continue;
|
|
1423
|
+
addFunc(
|
|
1424
|
+
`__const_${c.name}`,
|
|
1425
|
+
[],
|
|
1426
|
+
[extType(c.desc.wType)],
|
|
1427
|
+
[{ op: "global.get", index: g.index }, ...toExt(c.desc.wType)],
|
|
1428
|
+
);
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
// `(externref) -> i32`: `ref.test`s param 0 against each type index and
|
|
1433
|
+
// returns `resultFor(i)` for the first match, 0 otherwise.
|
|
1434
|
+
function refTestChain(typeIndices, resultFor) {
|
|
1435
|
+
const body = typeIndices.flatMap((ti, i) => [
|
|
1436
|
+
{ op: "local.get", index: 0 },
|
|
1437
|
+
{ op: "any.convert_extern" },
|
|
1438
|
+
{ op: "ref.test", typeIndex: ti },
|
|
1439
|
+
{ op: "if", blockType: "void" },
|
|
1440
|
+
{ op: "i32.const", value: resultFor(i) },
|
|
1441
|
+
{ op: "return" },
|
|
1442
|
+
{ op: "end" },
|
|
1443
|
+
]);
|
|
1444
|
+
body.push({ op: "i32.const", value: 0 });
|
|
1445
|
+
return body;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
// Type indices (incl. unexported and boxed-value types) whose method set
|
|
1449
|
+
// covers the interface, from the same candidate search the dispatchers use.
|
|
1450
|
+
function implementingTypeIndices(desc, mod) {
|
|
1451
|
+
let acc = null;
|
|
1452
|
+
for (const m of desc.methods) {
|
|
1453
|
+
const have = new Set(
|
|
1454
|
+
mod.findInterfaceCandidates(null, m.name).map((c) => c.testTypeIndex),
|
|
1455
|
+
);
|
|
1456
|
+
acc = acc === null ? have : new Set([...acc].filter((t) => have.has(t)));
|
|
1457
|
+
}
|
|
1458
|
+
return [...(acc ?? [])];
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
// Interfaces: method calls go through the existing `ref.test` dispatchers;
|
|
1462
|
+
// `__impl$<I>` tells whether a wasm value implements I (guards JS inputs);
|
|
1463
|
+
// `__itag$<I>` reports which exported `*T` (1-based) a value holds, 0 if none.
|
|
1464
|
+
function addIfaceHelpers(desc, mod, addFunc, addTrampoline) {
|
|
1465
|
+
for (const m of desc.methods) {
|
|
1466
|
+
const entry = { exportName: m.dispatch };
|
|
1467
|
+
addTrampoline(entry);
|
|
1468
|
+
if (!entry.callName)
|
|
1469
|
+
throw new Error(
|
|
1470
|
+
`internal: no wasm dispatcher for interface method ${desc.name}.${m.name}`,
|
|
1471
|
+
);
|
|
1472
|
+
m.callName = entry.callName;
|
|
1473
|
+
}
|
|
1474
|
+
addFunc(
|
|
1475
|
+
`__impl$${desc.key}`,
|
|
1476
|
+
["externref"],
|
|
1477
|
+
["i32"],
|
|
1478
|
+
refTestChain(implementingTypeIndices(desc, mod), () => 1),
|
|
1479
|
+
);
|
|
1480
|
+
if (desc.concrete.length === 0) return;
|
|
1481
|
+
addFunc(
|
|
1482
|
+
`__itag$${desc.key}`,
|
|
1483
|
+
["externref"],
|
|
1484
|
+
["i32"],
|
|
1485
|
+
refTestChain(
|
|
1486
|
+
desc.concrete.map((c) => c.typeIndex),
|
|
1487
|
+
(i) => i + 1,
|
|
1488
|
+
),
|
|
1489
|
+
);
|
|
826
1490
|
}
|
|
827
1491
|
|
|
828
1492
|
// ── JS facade generation ─────────────────────────────────────
|
|
@@ -845,16 +1509,34 @@ function inExpr(desc, v) {
|
|
|
845
1509
|
return `${P}strin(${v})`;
|
|
846
1510
|
case "any":
|
|
847
1511
|
return `(${v} === undefined ? null : ${v})`;
|
|
1512
|
+
case "error":
|
|
1513
|
+
return `${P}errin(${v})`;
|
|
1514
|
+
case "time":
|
|
1515
|
+
return desc.ptr ? `${P}timein_ptr(${v})` : `${P}timein(${v})`;
|
|
1516
|
+
case "map":
|
|
1517
|
+
return `${P}mapin_${desc.key}(${v})`;
|
|
1518
|
+
case "ptr_box":
|
|
1519
|
+
return `${P}boxin_${desc.key}(${v})`;
|
|
1520
|
+
case "anon_struct":
|
|
1521
|
+
return desc.ptr
|
|
1522
|
+
? `(${v} == null ? null : ${P}to_${desc.name}(${v}))`
|
|
1523
|
+
: `${P}to_${desc.name}(${v})`;
|
|
848
1524
|
case "struct":
|
|
849
1525
|
if (desc.info.pkgTarget === "both")
|
|
850
1526
|
return `${P}to_${desc.name}(${v}, ${desc.ptr ? "false" : "true"})`;
|
|
851
|
-
return desc.ptr
|
|
1527
|
+
return desc.ptr
|
|
1528
|
+
? `${P}href_${desc.name}(${v})`
|
|
1529
|
+
: `${P}hval_${desc.name}(${v})`;
|
|
852
1530
|
case "slice":
|
|
853
1531
|
return `${P}slin_${desc.key}(${v})`;
|
|
854
1532
|
case "array":
|
|
855
1533
|
return `${P}arrin_${desc.key}(${v})`;
|
|
856
1534
|
case "func":
|
|
857
1535
|
return `${P}fnin_${desc.key}(${v})`;
|
|
1536
|
+
case "iface":
|
|
1537
|
+
return `${P}ifin_${desc.key}(${v})`;
|
|
1538
|
+
case "shared":
|
|
1539
|
+
return `${P}shin(${v}, ${desc.ctor})`;
|
|
858
1540
|
default:
|
|
859
1541
|
return v;
|
|
860
1542
|
}
|
|
@@ -871,6 +1553,18 @@ function outExpr(desc, v) {
|
|
|
871
1553
|
return desc.unsigned ? `${P}u64out(${v})` : `${P}i64out(${v})`;
|
|
872
1554
|
case "string":
|
|
873
1555
|
return `(${v} ?? "")`;
|
|
1556
|
+
case "error":
|
|
1557
|
+
return `${P}errout(${v})`;
|
|
1558
|
+
case "time":
|
|
1559
|
+
return desc.ptr ? `${P}timeout_ptr(${v})` : `${P}timeout(${v})`;
|
|
1560
|
+
case "map":
|
|
1561
|
+
return `${P}mapout_${desc.key}(${v})`;
|
|
1562
|
+
case "ptr_box":
|
|
1563
|
+
return `${P}boxout_${desc.key}(${v})`;
|
|
1564
|
+
case "anon_struct":
|
|
1565
|
+
return desc.ptr
|
|
1566
|
+
? `(${v} == null ? null : ${P}from_${desc.name}(${v}))`
|
|
1567
|
+
: `${P}from_${desc.name}(${v})`;
|
|
874
1568
|
case "struct":
|
|
875
1569
|
return desc.info.pkgTarget === "both"
|
|
876
1570
|
? `${P}from_${desc.name}(${v})`
|
|
@@ -881,6 +1575,10 @@ function outExpr(desc, v) {
|
|
|
881
1575
|
return `${P}arrout_${desc.key}(${v})`;
|
|
882
1576
|
case "func":
|
|
883
1577
|
return `${P}fnout_${desc.key}(${v})`;
|
|
1578
|
+
case "iface":
|
|
1579
|
+
return `${P}ifout_${desc.key}(${v})`;
|
|
1580
|
+
case "shared":
|
|
1581
|
+
return `${P}shout(${v}, ${desc.ctor})`;
|
|
884
1582
|
default:
|
|
885
1583
|
return v;
|
|
886
1584
|
}
|
|
@@ -897,6 +1595,15 @@ function zeroExpr(desc) {
|
|
|
897
1595
|
return "0";
|
|
898
1596
|
case "string":
|
|
899
1597
|
return '""';
|
|
1598
|
+
case "error":
|
|
1599
|
+
case "time":
|
|
1600
|
+
case "map":
|
|
1601
|
+
case "ptr_box":
|
|
1602
|
+
return "null";
|
|
1603
|
+
case "anon_struct":
|
|
1604
|
+
return desc.ptr
|
|
1605
|
+
? "null"
|
|
1606
|
+
: `{ ${desc.fields.map((f) => `"${f.name}": ${zeroExpr(f.desc)}`).join(", ")} }`;
|
|
900
1607
|
case "struct":
|
|
901
1608
|
return desc.ptr ? "null" : `new ${desc.name}()`;
|
|
902
1609
|
case "array":
|
|
@@ -932,6 +1639,10 @@ function isBothStruct(d) {
|
|
|
932
1639
|
return d.k === "struct" && d.info.pkgTarget === "both";
|
|
933
1640
|
}
|
|
934
1641
|
|
|
1642
|
+
function isPtrWriteBack(d) {
|
|
1643
|
+
return isPtrBoth(d) || d.k === "ptr_box" || (d.k === "anon_struct" && d.ptr);
|
|
1644
|
+
}
|
|
1645
|
+
|
|
935
1646
|
function returnLines(returns, resultExpr) {
|
|
936
1647
|
if (returns.length === 0) return [];
|
|
937
1648
|
if (returns.length === 1)
|
|
@@ -946,13 +1657,21 @@ function genFuncBody(exportName, params, returns, leadArgs = []) {
|
|
|
946
1657
|
const names = params.map((_, i) => `a${i}`);
|
|
947
1658
|
const lines = [];
|
|
948
1659
|
const argExprs = [...leadArgs];
|
|
949
|
-
const writeBack = params.some(
|
|
950
|
-
if (
|
|
1660
|
+
const writeBack = params.some(isPtrWriteBack);
|
|
1661
|
+
if (params.some(isPtrBoth)) lines.push("\tconst __m = new Map();");
|
|
951
1662
|
for (let i = 0; i < params.length; i++) {
|
|
952
1663
|
const d = params[i];
|
|
953
1664
|
if (isPtrBoth(d)) {
|
|
954
1665
|
lines.push(`\tconst __p${i} = ${P}to_${d.name}(a${i}, false, __m);`);
|
|
955
1666
|
argExprs.push(`__p${i}`);
|
|
1667
|
+
} else if (d.k === "ptr_box") {
|
|
1668
|
+
lines.push(`\tconst __p${i} = ${P}boxin_${d.key}(a${i});`);
|
|
1669
|
+
argExprs.push(`__p${i}`);
|
|
1670
|
+
} else if (d.k === "anon_struct" && d.ptr) {
|
|
1671
|
+
lines.push(
|
|
1672
|
+
`\tconst __p${i} = a${i} == null ? null : ${P}to_${d.name}(a${i});`,
|
|
1673
|
+
);
|
|
1674
|
+
argExprs.push(`__p${i}`);
|
|
956
1675
|
} else {
|
|
957
1676
|
argExprs.push(inExpr(d, names[i]));
|
|
958
1677
|
}
|
|
@@ -970,8 +1689,12 @@ function genFuncBody(exportName, params, returns, leadArgs = []) {
|
|
|
970
1689
|
} else {
|
|
971
1690
|
lines.push(`\t\tconst __r = ${call};`);
|
|
972
1691
|
for (let i = 0; i < params.length; i++) {
|
|
973
|
-
|
|
974
|
-
|
|
1692
|
+
const d = params[i];
|
|
1693
|
+
if (isPtrBoth(d)) lines.push(`\t\t${P}back_${d.name}(__p${i}, a${i});`);
|
|
1694
|
+
else if (d.k === "ptr_box")
|
|
1695
|
+
lines.push(`\t\t${P}boxback_${d.key}(__p${i}, a${i});`);
|
|
1696
|
+
else if (d.k === "anon_struct" && d.ptr)
|
|
1697
|
+
lines.push(`\t\t${P}back_${d.name}(__p${i}, a${i});`);
|
|
975
1698
|
}
|
|
976
1699
|
lines.push(...returnLines(returns, "__r").map((l) => `\t${l}`));
|
|
977
1700
|
}
|
|
@@ -989,15 +1712,40 @@ function indent(text, prefix) {
|
|
|
989
1712
|
// quoted, helpers prefixed with `__`.
|
|
990
1713
|
export const WASM_IMPORTS_JS = `const ${P}MAX = 9007199254740991n;
|
|
991
1714
|
function ${P}imports(stringTable, extraEnv, tag, write) {
|
|
992
|
-
let
|
|
1715
|
+
let lineBuf = "";
|
|
993
1716
|
let targs = [];
|
|
994
|
-
|
|
995
|
-
const
|
|
996
|
-
const
|
|
1717
|
+
let fmtStructMap = new WeakMap();
|
|
1718
|
+
const late = { exports: null };
|
|
1719
|
+
const flush = () => { write(lineBuf); lineBuf = ""; };
|
|
1720
|
+
const print = (v) => {
|
|
1721
|
+
const s = String(v);
|
|
1722
|
+
if (lineBuf.length > 0 && !lineBuf.endsWith(" ") && !s.startsWith(" ")) {
|
|
1723
|
+
lineBuf += " ";
|
|
1724
|
+
}
|
|
1725
|
+
lineBuf += s;
|
|
1726
|
+
};
|
|
1727
|
+
const println = (v) => {
|
|
1728
|
+
const s = String(v);
|
|
1729
|
+
if (lineBuf.length > 0 && !lineBuf.endsWith(" ") && !s.startsWith(" ")) {
|
|
1730
|
+
lineBuf += " ";
|
|
1731
|
+
}
|
|
1732
|
+
lineBuf += s;
|
|
1733
|
+
flush();
|
|
1734
|
+
};
|
|
997
1735
|
const targ = (v) => { targs.push(v); };
|
|
1736
|
+
let utf8Encoded = null;
|
|
1737
|
+
let convBytes = null, convPos = 0;
|
|
998
1738
|
const env = {
|
|
999
1739
|
"panicTag": tag,
|
|
1000
|
-
"panic": (msg) => {
|
|
1740
|
+
"panic": (msg) => {
|
|
1741
|
+
let str;
|
|
1742
|
+
try {
|
|
1743
|
+
str = String(msg);
|
|
1744
|
+
} catch {
|
|
1745
|
+
str = "[panic object]";
|
|
1746
|
+
}
|
|
1747
|
+
throw new Error(str);
|
|
1748
|
+
},
|
|
1001
1749
|
"str": (i) => stringTable[i] ?? "",
|
|
1002
1750
|
"str_len": (s) => (s ? s.length : 0),
|
|
1003
1751
|
"str_concat": (a, b) => (a ?? "") + (b ?? ""),
|
|
@@ -1011,7 +1759,53 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
|
|
|
1011
1759
|
"str_slice": (s, a, b) => (s ? s.slice(a, b) : ""),
|
|
1012
1760
|
"str_from_code_point": (c) => String.fromCodePoint(c),
|
|
1013
1761
|
"str_code_point_at": (s, i) => (s ? s.codePointAt(i) : 0),
|
|
1762
|
+
"str_hash": (s) => {
|
|
1763
|
+
if (!s) return 0;
|
|
1764
|
+
let h = 0;
|
|
1765
|
+
for (let i = 0; i < s.length; i++) {
|
|
1766
|
+
h = (Math.imul(31, h) + s.charCodeAt(i)) | 0;
|
|
1767
|
+
}
|
|
1768
|
+
return h;
|
|
1769
|
+
},
|
|
1770
|
+
"str_to_upper": (s) => (s ? s.toUpperCase() : ""),
|
|
1771
|
+
"str_to_lower": (s) => (s ? s.toLowerCase() : ""),
|
|
1772
|
+
"str_trim_space": (s) => (s ? s.trim() : ""),
|
|
1773
|
+
"str_contains": (s, sub) => (s && sub !== undefined ? (s.includes(sub) ? 1 : 0) : 0),
|
|
1774
|
+
"str_has_prefix": (s, pre) => (s && pre !== undefined ? (s.startsWith(pre) ? 1 : 0) : 0),
|
|
1775
|
+
"str_has_suffix": (s, suf) => (s && suf !== undefined ? (s.endsWith(suf) ? 1 : 0) : 0),
|
|
1776
|
+
"str_index": (s, sub) => (s ? s.indexOf(sub) : -1),
|
|
1777
|
+
"str_last_index": (s, sub) => (s ? s.lastIndexOf(sub) : -1),
|
|
1778
|
+
"str_repeat": (s, n) => (s && n > 0 ? s.repeat(Number(n)) : ""),
|
|
1779
|
+
"str_replace_all": (s, o, n) => (s ? s.replaceAll(o, n) : ""),
|
|
1780
|
+
"str_equal_fold": (a, b) => ((a ?? "").toLowerCase() === (b ?? "").toLowerCase() ? 1 : 0),
|
|
1781
|
+
"str_count": (s, sep) => {
|
|
1782
|
+
if (!s) return sep === "" ? 1 : 0;
|
|
1783
|
+
if (sep === "") return s.length + 1;
|
|
1784
|
+
return s.split(sep).length - 1;
|
|
1785
|
+
},
|
|
1786
|
+
"str_from_i64": (n) => String(n),
|
|
1787
|
+
"str_from_i32": (n) => String(n),
|
|
1788
|
+
"str_from_f64": (f) => String(f),
|
|
1014
1789
|
"is_string": (v) => (typeof v === "string" ? 1 : 0),
|
|
1790
|
+
"str_to_bytes": (s) => {
|
|
1791
|
+
utf8Encoded = new TextEncoder().encode(s ?? "");
|
|
1792
|
+
return utf8Encoded.length;
|
|
1793
|
+
},
|
|
1794
|
+
"str_byte_at": (i) => (utf8Encoded ? (utf8Encoded[i] ?? 0) : 0),
|
|
1795
|
+
"bytes_to_str_init": (len) => {
|
|
1796
|
+
convBytes = new Uint8Array(len);
|
|
1797
|
+
convPos = 0;
|
|
1798
|
+
},
|
|
1799
|
+
"bytes_to_str_push": (b) => {
|
|
1800
|
+
if (convBytes && convPos < convBytes.length) {
|
|
1801
|
+
convBytes[convPos++] = b & 0xFF;
|
|
1802
|
+
}
|
|
1803
|
+
},
|
|
1804
|
+
"bytes_to_str_finish": () => {
|
|
1805
|
+
const res = convBytes ? new TextDecoder().decode(convBytes) : "";
|
|
1806
|
+
convBytes = null;
|
|
1807
|
+
return res;
|
|
1808
|
+
},
|
|
1015
1809
|
"print_i32": print, "print_i64": print, "print_f32": print, "print_f64": print,
|
|
1016
1810
|
"print_str": print, "print_any": print,
|
|
1017
1811
|
"print_bool": (b) => print(b !== 0 ? "true" : "false"),
|
|
@@ -1019,22 +1813,631 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
|
|
|
1019
1813
|
"println_str": println, "println_any": println,
|
|
1020
1814
|
"println_bool": (b) => println(b !== 0 ? "true" : "false"),
|
|
1021
1815
|
"println_empty": flush,
|
|
1816
|
+
"fmt_new_array": () => [],
|
|
1817
|
+
"fmt_array_push": (arr, v) => { arr.push(v); },
|
|
1818
|
+
"fmt_new_map": () => new Map(),
|
|
1819
|
+
"fmt_map_set": (m, k, v) => { m.set(k, v); },
|
|
1820
|
+
"fmt_new_struct": (ref, isPtr) => {
|
|
1821
|
+
if (ref !== null && ref !== undefined && typeof ref === "object" && fmtStructMap.has(ref)) {
|
|
1822
|
+
return fmtStructMap.get(ref);
|
|
1823
|
+
}
|
|
1824
|
+
const o = {};
|
|
1825
|
+
Object.defineProperty(o, "__struct", { value: true, configurable: true });
|
|
1826
|
+
if (isPtr !== 0) Object.defineProperty(o, "__p", { value: true, configurable: true });
|
|
1827
|
+
if (ref !== null && ref !== undefined && typeof ref === "object") fmtStructMap.set(ref, o);
|
|
1828
|
+
return o;
|
|
1829
|
+
},
|
|
1830
|
+
"fmt_struct_set": (o, k, v) => { o[typeof k === "number" ? stringTable[k] : k] = v; },
|
|
1831
|
+
"fmt_box_i32": (v) => v,
|
|
1832
|
+
"fmt_box_i64": (v) => (v > ${P}MAX || v < -${P}MAX ? v : Number(v)),
|
|
1833
|
+
"fmt_box_f64": (v) => v,
|
|
1834
|
+
"fmt_box_bool": (v) => v !== 0,
|
|
1835
|
+
"fmt_new_box": () => ({ value: null, __p: true, __box: true }),
|
|
1022
1836
|
"testing_arg_i32": targ, "testing_arg_f32": targ, "testing_arg_f64": targ,
|
|
1023
1837
|
"testing_arg_str": targ, "testing_arg_any": targ,
|
|
1024
1838
|
"testing_arg_i64": (v) => targ(v > ${P}MAX || v < -${P}MAX ? v : Number(v)),
|
|
1025
1839
|
"testing_arg_bool": (v) => targ(v !== 0),
|
|
1026
|
-
"testing_call": (t, i) => { const a = targs; targs = []; t[stringTable[i]](...a); },
|
|
1840
|
+
"testing_call": (t, i) => { const a = targs; targs = []; fmtStructMap = new WeakMap(); t[stringTable[i]](...a); },
|
|
1027
1841
|
"testing_name": (t) => t.Name(),
|
|
1028
1842
|
"testing_flag": (t, i) => (t[stringTable[i]]() ? 1 : 0),
|
|
1029
1843
|
};
|
|
1844
|
+
// Stdlib imports (strings/strconv/utf8/fmt/t.Run) are a separate block so
|
|
1845
|
+
// modules that never call them do not ship it (see WASM_STDLIB_JS).
|
|
1846
|
+
if (typeof ${P}stdlib === "function") {
|
|
1847
|
+
Object.assign(env, ${P}stdlib(stringTable, {
|
|
1848
|
+
"take": () => { const a = targs; targs = []; fmtStructMap = new WeakMap(); return a; },
|
|
1849
|
+
"printf": (s) => {
|
|
1850
|
+
lineBuf += s;
|
|
1851
|
+
const nl = lineBuf.lastIndexOf("\\n");
|
|
1852
|
+
if (nl >= 0) { write(lineBuf.slice(0, nl)); lineBuf = lineBuf.slice(nl + 1); }
|
|
1853
|
+
},
|
|
1854
|
+
"late": late,
|
|
1855
|
+
}));
|
|
1856
|
+
}
|
|
1030
1857
|
Object.assign(env, extraEnv);
|
|
1031
1858
|
const m = {};
|
|
1032
|
-
for (const k of ["sin", "cos", "tan", "asin", "acos", "atan", "atan2", "pow", "exp", "log", "log2", "log10", "
|
|
1033
|
-
|
|
1859
|
+
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];
|
|
1860
|
+
m.mod = (x, y) => x % y;
|
|
1861
|
+
// Go rounds half away from zero; Math.round rounds half toward +Infinity.
|
|
1862
|
+
m.round = (x) => Math.sign(x) * Math.round(Math.abs(x));
|
|
1863
|
+
// Exports are needed by imports that call back into the module (t.Run).
|
|
1864
|
+
return { "env": env, "Math": m, "__bind": (ex) => { late.exports = ex; } };
|
|
1865
|
+
}`;
|
|
1866
|
+
|
|
1867
|
+
// JS side of the STDLIB_IMPORTS table (index.js); emitted only when the
|
|
1868
|
+
// module imports one of the packages that use it.
|
|
1869
|
+
export const WASM_STDLIB_JS = `${HELPER_ERROR}
|
|
1870
|
+
${HELPER_SPRINTF}
|
|
1871
|
+
${HELPER_STRCONV}
|
|
1872
|
+
${HELPER_PATH_CLEAN}
|
|
1873
|
+
${HELPER_DURATION}
|
|
1874
|
+
${HELPER_SHA256}
|
|
1875
|
+
${HELPER_HEX}
|
|
1876
|
+
${HELPER_BASE64}
|
|
1877
|
+
function ${P}stdlib(stringTable, io) {
|
|
1878
|
+
let parts = [];
|
|
1879
|
+
let cutBefore = "", cutAfter = "";
|
|
1880
|
+
let strconvErr = null, strconvErrKind = 0;
|
|
1881
|
+
let decodeSize = 0;
|
|
1882
|
+
let replacerTarget = "", replacerPairs = [];
|
|
1883
|
+
let splitDir = "", splitFile = "";
|
|
1884
|
+
let joinParts = [];
|
|
1885
|
+
let matchHasErr = 0;
|
|
1886
|
+
let trimStart = 0, trimEnd = 0;
|
|
1887
|
+
let sscanTokens = [];
|
|
1888
|
+
let sscanHasErr = 0, sscanErrMsg = "";
|
|
1889
|
+
let timeParseErr = null, timeParseOffset = 0;
|
|
1890
|
+
let rawBytes = new Uint8Array(0);
|
|
1891
|
+
let rawPos = 0;
|
|
1892
|
+
let hexErr = null;
|
|
1893
|
+
let base64Err = null;
|
|
1894
|
+
let customBase64Encodings = [];
|
|
1895
|
+
return {
|
|
1896
|
+
"fmt_sscan_init": (s, mode) => {
|
|
1897
|
+
sscanHasErr = 0;
|
|
1898
|
+
sscanErrMsg = "";
|
|
1899
|
+
const str = s ?? "";
|
|
1900
|
+
sscanTokens = (mode === 1 ? str.split("\\n")[0] : str).trim().split(/\\s+/).filter(Boolean);
|
|
1901
|
+
return sscanTokens.length;
|
|
1902
|
+
},
|
|
1903
|
+
"fmt_sscanf_init": (s, fmt) => {
|
|
1904
|
+
sscanHasErr = 0;
|
|
1905
|
+
sscanErrMsg = "";
|
|
1906
|
+
const str = s ?? "";
|
|
1907
|
+
const fmtStr = fmt ?? "";
|
|
1908
|
+
let si = 0, fi = 0;
|
|
1909
|
+
sscanTokens = [];
|
|
1910
|
+
while (fi < fmtStr.length) {
|
|
1911
|
+
if (/\\s/.test(fmtStr[fi])) {
|
|
1912
|
+
while (fi < fmtStr.length && /\\s/.test(fmtStr[fi])) fi++;
|
|
1913
|
+
while (si < str.length && /\\s/.test(str[si])) si++;
|
|
1914
|
+
continue;
|
|
1915
|
+
}
|
|
1916
|
+
if (fmtStr[fi] === "%") {
|
|
1917
|
+
if (fi + 1 < fmtStr.length && fmtStr[fi + 1] === "%") {
|
|
1918
|
+
if (str[si] !== "%") {
|
|
1919
|
+
sscanHasErr = 1;
|
|
1920
|
+
sscanErrMsg = "input does not match format";
|
|
1921
|
+
break;
|
|
1922
|
+
}
|
|
1923
|
+
si++;
|
|
1924
|
+
fi += 2;
|
|
1925
|
+
continue;
|
|
1926
|
+
}
|
|
1927
|
+
const vm = fmtStr.slice(fi).match(/^%[vdsfqtxXbcoeEfFgG]/);
|
|
1928
|
+
if (vm) {
|
|
1929
|
+
const verb = vm[0];
|
|
1930
|
+
fi += verb.length;
|
|
1931
|
+
if (verb !== "%c") {
|
|
1932
|
+
while (si < str.length && /\\s/.test(str[si])) si++;
|
|
1933
|
+
}
|
|
1934
|
+
if (si >= str.length) break;
|
|
1935
|
+
let tokRe = /^\\S+/;
|
|
1936
|
+
if (verb === "%d" || verb === "%i") tokRe = /^[+-]?\\d+/;
|
|
1937
|
+
else if (verb === "%f" || verb === "%F" || verb === "%e" || verb === "%E" || verb === "%g" || verb === "%G") tokRe = /^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?/;
|
|
1938
|
+
else if (verb === "%t") tokRe = /^(?:true|false|1|0|t|f|T|F|TRUE|FALSE)/;
|
|
1939
|
+
else if (verb === "%x" || verb === "%X") tokRe = /^[+-]?(?:0x)?[0-9a-fA-F]+/;
|
|
1940
|
+
else if (verb === "%c") tokRe = /^[\\s\\S]/;
|
|
1941
|
+
const m = str.slice(si).match(tokRe);
|
|
1942
|
+
if (!m) {
|
|
1943
|
+
sscanHasErr = 1;
|
|
1944
|
+
sscanErrMsg = "input does not match format";
|
|
1945
|
+
break;
|
|
1946
|
+
}
|
|
1947
|
+
sscanTokens.push(m[0]);
|
|
1948
|
+
si += m[0].length;
|
|
1949
|
+
continue;
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
if (si >= str.length) break;
|
|
1953
|
+
if (str[si] !== fmtStr[fi]) {
|
|
1954
|
+
sscanHasErr = 1;
|
|
1955
|
+
sscanErrMsg = "input does not match format";
|
|
1956
|
+
break;
|
|
1957
|
+
}
|
|
1958
|
+
si++;
|
|
1959
|
+
fi++;
|
|
1960
|
+
}
|
|
1961
|
+
return sscanTokens.length;
|
|
1962
|
+
},
|
|
1963
|
+
"fmt_sscan_has_err": () => sscanHasErr,
|
|
1964
|
+
"fmt_sscan_err_msg": () => sscanErrMsg || "syntax error",
|
|
1965
|
+
"fmt_sscan_token_str": (i) => sscanTokens[i] ?? "",
|
|
1966
|
+
"fmt_sscan_token_i64": (i) => {
|
|
1967
|
+
if (i >= sscanTokens.length) { sscanHasErr = 1; sscanErrMsg = "unexpected EOF"; return 0n; }
|
|
1968
|
+
const t = sscanTokens[i];
|
|
1969
|
+
if (/^[+-]?(?:0[xX])?[0-9a-fA-F]+$/.test(t)) {
|
|
1970
|
+
try {
|
|
1971
|
+
if (/^[+-]?[0-9a-fA-F]+$/.test(t) && /[a-fA-F]/.test(t)) {
|
|
1972
|
+
return BigInt("0x" + t);
|
|
1973
|
+
}
|
|
1974
|
+
return BigInt(t);
|
|
1975
|
+
} catch {}
|
|
1976
|
+
}
|
|
1977
|
+
if (t.length === 1) {
|
|
1978
|
+
return BigInt(t.charCodeAt(0));
|
|
1979
|
+
}
|
|
1980
|
+
sscanHasErr = 1;
|
|
1981
|
+
sscanErrMsg = "expected integer";
|
|
1982
|
+
return 0n;
|
|
1983
|
+
},
|
|
1984
|
+
"fmt_sscan_token_f64": (i) => {
|
|
1985
|
+
if (i >= sscanTokens.length) { sscanHasErr = 1; sscanErrMsg = "unexpected EOF"; return 0; }
|
|
1986
|
+
const t = sscanTokens[i];
|
|
1987
|
+
const n = Number(t);
|
|
1988
|
+
if (Number.isNaN(n) || !/^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?$/.test(t)) {
|
|
1989
|
+
sscanHasErr = 1;
|
|
1990
|
+
sscanErrMsg = "expected float";
|
|
1991
|
+
return 0;
|
|
1992
|
+
}
|
|
1993
|
+
return n;
|
|
1994
|
+
},
|
|
1995
|
+
"fmt_sscan_token_bool": (i) => {
|
|
1996
|
+
if (i >= sscanTokens.length) { sscanHasErr = 1; sscanErrMsg = "unexpected EOF"; return 0; }
|
|
1997
|
+
const t = sscanTokens[i]?.toLowerCase();
|
|
1998
|
+
if (t === "true" || t === "1" || t === "t") return 1;
|
|
1999
|
+
if (t === "false" || t === "0" || t === "f") return 0;
|
|
2000
|
+
sscanHasErr = 1;
|
|
2001
|
+
sscanErrMsg = "expected boolean";
|
|
2002
|
+
return 0;
|
|
2003
|
+
},
|
|
2004
|
+
"testing_run": (t, name, fn) => (t.Run(name, (sub) => { io.late.exports.__testing_run_cb(fn, sub); }) ? 1 : 0),
|
|
2005
|
+
"fmt_sprintf": (f) => __sprintf(f ?? "", ...io.take()),
|
|
2006
|
+
"fmt_printf": (f) => { io.printf(__sprintf(f ?? "", ...io.take())); },
|
|
2007
|
+
"fmt_format_v": (v) => { const res = __sprintf("%v", v); io.take(); return res; },
|
|
2008
|
+
"str_trim": (s, c) => ${P}trim(s ?? "", c ?? "", true, true),
|
|
2009
|
+
"str_trim_left": (s, c) => ${P}trim(s ?? "", c ?? "", true, false),
|
|
2010
|
+
"str_trim_right": (s, c) => ${P}trim(s ?? "", c ?? "", false, true),
|
|
2011
|
+
"str_trim_prefix": (s, p) => (s && p && s.startsWith(p) ? s.slice(p.length) : (s ?? "")),
|
|
2012
|
+
"str_trim_suffix": (s, p) => (s && p && s.endsWith(p) ? s.slice(0, s.length - p.length) : (s ?? "")),
|
|
2013
|
+
"str_cut": (s, sep) => {
|
|
2014
|
+
const i = (s ?? "").indexOf(sep ?? "");
|
|
2015
|
+
if (i < 0) { cutBefore = s ?? ""; cutAfter = ""; return 0; }
|
|
2016
|
+
cutBefore = (s ?? "").slice(0, i);
|
|
2017
|
+
cutAfter = (s ?? "").slice(i + (sep ?? "").length);
|
|
2018
|
+
return 1;
|
|
2019
|
+
},
|
|
2020
|
+
"str_cut_before": () => cutBefore,
|
|
2021
|
+
"str_cut_after": () => cutAfter,
|
|
2022
|
+
"str_cut_prefix": (s, p) => {
|
|
2023
|
+
s = s ?? ""; p = p ?? "";
|
|
2024
|
+
if (s.startsWith(p)) { cutAfter = s.slice(p.length); return 1; }
|
|
2025
|
+
cutAfter = s; return 0;
|
|
2026
|
+
},
|
|
2027
|
+
"str_cut_suffix": (s, suf) => {
|
|
2028
|
+
s = s ?? ""; suf = suf ?? "";
|
|
2029
|
+
if (s.endsWith(suf)) { cutBefore = s.slice(0, s.length - suf.length); return 1; }
|
|
2030
|
+
cutBefore = s; return 0;
|
|
2031
|
+
},
|
|
2032
|
+
"str_replace": (s, o, n, cnt) => {
|
|
2033
|
+
s = s ?? ""; o = o ?? ""; n = n ?? "";
|
|
2034
|
+
if (cnt < 0) return s.replaceAll(o, n);
|
|
2035
|
+
let out = "", from = 0;
|
|
2036
|
+
for (let k = 0; k < cnt; k++) {
|
|
2037
|
+
const at = s.indexOf(o, from);
|
|
2038
|
+
if (at < 0) break;
|
|
2039
|
+
out += s.slice(from, at) + n;
|
|
2040
|
+
from = at + o.length;
|
|
2041
|
+
if (o === "") { if (from >= s.length) break; out += s[from]; from++; }
|
|
2042
|
+
}
|
|
2043
|
+
return out + s.slice(from);
|
|
2044
|
+
},
|
|
2045
|
+
"str_contains_rune": (s, r) => ((s ?? "").includes(String.fromCodePoint(r)) ? 1 : 0),
|
|
2046
|
+
"str_contains_any": (s, cs) => { for (const c of (cs ?? "")) if ((s ?? "").includes(c)) return 1; return 0; },
|
|
2047
|
+
"str_index_byte": (s, b) => (s ?? "").indexOf(String.fromCharCode(b)),
|
|
2048
|
+
"str_index_rune": (s, r) => (s ?? "").indexOf(String.fromCodePoint(r)),
|
|
2049
|
+
"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; },
|
|
2050
|
+
"str_last_index_byte": (s, b) => (s ?? "").lastIndexOf(String.fromCharCode(b)),
|
|
2051
|
+
"str_last_index_any": (s, cs) => {
|
|
2052
|
+
s = s ?? ""; let best = -1;
|
|
2053
|
+
for (const c of (cs ?? "")) {
|
|
2054
|
+
const i = s.lastIndexOf(c);
|
|
2055
|
+
if (i > best) best = i;
|
|
2056
|
+
}
|
|
2057
|
+
return best;
|
|
2058
|
+
},
|
|
2059
|
+
"str_title": (s) => (s ?? "").replace(/(^|[^\\p{L}\\p{N}_'])(\\p{Ll})/gu, (m, a, b) => a + b.toUpperCase()),
|
|
2060
|
+
"str_split": (s, sep) => { parts = (s ?? "").split(sep ?? ""); return parts.length; },
|
|
2061
|
+
"str_split_n": (s, sep, n) => {
|
|
2062
|
+
s = s ?? ""; sep = sep ?? "";
|
|
2063
|
+
if (n === 0) return -1;
|
|
2064
|
+
if (n < 0) { parts = sep === "" ? [...s] : s.split(sep); return parts.length; }
|
|
2065
|
+
if (sep === "") {
|
|
2066
|
+
const chars = [...s];
|
|
2067
|
+
if (chars.length <= n) { parts = chars; return parts.length; }
|
|
2068
|
+
const r = chars.slice(0, n - 1);
|
|
2069
|
+
r.push(chars.slice(n - 1).join(""));
|
|
2070
|
+
parts = r;
|
|
2071
|
+
return parts.length;
|
|
2072
|
+
}
|
|
2073
|
+
const r = [];
|
|
2074
|
+
let cur = s;
|
|
2075
|
+
for (let i = 1; i < n && cur.length; i++) {
|
|
2076
|
+
const j = cur.indexOf(sep);
|
|
2077
|
+
if (j < 0) break;
|
|
2078
|
+
r.push(cur.slice(0, j));
|
|
2079
|
+
cur = cur.slice(j + sep.length);
|
|
2080
|
+
}
|
|
2081
|
+
r.push(cur);
|
|
2082
|
+
parts = r;
|
|
2083
|
+
return parts.length;
|
|
2084
|
+
},
|
|
2085
|
+
"str_split_after": (s, sep) => {
|
|
2086
|
+
s = s ?? ""; sep = sep ?? "";
|
|
2087
|
+
if (sep === "") { parts = [...s]; return parts.length; }
|
|
2088
|
+
const r = [];
|
|
2089
|
+
let cur = s;
|
|
2090
|
+
while (true) {
|
|
2091
|
+
const j = cur.indexOf(sep);
|
|
2092
|
+
if (j < 0) { r.push(cur); break; }
|
|
2093
|
+
r.push(cur.slice(0, j + sep.length));
|
|
2094
|
+
cur = cur.slice(j + sep.length);
|
|
2095
|
+
}
|
|
2096
|
+
parts = r;
|
|
2097
|
+
return parts.length;
|
|
2098
|
+
},
|
|
2099
|
+
"str_split_after_n": (s, sep, n) => {
|
|
2100
|
+
s = s ?? ""; sep = sep ?? "";
|
|
2101
|
+
if (n === 0) return -1;
|
|
2102
|
+
if (sep === "") {
|
|
2103
|
+
const chars = [...s];
|
|
2104
|
+
if (n < 0 || chars.length <= n) parts = chars;
|
|
2105
|
+
else {
|
|
2106
|
+
const r = chars.slice(0, n - 1);
|
|
2107
|
+
r.push(chars.slice(n - 1).join(""));
|
|
2108
|
+
parts = r;
|
|
2109
|
+
}
|
|
2110
|
+
return parts.length;
|
|
2111
|
+
}
|
|
2112
|
+
const r = [];
|
|
2113
|
+
let cur = s;
|
|
2114
|
+
for (let i = 1; (n < 0 || i < n) && cur.length; i++) {
|
|
2115
|
+
const j = cur.indexOf(sep);
|
|
2116
|
+
if (j < 0) break;
|
|
2117
|
+
r.push(cur.slice(0, j + sep.length));
|
|
2118
|
+
cur = cur.slice(j + sep.length);
|
|
2119
|
+
}
|
|
2120
|
+
r.push(cur);
|
|
2121
|
+
parts = r;
|
|
2122
|
+
return parts.length;
|
|
2123
|
+
},
|
|
2124
|
+
"str_fields": (s) => { parts = (s ?? "").split(/\\s+/).filter((p) => p !== ""); return parts.length; },
|
|
2125
|
+
"str_part": (i) => parts[i] ?? "",
|
|
2126
|
+
"str_replacer_init": (s) => {
|
|
2127
|
+
replacerTarget = s ?? "";
|
|
2128
|
+
replacerPairs = [];
|
|
2129
|
+
},
|
|
2130
|
+
"str_replacer_add": (o, n) => {
|
|
2131
|
+
replacerPairs.push([o ?? "", n ?? ""]);
|
|
2132
|
+
},
|
|
2133
|
+
"str_replacer_run": () => {
|
|
2134
|
+
let s = replacerTarget;
|
|
2135
|
+
if (!replacerPairs.length || s === "") return s;
|
|
2136
|
+
let out = "";
|
|
2137
|
+
let i = 0;
|
|
2138
|
+
while (i < s.length) {
|
|
2139
|
+
let matched = false;
|
|
2140
|
+
for (let k = 0; k < replacerPairs.length; k++) {
|
|
2141
|
+
const [oldStr, newStr] = replacerPairs[k];
|
|
2142
|
+
if (oldStr.length > 0 && s.startsWith(oldStr, i)) {
|
|
2143
|
+
out += newStr;
|
|
2144
|
+
i += oldStr.length;
|
|
2145
|
+
matched = true;
|
|
2146
|
+
break;
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
if (!matched) {
|
|
2150
|
+
out += s[i];
|
|
2151
|
+
i++;
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
return out;
|
|
2155
|
+
},
|
|
2156
|
+
"strconv_atoi": (s) => {
|
|
2157
|
+
const r = __strconv_atoi(s ?? "");
|
|
2158
|
+
strconvErr = r[1];
|
|
2159
|
+
strconvErrKind = r[1] ? (r[1].includes("invalid syntax") ? 1 : r[1].includes("value out of range") ? 2 : 0) : 0;
|
|
2160
|
+
return r[0];
|
|
2161
|
+
},
|
|
2162
|
+
"strconv_parse_int": (s, base, bits) => {
|
|
2163
|
+
const r = __strconv_parse_int(s ?? "", base, bits);
|
|
2164
|
+
strconvErr = r[1];
|
|
2165
|
+
strconvErrKind = r[1] ? (r[1].includes("invalid syntax") ? 1 : r[1].includes("value out of range") ? 2 : 0) : 0;
|
|
2166
|
+
return r[0];
|
|
2167
|
+
},
|
|
2168
|
+
"strconv_parse_float": (s) => {
|
|
2169
|
+
const r = __strconv_parse_float(s ?? "");
|
|
2170
|
+
strconvErr = r[1];
|
|
2171
|
+
strconvErrKind = r[1] ? (r[1].includes("invalid syntax") ? 1 : r[1].includes("value out of range") ? 2 : 0) : 0;
|
|
2172
|
+
return r[0];
|
|
2173
|
+
},
|
|
2174
|
+
"strconv_parse_bool": (s) => {
|
|
2175
|
+
const r = __strconv_parse_bool(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] ? 1 : 0;
|
|
2179
|
+
},
|
|
2180
|
+
"strconv_err": () => strconvErr,
|
|
2181
|
+
"strconv_err_kind": () => strconvErrKind,
|
|
2182
|
+
"strconv_format_float": (f, fmtc, prec, _bits) => __strconv_format_float(f, fmtc, prec),
|
|
2183
|
+
"strconv_format_int": (n, base) => n.toString(base),
|
|
2184
|
+
"strconv_format_uint": (n, base) => BigInt.asUintN(64, BigInt(n)).toString(base),
|
|
2185
|
+
"strconv_parse_uint": (s, base, bits) => {
|
|
2186
|
+
const r = __strconv_parse_uint(s ?? "", base, bits);
|
|
2187
|
+
strconvErr = r[1];
|
|
2188
|
+
strconvErrKind = r[1] ? (r[1].includes("invalid syntax") ? 1 : r[1].includes("value out of range") ? 2 : 0) : 0;
|
|
2189
|
+
return r[0];
|
|
2190
|
+
},
|
|
2191
|
+
"strconv_quote": (s) => JSON.stringify(s ?? ""),
|
|
2192
|
+
"strconv_unquote": (s) => {
|
|
2193
|
+
s = s ?? "";
|
|
2194
|
+
if (s.startsWith("\`") && s.endsWith("\`") && s.length >= 2) {
|
|
2195
|
+
if (s.slice(1, -1).includes("\`")) {
|
|
2196
|
+
strconvErr = "invalid syntax";
|
|
2197
|
+
strconvErrKind = 1;
|
|
2198
|
+
return "";
|
|
2199
|
+
}
|
|
2200
|
+
strconvErr = null;
|
|
2201
|
+
strconvErrKind = 0;
|
|
2202
|
+
return s.slice(1, -1);
|
|
2203
|
+
}
|
|
2204
|
+
try {
|
|
2205
|
+
const v = JSON.parse(s);
|
|
2206
|
+
if (typeof v !== "string") {
|
|
2207
|
+
strconvErr = "invalid syntax";
|
|
2208
|
+
strconvErrKind = 1;
|
|
2209
|
+
return "";
|
|
2210
|
+
}
|
|
2211
|
+
strconvErr = null;
|
|
2212
|
+
strconvErrKind = 0;
|
|
2213
|
+
return v;
|
|
2214
|
+
} catch (e) {
|
|
2215
|
+
strconvErr = "invalid syntax";
|
|
2216
|
+
strconvErrKind = 1;
|
|
2217
|
+
return "";
|
|
2218
|
+
}
|
|
2219
|
+
},
|
|
2220
|
+
"utf8_rune_count": (s) => { let n = 0; for (const _ of (s ?? "")) n++; return n; },
|
|
2221
|
+
"utf8_rune_len": (r) => (r < 0 || r > 0x10ffff || (r >= 0xd800 && r <= 0xdfff) ? -1 : r < 0x80 ? 1 : r < 0x800 ? 2 : r < 0x10000 ? 3 : 4),
|
|
2222
|
+
"utf8_valid_string": (s) => (s ?? "").isWellFormed ? ((s ?? "").isWellFormed() ? 1 : 0) : (/[\\uD800-\\uDFFF]/.test((s ?? "").replace(/[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]/g, "")) ? 0 : 1),
|
|
2223
|
+
"utf8_decode_rune": (s, last) => {
|
|
2224
|
+
s = s ?? "";
|
|
2225
|
+
if (s.length === 0) { decodeSize = 0; return 0xfffd; }
|
|
2226
|
+
let cp;
|
|
2227
|
+
if (last) { const tail = s.codePointAt(s.length - 1); cp = (tail >= 0xdc00 && tail <= 0xdfff && s.length >= 2) ? s.codePointAt(s.length - 2) : tail; }
|
|
2228
|
+
else cp = s.codePointAt(0);
|
|
2229
|
+
if (cp >= 0xd800 && cp <= 0xdfff) { decodeSize = 1; return 0xfffd; }
|
|
2230
|
+
// Size is in UTF-16 code units, matching the JS backend (strings are JS strings).
|
|
2231
|
+
decodeSize = cp > 0xffff ? 2 : 1;
|
|
2232
|
+
return cp;
|
|
2233
|
+
},
|
|
2234
|
+
"utf8_decode_size": () => decodeSize,
|
|
2235
|
+
"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; },
|
|
2236
|
+
// unicode
|
|
2237
|
+
"unicode_is_letter": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{L}/u.test(c)) ? 1 : 0; },
|
|
2238
|
+
"unicode_is_digit": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{Nd}/u.test(c)) ? 1 : 0; },
|
|
2239
|
+
"unicode_is_space": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\s/u.test(c)) ? 1 : 0; },
|
|
2240
|
+
"unicode_is_upper": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{Lu}/u.test(c)) ? 1 : 0; },
|
|
2241
|
+
"unicode_is_lower": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{Ll}/u.test(c)) ? 1 : 0; },
|
|
2242
|
+
"unicode_is_punct": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{P}/u.test(c)) ? 1 : 0; },
|
|
2243
|
+
"unicode_is_control": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{Cc}/u.test(c)) ? 1 : 0; },
|
|
2244
|
+
"unicode_is_print": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && !/\\p{Cc}/u.test(c)) ? 1 : 0; },
|
|
2245
|
+
"unicode_is_graphic": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && !/\\p{Cc}/u.test(c)) ? 1 : 0; },
|
|
2246
|
+
"unicode_to_upper": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return c ? (c.toUpperCase().codePointAt(0) ?? r) : r; },
|
|
2247
|
+
"unicode_to_lower": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return c ? (c.toLowerCase().codePointAt(0) ?? r) : r; },
|
|
2248
|
+
// html
|
|
2249
|
+
"html_escape": (s) => (s ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'"),
|
|
2250
|
+
"html_unescape": (s) => (s ?? "").replace(/&(?:quot|apos|#39|#34|gt|lt|amp|#(?:[0-9]+|[xX][0-9a-fA-F]+));/g, (m) => {
|
|
2251
|
+
switch (m) {
|
|
2252
|
+
case """: case """: return '"';
|
|
2253
|
+
case "'": case "'": return "'";
|
|
2254
|
+
case ">": return ">";
|
|
2255
|
+
case "<": return "<";
|
|
2256
|
+
case "&": return "&";
|
|
2257
|
+
default: {
|
|
2258
|
+
if (m.startsWith("&#")) {
|
|
2259
|
+
const raw = m.slice(2, -1);
|
|
2260
|
+
const cp = (raw.startsWith("x") || raw.startsWith("X")) ? parseInt(raw.slice(1), 16) : parseInt(raw, 10);
|
|
2261
|
+
return (cp >= 0 && cp <= 0x10FFFF && !(cp >= 0xD800 && cp <= 0xDFFF)) ? String.fromCodePoint(cp) : "\\uFFFD";
|
|
2262
|
+
}
|
|
2263
|
+
return m;
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
}),
|
|
2267
|
+
// path
|
|
2268
|
+
"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) || "/"; },
|
|
2269
|
+
"path_dir": (p) => { const s = p ?? ""; const i = s.lastIndexOf("/"); if (i < 0) return "."; const res = s.slice(0, i); return res === "" ? "/" : __pathClean(res); },
|
|
2270
|
+
"path_ext": (p) => { const s = p ?? ""; const b = s.slice(s.lastIndexOf("/") + 1); const i = b.lastIndexOf("."); return i <= 0 ? "" : b.slice(i); },
|
|
2271
|
+
"path_is_abs": (p) => (p ?? "").startsWith("/") ? 1 : 0,
|
|
2272
|
+
"path_clean": (p) => __pathClean(p ?? ""),
|
|
2273
|
+
"path_split": (p) => {
|
|
2274
|
+
const s = p ?? "";
|
|
2275
|
+
const i = s.lastIndexOf("/");
|
|
2276
|
+
if (i < 0) { splitDir = ""; splitFile = s; }
|
|
2277
|
+
else { splitDir = s.slice(0, i + 1); splitFile = s.slice(i + 1); }
|
|
2278
|
+
},
|
|
2279
|
+
"path_split_dir": () => splitDir,
|
|
2280
|
+
"path_split_file": () => splitFile,
|
|
2281
|
+
"path_join_init": () => { joinParts = []; },
|
|
2282
|
+
"path_join_push": (s) => { if (s) joinParts.push(s); },
|
|
2283
|
+
"path_join_run": () => {
|
|
2284
|
+
if (joinParts.length === 0) return "";
|
|
2285
|
+
const res = __pathClean(joinParts.join("/"));
|
|
2286
|
+
joinParts = [];
|
|
2287
|
+
return res;
|
|
2288
|
+
},
|
|
2289
|
+
"path_match": (pat, name) => {
|
|
2290
|
+
const r = __pathMatch(pat ?? "", name ?? "");
|
|
2291
|
+
matchHasErr = r[1] ? 1 : 0;
|
|
2292
|
+
return r[0] ? 1 : 0;
|
|
2293
|
+
},
|
|
2294
|
+
"path_match_has_err": () => matchHasErr,
|
|
2295
|
+
// bytes trim
|
|
2296
|
+
"bytes_trim": (s, cutset, left, right) => {
|
|
2297
|
+
const cs = new Set(Array.from(cutset ?? ""));
|
|
2298
|
+
const chars = Array.from(s ?? "");
|
|
2299
|
+
let a = 0, b = chars.length;
|
|
2300
|
+
if (left) while (a < b && cs.has(chars[a])) a++;
|
|
2301
|
+
if (right) while (b > a && cs.has(chars[b - 1])) b--;
|
|
2302
|
+
const enc = new TextEncoder();
|
|
2303
|
+
trimStart = a === 0 ? 0 : enc.encode(chars.slice(0, a).join("")).length;
|
|
2304
|
+
trimEnd = b === chars.length ? enc.encode(s ?? "").length : enc.encode(chars.slice(0, b).join("")).length;
|
|
2305
|
+
},
|
|
2306
|
+
"bytes_trim_start": () => trimStart,
|
|
2307
|
+
"bytes_trim_end": () => trimEnd,
|
|
2308
|
+
// time
|
|
2309
|
+
"time_now_ms": () => (typeof globalThis.__GOFRONT_CLOCK === "function" ? globalThis.__GOFRONT_CLOCK() : Date.now()),
|
|
2310
|
+
"time_timezone_offset": (ms) => -new Date(ms).getTimezoneOffset() * 60,
|
|
2311
|
+
"time_duration_string": (d) => __durationString(d),
|
|
2312
|
+
"time_parse": (layout, val) => {
|
|
2313
|
+
try {
|
|
2314
|
+
const d = new Date(val ?? "");
|
|
2315
|
+
if (isNaN(d.getTime())) throw 0;
|
|
2316
|
+
timeParseErr = null;
|
|
2317
|
+
timeParseOffset = -d.getTimezoneOffset() * 60;
|
|
2318
|
+
return d.getTime();
|
|
2319
|
+
} catch {
|
|
2320
|
+
timeParseErr = "parsing time: cannot parse";
|
|
2321
|
+
timeParseOffset = 0;
|
|
2322
|
+
return 0;
|
|
2323
|
+
}
|
|
2324
|
+
},
|
|
2325
|
+
"time_parse_err": () => timeParseErr,
|
|
2326
|
+
"time_parse_offset": () => timeParseOffset,
|
|
2327
|
+
"time_format": (y, m, d, hr, min, sec, wd, ms, offsetSec, layout) => {
|
|
2328
|
+
const DAYS = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];
|
|
2329
|
+
const MONTHS = ["January","February","March","April","May","June","July","August","September","October","November","December"];
|
|
2330
|
+
const pad = (n, w = 2) => String(n).padStart(w, "0");
|
|
2331
|
+
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 => {
|
|
2332
|
+
if (t.startsWith(".")) {
|
|
2333
|
+
const isZeroPad = t.includes("0");
|
|
2334
|
+
const maxLen = t.length - 1;
|
|
2335
|
+
let frac = pad(ms % 1000, 3);
|
|
2336
|
+
if (maxLen > 3) frac = frac.padEnd(maxLen, "0");
|
|
2337
|
+
else frac = frac.slice(0, maxLen);
|
|
2338
|
+
if (!isZeroPad) {
|
|
2339
|
+
frac = frac.replace(/0+$/, "");
|
|
2340
|
+
return frac.length > 0 ? "." + frac : "";
|
|
2341
|
+
}
|
|
2342
|
+
return "." + frac;
|
|
2343
|
+
}
|
|
2344
|
+
switch (t) {
|
|
2345
|
+
case "2006": return String(y);
|
|
2346
|
+
case "06": return String(y).slice(-2);
|
|
2347
|
+
case "Monday": return DAYS[wd];
|
|
2348
|
+
case "Mon": return DAYS[wd].slice(0, 3);
|
|
2349
|
+
case "January": return MONTHS[m - 1];
|
|
2350
|
+
case "Jan": return MONTHS[m - 1].slice(0, 3);
|
|
2351
|
+
case "01": return pad(m);
|
|
2352
|
+
case "1": return String(m);
|
|
2353
|
+
case "02": return pad(d);
|
|
2354
|
+
case "2": return String(d);
|
|
2355
|
+
case "15": return pad(hr);
|
|
2356
|
+
case "PM": return hr < 12 ? "AM" : "PM";
|
|
2357
|
+
case "pm": return hr < 12 ? "am" : "pm";
|
|
2358
|
+
case "03": return pad(hr % 12 || 12);
|
|
2359
|
+
case "3": return String(hr % 12 || 12);
|
|
2360
|
+
case "04": return pad(min);
|
|
2361
|
+
case "4": return String(min);
|
|
2362
|
+
case "05": return pad(sec);
|
|
2363
|
+
case "5": return String(sec);
|
|
2364
|
+
case "Z07:00": {
|
|
2365
|
+
const o = Math.floor(offsetSec / 60);
|
|
2366
|
+
return o === 0 ? "Z" : (o > 0 ? "+" : "-") + pad(Math.floor(Math.abs(o) / 60)) + ":" + pad(Math.abs(o) % 60);
|
|
2367
|
+
}
|
|
2368
|
+
default: return t;
|
|
2369
|
+
}
|
|
2370
|
+
});
|
|
2371
|
+
},
|
|
2372
|
+
"raw_bytes_init": (len) => {
|
|
2373
|
+
rawBytes = new Uint8Array(len);
|
|
2374
|
+
rawPos = 0;
|
|
2375
|
+
},
|
|
2376
|
+
"raw_bytes_push": (b) => {
|
|
2377
|
+
if (rawPos < rawBytes.length) {
|
|
2378
|
+
rawBytes[rawPos++] = b & 0xff;
|
|
2379
|
+
}
|
|
2380
|
+
},
|
|
2381
|
+
"raw_bytes_len": () => rawBytes.length,
|
|
2382
|
+
"raw_bytes_get": (i) => rawBytes[i] ?? 0,
|
|
2383
|
+
"crypto_rand_fill": (len) => {
|
|
2384
|
+
rawBytes = new Uint8Array(len);
|
|
2385
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
2386
|
+
const CHUNK = 65536;
|
|
2387
|
+
for (let offset = 0; offset < len; offset += CHUNK) {
|
|
2388
|
+
crypto.getRandomValues(rawBytes.subarray(offset, Math.min(offset + CHUNK, len)));
|
|
2389
|
+
}
|
|
2390
|
+
} else {
|
|
2391
|
+
for (let i = 0; i < len; i++) rawBytes[i] = Math.floor(Math.random() * 256);
|
|
2392
|
+
}
|
|
2393
|
+
return len;
|
|
2394
|
+
},
|
|
2395
|
+
"crypto_sha256_sum": () => {
|
|
2396
|
+
const d = __sha256.Sum256(rawBytes);
|
|
2397
|
+
rawBytes = new Uint8Array(d);
|
|
2398
|
+
return 32;
|
|
2399
|
+
},
|
|
2400
|
+
"hex_encode_to_string": () => {
|
|
2401
|
+
return __hex_encode_to_string(rawBytes);
|
|
2402
|
+
},
|
|
2403
|
+
"hex_decode_string": (s) => {
|
|
2404
|
+
const [res, err] = __hex_decode_string(s ?? "");
|
|
2405
|
+
hexErr = err ? (err.message ?? String(err)) : null;
|
|
2406
|
+
rawBytes = new Uint8Array(res);
|
|
2407
|
+
return rawBytes.length;
|
|
2408
|
+
},
|
|
2409
|
+
"hex_get_err": () => hexErr,
|
|
2410
|
+
"base64_new_encoding": (alphabet) => {
|
|
2411
|
+
const id = customBase64Encodings.length + 4;
|
|
2412
|
+
customBase64Encodings.push(__base64_make(alphabet ?? "", "="));
|
|
2413
|
+
return id;
|
|
2414
|
+
},
|
|
2415
|
+
"base64_encode_to_string": (mode) => {
|
|
2416
|
+
const enc = mode === 1 ? __base64_url : mode === 2 ? __base64_raw_std : mode === 3 ? __base64_raw_url : mode >= 4 ? customBase64Encodings[mode - 4] : __base64_std;
|
|
2417
|
+
return (enc ?? __base64_std).EncodeToString(rawBytes);
|
|
2418
|
+
},
|
|
2419
|
+
"base64_decode_string": (mode, s) => {
|
|
2420
|
+
const enc = mode === 1 ? __base64_url : mode === 2 ? __base64_raw_std : mode === 3 ? __base64_raw_url : mode >= 4 ? customBase64Encodings[mode - 4] : __base64_std;
|
|
2421
|
+
const [res, err] = (enc ?? __base64_std).DecodeString(s ?? "");
|
|
2422
|
+
base64Err = err ? (err.message ?? String(err)) : null;
|
|
2423
|
+
rawBytes = new Uint8Array(res);
|
|
2424
|
+
return rawBytes.length;
|
|
2425
|
+
},
|
|
2426
|
+
"base64_get_err": () => base64Err,
|
|
2427
|
+
};
|
|
2428
|
+
}
|
|
2429
|
+
function ${P}trim(s, cutset, left, right) {
|
|
2430
|
+
const cs = new Set(Array.from(cutset));
|
|
2431
|
+
const chars = Array.from(s);
|
|
2432
|
+
let a = 0, b = chars.length;
|
|
2433
|
+
if (left) while (a < b && cs.has(chars[a])) a++;
|
|
2434
|
+
if (right) while (b > a && cs.has(chars[b - 1])) b--;
|
|
2435
|
+
return chars.slice(a, b).join("");
|
|
1034
2436
|
}`;
|
|
1035
2437
|
|
|
1036
2438
|
// Runtime loader emitted at the top of the facade.
|
|
1037
|
-
|
|
2439
|
+
const wasmLoaderJs = (stdlibEnv) => `${WASM_IMPORTS_JS}
|
|
2440
|
+
${stdlibEnv ? WASM_STDLIB_JS : ""}
|
|
1038
2441
|
async function ${P}fetch(url) {
|
|
1039
2442
|
const res = await fetch(url);
|
|
1040
2443
|
if (!res.ok) throw new Error("GoFront: failed to fetch " + url + " (" + res.status + ")");
|
|
@@ -1043,7 +2446,8 @@ async function ${P}fetch(url) {
|
|
|
1043
2446
|
async function ${P}instantiate(imports) {
|
|
1044
2447
|
const bytes = globalThis.__GOFRONT_WASM_BYTES;
|
|
1045
2448
|
if (bytes) return WebAssembly.instantiate(bytes, imports);
|
|
1046
|
-
|
|
2449
|
+
// Resolve relative to the bundle so the app works from any page path.
|
|
2450
|
+
const url = globalThis.__GOFRONT_WASM_URL ?? new URL("app.wasm", import.meta.url).href;
|
|
1047
2451
|
if (typeof WebAssembly.instantiateStreaming === "function") {
|
|
1048
2452
|
try { return await WebAssembly.instantiateStreaming(fetch(url), imports); }
|
|
1049
2453
|
catch { /* fall through: wrong MIME type or no streaming support */ }
|
|
@@ -1060,6 +2464,7 @@ async function ${P}load(stringTable, extraEnv) {
|
|
|
1060
2464
|
// start function while package-level initializers run), so exports are
|
|
1061
2465
|
// returned raw: no try/catch wrapper, which keeps JS→wasm calls inlinable.
|
|
1062
2466
|
const result = await ${P}instantiate(imports);
|
|
2467
|
+
imports.__bind(result.instance.exports);
|
|
1063
2468
|
return result.instance.exports;
|
|
1064
2469
|
}
|
|
1065
2470
|
// Go int/int64 cross the boundary as f64 (exact within the safe-integer range).
|
|
@@ -1074,6 +2479,11 @@ const ${P}u64out = (v) => {
|
|
|
1074
2479
|
};
|
|
1075
2480
|
const ${P}strin = (v) => (v == null ? "" : String(v));
|
|
1076
2481
|
const ${P}href = (h) => (h == null ? null : h.__ref);
|
|
2482
|
+
// Tags a struct pointer held in an interface (same marker as the JS backend's __ifp).
|
|
2483
|
+
const ${P}ptag = (h) => {
|
|
2484
|
+
if (h !== null && h.__p !== true && Object.isExtensible(h)) Object.defineProperty(h, "__p", { "value": true, "configurable": true });
|
|
2485
|
+
return h;
|
|
2486
|
+
};
|
|
1077
2487
|
const ${P}NIL_DEREF_PATTERNS = [
|
|
1078
2488
|
"dereferencing a null pointer", // V8
|
|
1079
2489
|
"dereferencing null pointer", // SpiderMonkey
|
|
@@ -1110,16 +2520,19 @@ function ${P}idxview(n, getAt, setAt) {
|
|
|
1110
2520
|
// Generates the JS facade for the linked wasm module.
|
|
1111
2521
|
export function generateFacade(
|
|
1112
2522
|
meta,
|
|
1113
|
-
{ stringTable = [], callMain = false } = {},
|
|
2523
|
+
{ stringTable = [], callMain = false, stdlibEnv = true } = {},
|
|
1114
2524
|
) {
|
|
1115
2525
|
const needs = meta._needs ?? collectNeeds(meta);
|
|
1116
|
-
const out = [
|
|
2526
|
+
const out = [wasmLoaderJs(stdlibEnv)];
|
|
1117
2527
|
|
|
1118
2528
|
// JS callback imports (registered before instantiation; bodies run after).
|
|
1119
2529
|
out.push(`const ${P}env = {};`);
|
|
1120
2530
|
for (const [key, desc] of needs.funcs) {
|
|
1121
2531
|
const names = desc.params.map((_, i) => `a${i}`);
|
|
1122
|
-
const
|
|
2532
|
+
const args = desc.params.map((d, i) =>
|
|
2533
|
+
isPtrBoth(d) ? `${P}view_${d.name}(${names[i]})` : outExpr(d, names[i]),
|
|
2534
|
+
);
|
|
2535
|
+
const call = `fn(${args.join(", ")})`;
|
|
1123
2536
|
let body;
|
|
1124
2537
|
if (desc.returns.length === 0) body = `${call};`;
|
|
1125
2538
|
else if (desc.returns.length === 1)
|
|
@@ -1129,7 +2542,9 @@ export function generateFacade(
|
|
|
1129
2542
|
.map((d, i) => inExpr(d, `__r[${i}]`))
|
|
1130
2543
|
.join(", ")}];`;
|
|
1131
2544
|
const sig = ["fn", ...names].join(", ");
|
|
1132
|
-
out.push(
|
|
2545
|
+
out.push(
|
|
2546
|
+
`${P}env["__invoke$${key}"] = (${sig}) => { try { ${body} } catch (__e) { if (__w && __w.__push_panic) __w.__push_panic(__e.message ?? String(__e)); throw __e; } };`,
|
|
2547
|
+
);
|
|
1133
2548
|
}
|
|
1134
2549
|
out.push(
|
|
1135
2550
|
`const __w = await ${P}load(${JSON.stringify(stringTable)}, ${P}env);`,
|
|
@@ -1143,6 +2558,39 @@ export function generateFacade(
|
|
|
1143
2558
|
}
|
|
1144
2559
|
}
|
|
1145
2560
|
|
|
2561
|
+
for (const [name, desc] of needs.anonStructs) {
|
|
2562
|
+
out.push(`function ${P}to_${name}(o) {
|
|
2563
|
+
if (o == null) return __w.__zero_${name}();
|
|
2564
|
+
return __w.__new_${name}(${desc.fields.map((f) => inExpr(f.desc, `(o.${f.name} ?? ${zeroExpr(f.desc)})`)).join(", ")});
|
|
2565
|
+
}
|
|
2566
|
+
function ${P}from_${name}(r) {
|
|
2567
|
+
if (r == null) return null;
|
|
2568
|
+
return {
|
|
2569
|
+
${desc.fields.map((f) => `\t\t"${f.name}": ${outExpr(f.desc, `__w.__get_${name}_${f.name}(r)`)}`).join(",\n")}
|
|
2570
|
+
};
|
|
2571
|
+
}
|
|
2572
|
+
function ${P}back_${name}(r, o) {
|
|
2573
|
+
if (r == null || o == null) return;
|
|
2574
|
+
${desc.fields.map((f) => `\to.${f.name} = ${outExpr(f.desc, `__w.__get_${name}_${f.name}(r)`)};`).join("\n")}
|
|
2575
|
+
}`);
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2578
|
+
for (const [key, desc] of needs.boxes) {
|
|
2579
|
+
out.push(`function ${P}boxin_${key}(b) {
|
|
2580
|
+
if (b == null) return null;
|
|
2581
|
+
const v = b.value !== undefined ? b.value : ${zeroExpr(desc.base)};
|
|
2582
|
+
return __w.__box_new_${key}(${inExpr(desc.base, "v")});
|
|
2583
|
+
}
|
|
2584
|
+
function ${P}boxout_${key}(r) {
|
|
2585
|
+
if (r == null) return null;
|
|
2586
|
+
return { "value": ${outExpr(desc.base, `__w.__box_get_${key}(r)`)} };
|
|
2587
|
+
}
|
|
2588
|
+
function ${P}boxback_${key}(r, b) {
|
|
2589
|
+
if (r == null || b == null) return;
|
|
2590
|
+
b.value = ${outExpr(desc.base, `__w.__box_get_${key}(r)`)};
|
|
2591
|
+
}`);
|
|
2592
|
+
}
|
|
2593
|
+
|
|
1146
2594
|
for (const [key, desc] of needs.slices) {
|
|
1147
2595
|
out.push(`function ${P}slin_${key}(arr) {
|
|
1148
2596
|
if (arr == null) return __w.__slice_nil_${key}();
|
|
@@ -1161,8 +2609,34 @@ function ${P}slout_${key}(s) {
|
|
|
1161
2609
|
function ${P}slview_${key}(s) {
|
|
1162
2610
|
if (s == null || __w.__slice_is_nil_${key}(s)) return null;
|
|
1163
2611
|
return ${P}idxview(__w.__slice_len_${key}(s), (i) => ${viewExpr(desc.elem, `__w.__slice_get_${key}(s, i)`)}, (i, v) => __w.__slice_set_${key}(s, i, ${inExpr(desc.elem, "v")}));
|
|
2612
|
+
} `);
|
|
2613
|
+
}
|
|
2614
|
+
|
|
2615
|
+
for (const [key, desc] of needs.maps) {
|
|
2616
|
+
out.push(`function ${P}mapin_${key}(m) {
|
|
2617
|
+
if (m == null) return null;
|
|
2618
|
+
const entries = m instanceof Map ? m.entries() : Object.entries(m);
|
|
2619
|
+
const size = m instanceof Map ? m.size : Object.keys(m).length;
|
|
2620
|
+
const r = __w.__map_new_${key}(size);
|
|
2621
|
+
for (const [k, v] of entries) {
|
|
2622
|
+
__w.__map_set_${key}(r, ${desc.keyDesc.k === "bool" ? `(k === "true" || k === true ? 1 : 0)` : inExpr(desc.keyDesc, "k")}, ${inExpr(desc.valDesc, "v")});
|
|
2623
|
+
}
|
|
2624
|
+
return r;
|
|
2625
|
+
}
|
|
2626
|
+
function ${P}mapout_${key}(m) {
|
|
2627
|
+
if (m == null) return null;
|
|
2628
|
+
const ks = ${P}slout_${desc.keySliceDesc.key}(__w.__map_keys_${key}(m));
|
|
2629
|
+
const vs = ${P}slout_${desc.valSliceDesc.key}(__w.__map_values_${key}(m));
|
|
2630
|
+
const out = new Map();
|
|
2631
|
+
if (ks) {
|
|
2632
|
+
for (let i = 0; i < ks.length; i++) {
|
|
2633
|
+
out.set(ks[i], vs[i]);
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
return out;
|
|
1164
2637
|
}`);
|
|
1165
2638
|
}
|
|
2639
|
+
|
|
1166
2640
|
for (const [key, desc] of needs.arrays) {
|
|
1167
2641
|
out.push(`function ${P}arrzero_${key}(n) {
|
|
1168
2642
|
const out = new Array(n);
|
|
@@ -1186,7 +2660,7 @@ function ${P}arrout_${key}(a) {
|
|
|
1186
2660
|
function ${P}arrview_${key}(a) {
|
|
1187
2661
|
if (a == null) return null;
|
|
1188
2662
|
return ${P}idxview(__w.__array_len_${key}(a), (i) => ${viewExpr(desc.elem, `__w.__array_get_${key}(a, i)`)}, (i, v) => __w.__array_set_${key}(a, i, ${inExpr(desc.elem, "v")}));
|
|
1189
|
-
}`);
|
|
2663
|
+
} `);
|
|
1190
2664
|
}
|
|
1191
2665
|
|
|
1192
2666
|
for (const [key, desc] of needs.funcs) {
|
|
@@ -1196,22 +2670,122 @@ function ${P}arrview_${key}(a) {
|
|
|
1196
2670
|
desc.returns,
|
|
1197
2671
|
["c"],
|
|
1198
2672
|
);
|
|
1199
|
-
out.push(`const ${P}
|
|
2673
|
+
out.push(`const ${P}fn_j2w_${key} = new WeakMap();
|
|
2674
|
+
const ${P}fn_w2j_${key} = new WeakMap();
|
|
1200
2675
|
function ${P}fnin_${key}(fn) {
|
|
1201
2676
|
if (fn == null) return null;
|
|
1202
|
-
let c = ${P}
|
|
1203
|
-
if (!c) {
|
|
2677
|
+
let c = ${P}fn_j2w_${key}.get(fn);
|
|
2678
|
+
if (!c) {
|
|
2679
|
+
c = __w.__wrap_fn$${key}(fn);
|
|
2680
|
+
${P}fn_j2w_${key}.set(fn, c);
|
|
2681
|
+
${P}fn_w2j_${key}.set(c, fn);
|
|
2682
|
+
}
|
|
1204
2683
|
return c;
|
|
1205
2684
|
}
|
|
1206
2685
|
function ${P}fnout_${key}(c) {
|
|
1207
2686
|
if (c == null) return null;
|
|
1208
|
-
|
|
2687
|
+
let fn = ${P}fn_w2j_${key}.get(c);
|
|
2688
|
+
if (!fn) {
|
|
2689
|
+
fn = (${names.join(", ")}) => {
|
|
1209
2690
|
${indent(body, "\t")}
|
|
1210
|
-
|
|
2691
|
+
};
|
|
2692
|
+
fn.__ref = c;
|
|
2693
|
+
${P}fn_w2j_${key}.set(c, fn);
|
|
2694
|
+
${P}fn_j2w_${key}.set(fn, c);
|
|
2695
|
+
}
|
|
2696
|
+
return fn;
|
|
2697
|
+
}`);
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2700
|
+
for (const [key, desc] of needs.ifaces) genIfaceFacade(out, key, desc);
|
|
2701
|
+
|
|
2702
|
+
if (needs.error) {
|
|
2703
|
+
out.push(`class ${P}Error extends Error {
|
|
2704
|
+
constructor(msg, ref) {
|
|
2705
|
+
super(msg);
|
|
2706
|
+
this.name = "GoError";
|
|
2707
|
+
this._msg = msg;
|
|
2708
|
+
this.__ref = ref;
|
|
2709
|
+
}
|
|
2710
|
+
Error() {
|
|
2711
|
+
return this.message;
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
const ${P}err_w2j = new WeakMap();
|
|
2715
|
+
function ${P}errout(r) {
|
|
2716
|
+
if (r == null) return null;
|
|
2717
|
+
let e = ${P}err_w2j.get(r);
|
|
2718
|
+
if (!e) {
|
|
2719
|
+
const msg = __w.__error_msg ? (__w.__error_msg(r) ?? "") : "";
|
|
2720
|
+
e = new ${P}Error(msg, r);
|
|
2721
|
+
${P}err_w2j.set(r, e);
|
|
2722
|
+
}
|
|
2723
|
+
return e;
|
|
2724
|
+
}
|
|
2725
|
+
function ${P}errin(e) {
|
|
2726
|
+
if (e == null) return null;
|
|
2727
|
+
if (e.__ref === undefined) throw new TypeError("GoFront: expected a WASM-owned error");
|
|
2728
|
+
if (!__w.__is_error(e.__ref)) throw new TypeError("GoFront: value does not implement error");
|
|
2729
|
+
return e.__ref;
|
|
2730
|
+
}`);
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2733
|
+
if (needs.time) {
|
|
2734
|
+
out.push(`function ${P}timeout(r) {
|
|
2735
|
+
if (r == null) return null;
|
|
2736
|
+
const ms = __w.__time_unix_milli ? __w.__time_unix_milli(r) : 0;
|
|
2737
|
+
const off = __w.__time_offset ? __w.__time_offset(r) : 0;
|
|
2738
|
+
return { _d: new Date(ms), _offset: off };
|
|
2739
|
+
}
|
|
2740
|
+
function ${P}timeout_ptr(r) {
|
|
2741
|
+
return ${P}timeout(r);
|
|
2742
|
+
}
|
|
2743
|
+
function ${P}timein(v) {
|
|
2744
|
+
if (v == null) return null;
|
|
2745
|
+
const d = v._d ? v._d : (v instanceof Date ? v : new Date(v));
|
|
2746
|
+
const ms = d.getTime();
|
|
2747
|
+
const off = typeof v?._offset === "number" ? v._offset : -d.getTimezoneOffset() * 60;
|
|
2748
|
+
return __w.__time_make ? __w.__time_make(ms, off) : null;
|
|
2749
|
+
}
|
|
2750
|
+
function ${P}timein_ptr(v) {
|
|
2751
|
+
return ${P}timein(v);
|
|
2752
|
+
}`);
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
if (needs.shared) {
|
|
2756
|
+
// Zero-copy TypedArray views over the exported memory. Memory never
|
|
2757
|
+
// grows after the start function (allocation is startup-only), but the
|
|
2758
|
+
// cached view is still re-validated against the live buffer.
|
|
2759
|
+
out.push(`const ${P}shc = new WeakMap();
|
|
2760
|
+
function ${P}shout(r, Ctor) {
|
|
2761
|
+
if (r == null) return null;
|
|
2762
|
+
let v = ${P}shc.get(r);
|
|
2763
|
+
if (!v || v.buffer !== __w.memory.buffer) {
|
|
2764
|
+
v = new Ctor(__w.memory.buffer, __w.__shared_base(r), __w.__shared_len(r));
|
|
2765
|
+
${P}shc.set(r, v);
|
|
2766
|
+
}
|
|
2767
|
+
return v;
|
|
2768
|
+
}
|
|
2769
|
+
function ${P}shin(v, Ctor) {
|
|
2770
|
+
if (v == null) return null;
|
|
2771
|
+
if (!(v instanceof Ctor) || v.buffer !== __w.memory.buffer) throw new TypeError("GoFront: expected a " + Ctor.name + " view over the wasm shared memory");
|
|
2772
|
+
return __w.__shared_make(v.byteOffset, v.length);
|
|
1211
2773
|
}`);
|
|
1212
2774
|
}
|
|
1213
2775
|
|
|
1214
|
-
for (const c of meta.consts)
|
|
2776
|
+
for (const c of meta.consts) {
|
|
2777
|
+
if (c.dynamic) {
|
|
2778
|
+
out.push(
|
|
2779
|
+
`const ${c.name} = ${outExpr(c.desc, `__w.__const_${c.name}()`)};`,
|
|
2780
|
+
);
|
|
2781
|
+
} else {
|
|
2782
|
+
out.push(`const ${c.name} = ${c.value};`);
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
for (const v of meta.vars ?? [])
|
|
2786
|
+
out.push(
|
|
2787
|
+
`const ${v.name} = ${P}shout(__w.__var_${v.name}(), ${v.desc.ctor});`,
|
|
2788
|
+
);
|
|
1215
2789
|
|
|
1216
2790
|
for (const f of meta.funcs) {
|
|
1217
2791
|
const { names, body } = genFuncBody(
|
|
@@ -1298,21 +2872,66 @@ ${backLines.join("\n")}
|
|
|
1298
2872
|
}`);
|
|
1299
2873
|
}
|
|
1300
2874
|
|
|
2875
|
+
function genIfaceFacade(out, key, desc) {
|
|
2876
|
+
const methods = desc.methods.map((m) => {
|
|
2877
|
+
const { names, body } = genFuncBody(m.callName, m.params, m.returns, [
|
|
2878
|
+
"this.__ref",
|
|
2879
|
+
]);
|
|
2880
|
+
return `\t${m.name}(${names.join(", ")}) {\n${indent(body, "\t")}\n\t}`;
|
|
2881
|
+
});
|
|
2882
|
+
const cases = desc.concrete.map((c, i) => {
|
|
2883
|
+
const wrap = c.both ? `${P}view_${c.name}(r)` : `${c.name}.__wrap(r)`;
|
|
2884
|
+
return `\t\tcase ${i + 1}: return ${P}ptag(${wrap});`;
|
|
2885
|
+
});
|
|
2886
|
+
const tag =
|
|
2887
|
+
cases.length > 0
|
|
2888
|
+
? `\tswitch (__w.__itag$${key}(r)) {\n${cases.join("\n")}\n\t}\n`
|
|
2889
|
+
: "";
|
|
2890
|
+
const msg = `GoFront: a JS-implemented value cannot cross into wasm as interface '${desc.name}'; implement it in a wasm package or pass a func callback`;
|
|
2891
|
+
const implMsg = `GoFront: wasm value does not implement interface '${desc.name}'`;
|
|
2892
|
+
out.push(`class ${P}I_${key} {
|
|
2893
|
+
constructor(r) { this.__ref = r; }
|
|
2894
|
+
${methods.join("\n")}
|
|
2895
|
+
}
|
|
2896
|
+
const ${P}ic_${key} = new WeakMap();
|
|
2897
|
+
function ${P}ifout_${key}(r) {
|
|
2898
|
+
if (r == null) return null;
|
|
2899
|
+
${tag} let o = ${P}ic_${key}.get(r);
|
|
2900
|
+
if (!o) { o = new ${P}I_${key}(r); ${P}ic_${key}.set(r, o); }
|
|
2901
|
+
return o;
|
|
2902
|
+
}
|
|
2903
|
+
function ${P}ifin_${key}(v) {
|
|
2904
|
+
if (v == null) return null;
|
|
2905
|
+
if (v.__ref === undefined) throw new TypeError(${JSON.stringify(msg)});
|
|
2906
|
+
if (!__w.__impl$${key}(v.__ref)) throw new TypeError(${JSON.stringify(implMsg)});
|
|
2907
|
+
return v.__ref;
|
|
2908
|
+
}`);
|
|
2909
|
+
}
|
|
2910
|
+
|
|
1301
2911
|
function genHandleStruct(out, name, fields, methods) {
|
|
1302
2912
|
// `$`-suffixed params keep the mangler from renaming the matching getters.
|
|
1303
2913
|
const ctorParams = fields.map((f) => `${f.name}$ = ${zeroExpr(f.desc)}`);
|
|
1304
2914
|
const ctorArgs = fields.map((f) => inExpr(f.desc, `${f.name}$`));
|
|
2915
|
+
const fromArgs = fields.map((f) => `o.${f.name}`);
|
|
2916
|
+
const ptrMsg = `GoFront: expected a ${name} handle for a *${name} parameter (use ${name}.from(obj) to create one)`;
|
|
1305
2917
|
const lines = [
|
|
1306
2918
|
`const ${P}h_${name} = new WeakMap();
|
|
1307
2919
|
function ${P}hval_${name}(h) {
|
|
1308
2920
|
if (h == null) return __w.__zero_${name}();
|
|
1309
|
-
return __w.__clone_${name}(h.__ref);
|
|
2921
|
+
if (h.__ref !== undefined) return __w.__clone_${name}(h.__ref);
|
|
2922
|
+
return ${name}.from(h).__ref;
|
|
2923
|
+
}
|
|
2924
|
+
function ${P}href_${name}(h) {
|
|
2925
|
+
if (h == null) return null;
|
|
2926
|
+
if (h.__ref !== undefined) return h.__ref;
|
|
2927
|
+
throw new TypeError(${JSON.stringify(ptrMsg)});
|
|
1310
2928
|
}
|
|
1311
2929
|
class ${name} {
|
|
1312
2930
|
constructor(${ctorParams.join(", ")}) {
|
|
1313
2931
|
this.__ref = __w.__new_${name}(${ctorArgs.join(", ")});
|
|
1314
2932
|
${P}h_${name}.set(this.__ref, this);
|
|
1315
2933
|
}
|
|
2934
|
+
static from(o) { return new ${name}(${fromArgs.join(", ")}); }
|
|
1316
2935
|
static __wrap(ref) {
|
|
1317
2936
|
if (ref == null) return null;
|
|
1318
2937
|
let h = ${P}h_${name}.get(ref);
|