gofront 1.7.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -14
- package/package.json +10 -38
- package/src/backend/js/expressions.js +2060 -389
- package/src/backend/js/index.js +586 -174
- package/src/backend/js/natives.js +383 -0
- package/src/backend/js/runtime.js +936 -1644
- package/src/backend/js/statements.js +1349 -132
- package/src/backend/js/stdlib/index.js +51 -94
- package/src/backend/js/templ.js +67 -118
- package/src/backend/js/type-info.js +436 -0
- package/src/backend/shared/fixed-float.js +20 -0
- package/src/backend/shared/fmt-types.js +80 -0
- package/src/backend/shared/host-unicode.js +36 -0
- package/src/backend/wasm/boundary.js +753 -872
- package/src/backend/wasm/emit-builtins.js +914 -237
- package/src/backend/wasm/emit-exprs.js +1456 -195
- package/src/backend/wasm/emit-maps.js +78 -380
- package/src/backend/wasm/emit-stmts.js +1659 -214
- package/src/backend/wasm/emit.js +614 -88
- package/src/backend/wasm/encode.js +43 -13
- package/src/backend/wasm/globals.js +491 -0
- package/src/backend/wasm/glue.js +2 -4
- package/src/backend/wasm/index.js +406 -3195
- package/src/backend/wasm/method-expr.js +392 -0
- package/src/backend/wasm/module-emitter.js +1615 -0
- package/src/backend/wasm/monomorph.js +164 -9
- package/src/backend/wasm/natives.js +596 -0
- package/src/backend/wasm/prescan.js +249 -0
- package/src/backend/wasm/prune-funcs.js +89 -0
- package/src/backend/wasm/stdlib/index.js +2 -37
- package/src/backend/wasm/stdlib/simd.js +293 -28
- package/src/backend/wasm/types.js +260 -27
- package/src/backend/wasm/variadic.js +93 -0
- package/src/backend/wasm/wat.js +47 -20
- package/src/{cli-core.js → cli/cli-core.js} +241 -11
- package/src/{test-runner.js → cli/test-runner.js} +188 -122
- package/src/{vendor.js → cli/vendor.js} +3 -4
- package/src/compiler.js +202 -386
- package/src/index.js +15 -169
- package/src/{lexer.js → lexer/lexer.js} +203 -62
- package/src/{templ-lexer.js → lexer/templ-lexer.js} +61 -18
- package/src/{tokens.js → lexer/tokens.js} +2 -0
- package/src/link/sourcemap.js +97 -0
- package/src/link/stdlib.js +737 -0
- package/src/link/type-names.js +148 -0
- package/src/link/wasm.js +318 -0
- package/src/lower/bounds.js +547 -0
- package/src/lower/boxing.js +1 -1
- package/src/lower/captures.js +267 -64
- package/src/lower/embedding.js +32 -3
- package/src/lower/errors-as.js +196 -0
- package/src/lower/fmt-formatters.js +905 -0
- package/src/lower/functions.js +2 -0
- package/src/lower/gen-types.js +274 -0
- package/src/lower/index.js +33 -19
- package/src/lower/json-codecs.js +1379 -0
- package/src/lower/ownership.js +47 -8
- package/src/lower/range.js +86 -1
- package/src/lower/reachability.js +150 -0
- package/src/lower/walk.js +12 -0
- package/src/parser/declarations.js +83 -15
- package/src/parser/expressions.js +110 -18
- package/src/parser/index.js +2 -1
- package/src/parser/statements.js +38 -22
- package/src/{templ-parser.js → parser/templ.js} +179 -17
- package/src/parser/types.js +70 -9
- package/src/resolver.js +21 -1
- package/src/typechecker/assignability.js +291 -19
- package/src/typechecker/const-fold.js +399 -0
- package/src/typechecker/constants.js +282 -0
- package/src/typechecker/expressions.js +1220 -183
- package/src/typechecker/index.js +659 -92
- package/src/typechecker/init-order.js +426 -0
- package/src/typechecker/operand-text.js +40 -0
- package/src/typechecker/resolve.js +613 -62
- package/src/typechecker/statements.js +212 -20
- package/src/typechecker/stdlib/excluded.js +902 -0
- package/src/typechecker/stdlib/index.js +38 -0
- package/src/typechecker/stdlib/simd.js +65 -15
- package/src/typechecker/termination.js +11 -2
- package/src/typechecker/types.js +268 -514
- package/stdlib/bytes/buffer.go +479 -0
- package/stdlib/bytes/bytes.go +1010 -0
- package/stdlib/bytes/reader.go +161 -0
- package/stdlib/cmp/cmp.go +65 -0
- package/stdlib/context/context.go +115 -0
- package/stdlib/crypto/rand/rand.go +34 -0
- package/stdlib/crypto/sha256/sha256.go +249 -0
- package/stdlib/encoding/base64/base64.go +331 -0
- package/stdlib/encoding/binary/binary.go +332 -0
- package/stdlib/encoding/hex/hex.go +140 -0
- package/stdlib/encoding/json/errors.go +31 -0
- package/stdlib/encoding/json/indent.go +71 -0
- package/stdlib/encoding/json/json.go +62 -0
- package/stdlib/encoding/json/number.go +12 -0
- package/stdlib/encoding/json/stream.go +173 -0
- package/stdlib/errors/errors.go +174 -0
- package/stdlib/fmt/errors.go +110 -0
- package/stdlib/fmt/format.go +732 -0
- package/stdlib/fmt/print.go +1232 -0
- package/stdlib/gom/gom.go +293 -0
- package/stdlib/hash/hash.go +53 -0
- package/stdlib/html/html.go +251 -0
- package/stdlib/internal/fmtrt/fmtrt.go +163 -0
- package/stdlib/internal/jsonrt/dec.go +871 -0
- package/stdlib/internal/jsonrt/enc.go +516 -0
- package/stdlib/internal/jsonrt/indent.go +135 -0
- package/stdlib/internal/jsonrt/number.go +27 -0
- package/stdlib/internal/jsonrt/reader.go +73 -0
- package/stdlib/internal/jsonrt/scanner.go +658 -0
- package/stdlib/io/io.go +502 -0
- package/stdlib/iter/iter.go +18 -0
- package/stdlib/maps/maps.go +131 -0
- package/stdlib/math/bits/bits.go +507 -0
- package/stdlib/math/extra.go +272 -0
- package/stdlib/math/math.go +439 -0
- package/stdlib/math/rand/rand.go +297 -0
- package/stdlib/os/os.go +93 -0
- package/stdlib/path/filepath/filepath.go +47 -0
- package/stdlib/path/path.go +394 -0
- package/stdlib/regexp/regexp.go +231 -0
- package/stdlib/slices/slices.go +526 -0
- package/stdlib/slices/sort.go +209 -0
- package/stdlib/slices/zsort_cmp.go +480 -0
- package/stdlib/slices/zsort_ordered.go +482 -0
- package/stdlib/sort/sort.go +500 -0
- package/stdlib/sort/zsort_func.go +480 -0
- package/stdlib/sort/zsort_iface.go +480 -0
- package/stdlib/strconv/float.go +1699 -0
- package/stdlib/strconv/isprint.go +752 -0
- package/stdlib/strconv/quote.go +526 -0
- package/stdlib/strconv/strconv.go +616 -0
- package/stdlib/strings/builder.go +95 -0
- package/stdlib/strings/reader.go +163 -0
- package/stdlib/strings/replacer.go +102 -0
- package/stdlib/strings/strings.go +807 -0
- package/stdlib/sync/sync.go +268 -0
- package/stdlib/testing/async_js.go +61 -0
- package/stdlib/testing/benchmark.go +297 -0
- package/stdlib/testing/testing.go +523 -0
- package/stdlib/time/duration.go +233 -0
- package/stdlib/time/format.go +745 -0
- package/stdlib/time/parse.go +790 -0
- package/stdlib/time/sleep_js.go +63 -0
- package/stdlib/time/time.go +834 -0
- package/stdlib/unicode/unicode.go +250 -0
- package/stdlib/unicode/utf8/utf8.go +464 -0
- package/src/backend/js/stdlib/base64.js +0 -12
- package/src/backend/js/stdlib/binary.js +0 -102
- package/src/backend/js/stdlib/bits.js +0 -46
- package/src/backend/js/stdlib/builder.js +0 -63
- package/src/backend/js/stdlib/bytes.js +0 -122
- package/src/backend/js/stdlib/errors.js +0 -44
- package/src/backend/js/stdlib/fmt.js +0 -178
- package/src/backend/js/stdlib/gom.js +0 -155
- package/src/backend/js/stdlib/hex.js +0 -17
- package/src/backend/js/stdlib/html.js +0 -20
- package/src/backend/js/stdlib/io.js +0 -137
- package/src/backend/js/stdlib/json.js +0 -972
- package/src/backend/js/stdlib/maps.js +0 -45
- package/src/backend/js/stdlib/math.js +0 -69
- package/src/backend/js/stdlib/os.js +0 -13
- package/src/backend/js/stdlib/path.js +0 -48
- package/src/backend/js/stdlib/rand.js +0 -32
- package/src/backend/js/stdlib/regexp.js +0 -54
- package/src/backend/js/stdlib/sha256.js +0 -12
- package/src/backend/js/stdlib/shared.js +0 -17
- package/src/backend/js/stdlib/simd.js +0 -24
- package/src/backend/js/stdlib/slices.js +0 -77
- package/src/backend/js/stdlib/sort.js +0 -30
- package/src/backend/js/stdlib/strconv.js +0 -74
- package/src/backend/js/stdlib/strings.js +0 -222
- package/src/backend/js/stdlib/sync.js +0 -13
- package/src/backend/js/stdlib/testing.js +0 -34
- package/src/backend/js/stdlib/time.js +0 -128
- package/src/backend/js/stdlib/unicode.js +0 -25
- package/src/backend/js/stdlib/utf8.js +0 -26
- package/src/backend/wasm/const-fold.js +0 -205
- package/src/backend/wasm/emit-stdlib.js +0 -13
- package/src/backend/wasm/stdlib/bytes.js +0 -5039
- package/src/backend/wasm/stdlib/core.js +0 -8903
- package/src/backend/wasm/stdlib/io.js +0 -2161
- package/src/backend/wasm/stdlib/packages.js +0 -2400
- package/src/backend/wasm/stdlib/pure.js +0 -720
- package/src/backend/wasm/stdlib/time.js +0 -1200
- package/src/typechecker/stdlib/core.js +0 -801
- package/src/typechecker/stdlib/extended.js +0 -914
- package/src/typechecker/stdlib.js +0 -22
- /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
- /package/src/{colors.js → cli/colors.js} +0 -0
- /package/src/{dev-server.js → cli/dev-server.js} +0 -0
- /package/src/{minifier.js → cli/minifier.js} +0 -0
- /package/src/{project-config.js → cli/project-config.js} +0 -0
- /package/src/{pwa.js → cli/pwa.js} +0 -0
|
@@ -2,23 +2,13 @@
|
|
|
2
2
|
// Maps GoFront types to WasmGC types and manages type registry.
|
|
3
3
|
|
|
4
4
|
import { SHARED_KINDS } from "../../typechecker/stdlib/shared.js";
|
|
5
|
+
import { isComplex, isComplex64 } from "../../typechecker/types.js";
|
|
6
|
+
|
|
7
|
+
export { isComplex, isComplex64 };
|
|
5
8
|
|
|
6
9
|
// Name of the runtime's error struct (`errors.New`, `fmt.Errorf`, strconv).
|
|
7
10
|
export const ERROR_STRUCT = "__errorString";
|
|
8
11
|
|
|
9
|
-
// `*testing.T` is a JS-side object (the harness's __GoFront_T) passed through
|
|
10
|
-
// the boundary as an opaque externref.
|
|
11
|
-
export function isTestingT(goType) {
|
|
12
|
-
if (!goType) return false;
|
|
13
|
-
const base =
|
|
14
|
-
goType.kind === "pointer" || goType.kind === "PointerType"
|
|
15
|
-
? goType.base
|
|
16
|
-
: goType.kind === "StarExpr"
|
|
17
|
-
? (goType.expr ?? goType.operand)
|
|
18
|
-
: null;
|
|
19
|
-
return base?.name === "testing.T";
|
|
20
|
-
}
|
|
21
|
-
|
|
22
12
|
export function toWasmType(goType, checker = null, mod = null) {
|
|
23
13
|
if (!goType) return "i32";
|
|
24
14
|
if (typeof goType === "string") {
|
|
@@ -38,7 +28,6 @@ export function toWasmType(goType, checker = null, mod = null) {
|
|
|
38
28
|
if (typeof goType === "object" && goType.kind === "ref") {
|
|
39
29
|
return goType;
|
|
40
30
|
}
|
|
41
|
-
if (isTestingT(goType)) return "externref";
|
|
42
31
|
|
|
43
32
|
if (
|
|
44
33
|
goType.kind === "simd" ||
|
|
@@ -105,16 +94,25 @@ export function toWasmType(goType, checker = null, mod = null) {
|
|
|
105
94
|
) {
|
|
106
95
|
return "v128";
|
|
107
96
|
}
|
|
108
|
-
|
|
97
|
+
// An instance written `Box[int]` is the monomorphised struct `Box$int`.
|
|
98
|
+
const instance = goType.typeArgs?.length
|
|
99
|
+
? mod?.getStructType(`${goType.name}$${instanceArgsKey(goType.typeArgs)}`)
|
|
100
|
+
: null;
|
|
101
|
+
const struct = instance ?? mod?.getStructType(goType.name);
|
|
102
|
+
if (struct) {
|
|
109
103
|
return {
|
|
110
104
|
kind: "ref",
|
|
111
105
|
nullable: true,
|
|
112
|
-
typeIndex:
|
|
106
|
+
typeIndex: struct.typeIndex,
|
|
113
107
|
};
|
|
114
108
|
}
|
|
115
109
|
if (goType.underlying) {
|
|
116
110
|
return toWasmType(goType.underlying, checker, mod);
|
|
117
111
|
}
|
|
112
|
+
// A bare `{ kind: "named", name }` (method receivers): look it up.
|
|
113
|
+
const resolved = checker?.types?.get(goType.name);
|
|
114
|
+
if (resolved?.underlying)
|
|
115
|
+
return toWasmType(resolved.underlying, checker, mod);
|
|
118
116
|
}
|
|
119
117
|
|
|
120
118
|
// Untyped constants
|
|
@@ -128,6 +126,17 @@ export function toWasmType(goType, checker = null, mod = null) {
|
|
|
128
126
|
return "externref";
|
|
129
127
|
case "bool":
|
|
130
128
|
return "i32";
|
|
129
|
+
case "complex128":
|
|
130
|
+
if (mod?.getComplexType) {
|
|
131
|
+
const info = mod.getComplexType("complex128");
|
|
132
|
+
return {
|
|
133
|
+
kind: "ref",
|
|
134
|
+
nullable: true,
|
|
135
|
+
typeIndex: info.typeIndex,
|
|
136
|
+
typeName: info.name,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
return { kind: "ref", nullable: true, heapType: "struct" };
|
|
131
140
|
default:
|
|
132
141
|
return "i64";
|
|
133
142
|
}
|
|
@@ -159,6 +168,32 @@ export function toWasmType(goType, checker = null, mod = null) {
|
|
|
159
168
|
case "float64":
|
|
160
169
|
return "f64";
|
|
161
170
|
|
|
171
|
+
case "complex64": {
|
|
172
|
+
if (mod?.getComplexType) {
|
|
173
|
+
const info = mod.getComplexType("complex64");
|
|
174
|
+
return {
|
|
175
|
+
kind: "ref",
|
|
176
|
+
nullable: true,
|
|
177
|
+
typeIndex: info.typeIndex,
|
|
178
|
+
typeName: info.name,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
return { kind: "ref", nullable: true, heapType: "struct" };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
case "complex128": {
|
|
185
|
+
if (mod?.getComplexType) {
|
|
186
|
+
const info = mod.getComplexType("complex128");
|
|
187
|
+
return {
|
|
188
|
+
kind: "ref",
|
|
189
|
+
nullable: true,
|
|
190
|
+
typeIndex: info.typeIndex,
|
|
191
|
+
typeName: info.name,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
return { kind: "ref", nullable: true, heapType: "struct" };
|
|
195
|
+
}
|
|
196
|
+
|
|
162
197
|
case "string":
|
|
163
198
|
return "externref";
|
|
164
199
|
|
|
@@ -181,14 +216,32 @@ export function toWasmType(goType, checker = null, mod = null) {
|
|
|
181
216
|
if (
|
|
182
217
|
typeof baseWType === "object" &&
|
|
183
218
|
baseWType !== null &&
|
|
184
|
-
baseWType.kind === "ref"
|
|
219
|
+
baseWType.kind === "ref" &&
|
|
220
|
+
// A pointer to a slice or map goes through the scalar box:
|
|
221
|
+
// the pointee is a replaceable reference value.
|
|
222
|
+
!isReplaceableRef(goType.base, checker)
|
|
185
223
|
) {
|
|
186
224
|
return baseWType;
|
|
187
225
|
}
|
|
188
|
-
if (mod?.
|
|
226
|
+
if (mod?.getPtrType) {
|
|
227
|
+
const ptr = mod.getPtrType(goType.base);
|
|
228
|
+
if (mod.getBoxType) mod.getBoxType(goType.base);
|
|
229
|
+
if (ptr)
|
|
230
|
+
return {
|
|
231
|
+
kind: "ref",
|
|
232
|
+
nullable: true,
|
|
233
|
+
typeIndex: ptr.typeIndex,
|
|
234
|
+
typeName: ptr.name,
|
|
235
|
+
};
|
|
236
|
+
} else if (mod?.getBoxType) {
|
|
189
237
|
const box = mod.getBoxType(goType.base);
|
|
190
238
|
if (box)
|
|
191
|
-
return {
|
|
239
|
+
return {
|
|
240
|
+
kind: "ref",
|
|
241
|
+
nullable: true,
|
|
242
|
+
typeIndex: box.typeIndex,
|
|
243
|
+
typeName: box.name,
|
|
244
|
+
};
|
|
192
245
|
}
|
|
193
246
|
}
|
|
194
247
|
return { kind: "ref", nullable: true, heapType: "any" };
|
|
@@ -262,10 +315,8 @@ export function toWasmType(goType, checker = null, mod = null) {
|
|
|
262
315
|
for (const n of names) rawFields.push({ name: n, goType: f.type });
|
|
263
316
|
}
|
|
264
317
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
return { kind: "ref", nullable: true, typeIndex: info.typeIndex };
|
|
268
|
-
}
|
|
318
|
+
const info = mod.getAnonStructType(rawFields);
|
|
319
|
+
return { kind: "ref", nullable: true, typeIndex: info.typeIndex };
|
|
269
320
|
}
|
|
270
321
|
return { kind: "ref", nullable: true, heapType: "struct" };
|
|
271
322
|
}
|
|
@@ -294,14 +345,19 @@ export function getInterfaceMethodList(iface) {
|
|
|
294
345
|
if (iface.methods instanceof Map) {
|
|
295
346
|
return Array.from(iface.methods.entries()).map(([name, sig]) => ({
|
|
296
347
|
name,
|
|
297
|
-
params: (
|
|
348
|
+
params: variadicAsSlice(
|
|
349
|
+
(sig.params ?? []).map((p) => p.type ?? p),
|
|
350
|
+
sig.variadic,
|
|
351
|
+
),
|
|
298
352
|
returns: (sig.returns ?? []).map((r) => r.type ?? r),
|
|
299
353
|
}));
|
|
300
354
|
}
|
|
301
355
|
if (Array.isArray(iface.methods)) {
|
|
302
356
|
return iface.methods.map((m) => ({
|
|
303
357
|
name: m.name?.value ?? m.name ?? m,
|
|
304
|
-
params: (m.params ?? []).map((p) =>
|
|
358
|
+
params: (m.params ?? []).map((p) =>
|
|
359
|
+
p.variadic ? { kind: "SliceType", elem: p.type } : (p.type ?? p),
|
|
360
|
+
),
|
|
305
361
|
returns: m.returnType
|
|
306
362
|
? m.returnType.kind === "TupleType" || m.returnType.kind === "tuple"
|
|
307
363
|
? m.returnType.types
|
|
@@ -312,6 +368,12 @@ export function getInterfaceMethodList(iface) {
|
|
|
312
368
|
return [];
|
|
313
369
|
}
|
|
314
370
|
|
|
371
|
+
// The type-argument part of a monomorphised name (see monomorph.js typeKey):
|
|
372
|
+
// the dot of a qualified argument is replaced to keep the name an identifier.
|
|
373
|
+
function instanceArgsKey(typeArgs) {
|
|
374
|
+
return typeArgs.map(goTypeKey).join("$").replaceAll(".", "_");
|
|
375
|
+
}
|
|
376
|
+
|
|
315
377
|
export function goTypeKey(t) {
|
|
316
378
|
if (!t) return "any";
|
|
317
379
|
if (typeof t === "string") {
|
|
@@ -361,6 +423,9 @@ export function goTypeKey(t) {
|
|
|
361
423
|
return `struct_${rawFields.join("_")}`;
|
|
362
424
|
}
|
|
363
425
|
if (t.kind === "named") {
|
|
426
|
+
// An instance of a generic type is the monomorphised `Box$int`.
|
|
427
|
+
if (t.name && t.typeArgs?.length && !t.name.includes("$"))
|
|
428
|
+
return `${t.name}$${instanceArgsKey(t.typeArgs)}`;
|
|
364
429
|
return t.name ?? (t.underlying ? goTypeKey(t.underlying) : "any");
|
|
365
430
|
}
|
|
366
431
|
if (t.name) return t.name;
|
|
@@ -404,6 +469,43 @@ export function isPointerToStruct(goType, checker = null, mod = null) {
|
|
|
404
469
|
return false;
|
|
405
470
|
}
|
|
406
471
|
|
|
472
|
+
// Slices, maps and pointers are reference values that an assignment through a
|
|
473
|
+
// pointer replaces (a nil map becomes a new one, `*pp = q` repoints `p`),
|
|
474
|
+
// unlike structs and arrays, whose pointer is the value's own reference.
|
|
475
|
+
function isReplaceableRef(goType, checker) {
|
|
476
|
+
let t = goType;
|
|
477
|
+
if (t?.kind === "TypeName" && checker?.types?.has(t.name))
|
|
478
|
+
t = checker.types.get(t.name);
|
|
479
|
+
if (t?.kind === "named" && t.underlying) t = t.underlying;
|
|
480
|
+
return (
|
|
481
|
+
isSliceType(goType, checker) ||
|
|
482
|
+
isMapType(goType, checker) ||
|
|
483
|
+
t?.kind === "pointer" ||
|
|
484
|
+
t?.kind === "PointerType"
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
export function isScalarType(goType, checker = null, mod = null) {
|
|
489
|
+
if (!goType) return false;
|
|
490
|
+
// A slice or map is a pointee "scalar": the pointer to it is a
|
|
491
|
+
// box, so the pointee can be replaced (`*p = make(...)`).
|
|
492
|
+
if (isReplaceableRef(goType, checker)) return true;
|
|
493
|
+
if (
|
|
494
|
+
isStructType(goType, checker, mod) ||
|
|
495
|
+
isArrayType(goType, checker) ||
|
|
496
|
+
isMapType(goType, checker) ||
|
|
497
|
+
isFuncType(goType, checker) ||
|
|
498
|
+
isComplex(goType)
|
|
499
|
+
) {
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
const wType = toWasmType(goType, checker, mod);
|
|
503
|
+
if (typeof wType === "object" && wType !== null && wType.kind === "ref") {
|
|
504
|
+
return false;
|
|
505
|
+
}
|
|
506
|
+
return true;
|
|
507
|
+
}
|
|
508
|
+
|
|
407
509
|
export function isSliceType(goType, checker = null) {
|
|
408
510
|
if (!goType) return false;
|
|
409
511
|
if (goType.kind === "TypeName" || goType.kind === "Ident") {
|
|
@@ -413,9 +515,79 @@ export function isSliceType(goType, checker = null) {
|
|
|
413
515
|
if (goType.kind === "named" && goType.underlying) {
|
|
414
516
|
return isSliceType(goType.underlying, checker);
|
|
415
517
|
}
|
|
518
|
+
if (goType.kind === "named" && !goType.underlying) {
|
|
519
|
+
const resolved = checker?.types?.get(goType.name);
|
|
520
|
+
if (resolved && resolved !== goType) return isSliceType(resolved, checker);
|
|
521
|
+
}
|
|
416
522
|
return goType.kind === "slice" || goType.kind === "SliceType";
|
|
417
523
|
}
|
|
418
524
|
|
|
525
|
+
// Element kinds whose slices share one WebAssembly representation (a slice of
|
|
526
|
+
// i32), so `[]bool`, `[]byte` and `[]rune` cannot be told apart by `ref.test`.
|
|
527
|
+
const TAGGED_SLICE_ELEMS = new Set([
|
|
528
|
+
"bool",
|
|
529
|
+
"int8",
|
|
530
|
+
"int16",
|
|
531
|
+
"int32",
|
|
532
|
+
"uint8",
|
|
533
|
+
"uint16",
|
|
534
|
+
"uint32",
|
|
535
|
+
"byte",
|
|
536
|
+
"rune",
|
|
537
|
+
]);
|
|
538
|
+
|
|
539
|
+
// True for a defined slice type or a slice of booleans or integers of at
|
|
540
|
+
// most 32 bits. In an
|
|
541
|
+
// interface such a value carries a per-Go-type tag (see getTaggedSliceType).
|
|
542
|
+
export function isTaggedSliceType(goType, checker = null) {
|
|
543
|
+
if (!isSliceType(goType, checker)) return false;
|
|
544
|
+
// A defined slice type (`type S []int`) is tagged whatever its element, so
|
|
545
|
+
// `case S` does not match an unnamed `[]int`.
|
|
546
|
+
const declared =
|
|
547
|
+
goType.kind === "TypeName" || goType.kind === "Ident"
|
|
548
|
+
? (checker?.types?.get(goType.name) ?? goType)
|
|
549
|
+
: goType;
|
|
550
|
+
if (declared?.kind === "named") return true;
|
|
551
|
+
let elem = getSliceElemType(goType, checker);
|
|
552
|
+
// A source-level element (`uint8` in a case list) is a TypeName node.
|
|
553
|
+
for (let i = 0; i < 16 && elem; i++) {
|
|
554
|
+
if (elem.kind === "named")
|
|
555
|
+
elem = elem.underlying ?? checker?.types?.get(elem.name);
|
|
556
|
+
else if (elem.kind === "TypeName" || elem.kind === "Ident")
|
|
557
|
+
elem = checker?.types?.get(elem.name) ?? {
|
|
558
|
+
kind: "basic",
|
|
559
|
+
name: elem.name,
|
|
560
|
+
};
|
|
561
|
+
else break;
|
|
562
|
+
}
|
|
563
|
+
return elem?.kind === "basic" && TAGGED_SLICE_ELEMS.has(elem.name);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
export function getSliceElemType(t, checker = null) {
|
|
567
|
+
if (!t) return null;
|
|
568
|
+
if (t.elem) return t.elem;
|
|
569
|
+
if (t.underlying?.elem) return t.underlying.elem;
|
|
570
|
+
if (t.name && checker?.types?.has(t.name)) {
|
|
571
|
+
const resolved = checker.types.get(t.name);
|
|
572
|
+
return getSliceElemType(resolved, checker);
|
|
573
|
+
}
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// `[]rune` / `[]int32` (also through named types): the target of `[]rune(s)`.
|
|
578
|
+
export function isRuneSlice(goType, checker = null) {
|
|
579
|
+
if (!isSliceType(goType, checker)) return false;
|
|
580
|
+
let t = goType;
|
|
581
|
+
while (t && !t.elem) {
|
|
582
|
+
if (t.kind === "named") t = t.underlying;
|
|
583
|
+
else if (t.kind === "TypeName" || t.kind === "Ident")
|
|
584
|
+
t = checker?.types?.get(t.name);
|
|
585
|
+
else return false;
|
|
586
|
+
}
|
|
587
|
+
const name = t?.elem?.name;
|
|
588
|
+
return name === "rune" || name === "int32";
|
|
589
|
+
}
|
|
590
|
+
|
|
419
591
|
// `gofront/shared` linear-memory buffer: returns the SHARED_KINDS entry
|
|
420
592
|
// (elem, ctor, bytes, key) or null.
|
|
421
593
|
export function getSharedInfo(goType, checker = null) {
|
|
@@ -451,6 +623,10 @@ export function isMapType(goType, checker = null) {
|
|
|
451
623
|
if (goType.kind === "named" && goType.underlying) {
|
|
452
624
|
return isMapType(goType.underlying, checker);
|
|
453
625
|
}
|
|
626
|
+
if (goType.kind === "named" && !goType.underlying) {
|
|
627
|
+
const resolved = checker?.types?.get(goType.name);
|
|
628
|
+
if (resolved && resolved !== goType) return isMapType(resolved, checker);
|
|
629
|
+
}
|
|
454
630
|
return goType.kind === "map" || goType.kind === "MapType";
|
|
455
631
|
}
|
|
456
632
|
|
|
@@ -464,6 +640,11 @@ export function getMapKeyValTypes(goType, checker = null) {
|
|
|
464
640
|
if (goType.kind === "named" && goType.underlying) {
|
|
465
641
|
return getMapKeyValTypes(goType.underlying, checker);
|
|
466
642
|
}
|
|
643
|
+
if (goType.kind === "named" && !goType.underlying) {
|
|
644
|
+
const resolved = checker?.types?.get(goType.name);
|
|
645
|
+
if (resolved && resolved !== goType)
|
|
646
|
+
return getMapKeyValTypes(resolved, checker);
|
|
647
|
+
}
|
|
467
648
|
if (goType.kind === "map" || goType.kind === "MapType") {
|
|
468
649
|
const key = goType.key ?? goType.keyType;
|
|
469
650
|
const val =
|
|
@@ -483,6 +664,10 @@ export function isStringType(goType, checker = null) {
|
|
|
483
664
|
if (goType.kind === "named") {
|
|
484
665
|
if (goType.name === "string") return true;
|
|
485
666
|
if (goType.underlying) return isStringType(goType.underlying, checker);
|
|
667
|
+
// A bare `{ kind: "named", name }` (interface candidates) resolves by name.
|
|
668
|
+
const resolved = checker?.types?.get(goType.name);
|
|
669
|
+
if (resolved && resolved !== goType && resolved.kind === "named")
|
|
670
|
+
return isStringType(resolved, checker);
|
|
486
671
|
}
|
|
487
672
|
if (goType.kind === "basic") return goType.name === "string";
|
|
488
673
|
if (goType.kind === "untyped") return goType.base === "string";
|
|
@@ -528,6 +713,14 @@ export function isInterfaceType(goType, checker = null) {
|
|
|
528
713
|
return false;
|
|
529
714
|
}
|
|
530
715
|
|
|
716
|
+
// `bool`, including the untyped boolean of a constant (`const ok = true`).
|
|
717
|
+
export function isBoolGoType(goType) {
|
|
718
|
+
return (
|
|
719
|
+
goType?.name === "bool" ||
|
|
720
|
+
(goType?.kind === "untyped" && goType.base === "bool")
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
|
|
531
724
|
// The built-in `error` interface (checker ERROR type or its name).
|
|
532
725
|
export function isErrorType(goType) {
|
|
533
726
|
if (!goType) return false;
|
|
@@ -593,6 +786,33 @@ export function isSigned(goType) {
|
|
|
593
786
|
return true;
|
|
594
787
|
}
|
|
595
788
|
|
|
789
|
+
// How a builtin `print`/`println` operand is handed to the host. The host
|
|
790
|
+
// prints an i32 or i64 as signed, so a uint32 widens to i64 and a uint64 uses
|
|
791
|
+
// its own unsigned import (`wType` "u64").
|
|
792
|
+
export function printOperandKind(arg, checker) {
|
|
793
|
+
const isBool =
|
|
794
|
+
isBoolGoType(arg._type) ||
|
|
795
|
+
_namedBasic(arg._type) === "bool" ||
|
|
796
|
+
(arg.kind === "BasicLit" && arg.litKind === "BOOL");
|
|
797
|
+
let wType = toWasmType(arg._type, checker);
|
|
798
|
+
let widen = false;
|
|
799
|
+
if (!isBool && (wType === "i32" || wType === "i64") && !isSigned(arg._type)) {
|
|
800
|
+
if (wType === "i64") wType = "u64";
|
|
801
|
+
else if (_namedBasic(arg._type) === "uint32") {
|
|
802
|
+
wType = "i64";
|
|
803
|
+
widen = true;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
return { wType, isBool, widen };
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
function _namedBasic(t) {
|
|
810
|
+
while (t && (t.kind === "named" || t.kind === "TypeName") && t.underlying) {
|
|
811
|
+
t = t.underlying;
|
|
812
|
+
}
|
|
813
|
+
return t?.name;
|
|
814
|
+
}
|
|
815
|
+
|
|
596
816
|
export function isFloat(goType) {
|
|
597
817
|
if (!goType) return false;
|
|
598
818
|
if (goType.kind === "TypeName" || goType.kind === "Ident") {
|
|
@@ -639,7 +859,9 @@ export function getFuncSignature(goType, checker = null) {
|
|
|
639
859
|
goType.kind === "FuncLit" ||
|
|
640
860
|
goType.kind === "FuncDecl"
|
|
641
861
|
) {
|
|
642
|
-
const params = (goType.params ?? []).map((p) =>
|
|
862
|
+
const params = (goType.params ?? []).map((p) =>
|
|
863
|
+
p.variadic ? { kind: "SliceType", elem: p.type } : (p.type ?? p),
|
|
864
|
+
);
|
|
643
865
|
const returns = [];
|
|
644
866
|
if (goType.returnType) {
|
|
645
867
|
if (
|
|
@@ -672,11 +894,22 @@ export function getFuncSignature(goType, checker = null) {
|
|
|
672
894
|
returns.push(r);
|
|
673
895
|
}
|
|
674
896
|
}
|
|
675
|
-
return {
|
|
897
|
+
return {
|
|
898
|
+
params: variadicAsSlice(goType.params ?? [], goType.variadic),
|
|
899
|
+
returns,
|
|
900
|
+
};
|
|
676
901
|
}
|
|
677
902
|
return { params: [], returns: [] };
|
|
678
903
|
}
|
|
679
904
|
|
|
905
|
+
// Checker func types keep a variadic parameter's element type; in WASM the
|
|
906
|
+
// parameter is a slice (see variadic.js).
|
|
907
|
+
export function variadicAsSlice(params, variadic) {
|
|
908
|
+
if (!variadic || params.length === 0) return params;
|
|
909
|
+
const last = params[params.length - 1];
|
|
910
|
+
return [...params.slice(0, -1), { kind: "slice", elem: last }];
|
|
911
|
+
}
|
|
912
|
+
|
|
680
913
|
export function isUnhashableGoType(goType, checker = null, seen = new Set()) {
|
|
681
914
|
if (!goType) return false;
|
|
682
915
|
if (seen.has(goType)) return false;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// src/backend/wasm/variadic.js
|
|
2
|
+
// WASM has no rest parameters. A variadic `xs ...T` becomes an ordinary
|
|
3
|
+
// `[]T` parameter, and every call to a GoFront function, method or closure
|
|
4
|
+
// packs its trailing arguments into a `[]T` literal (`f(s...)` passes `s`
|
|
5
|
+
// through). Emitter-backed stdlib calls keep their raw arguments. Runs
|
|
6
|
+
// after monomorphisation, so callee types are concrete, on the WASM
|
|
7
|
+
// pipeline's own copy of the programs.
|
|
8
|
+
|
|
9
|
+
import { typeToTypeNode } from "./monomorph.js";
|
|
10
|
+
|
|
11
|
+
function variadicFuncType(t) {
|
|
12
|
+
const fn = t?.kind === "named" ? t.underlying : t;
|
|
13
|
+
return fn?.kind === "func" && fn.variadic ? fn : null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// A call of a package member or a method of an emitter-backed type: those
|
|
17
|
+
// emitters take the arguments unpacked. Emitter types are the dotted names;
|
|
18
|
+
// GoFront and Go-source types are not.
|
|
19
|
+
function isStdlibCall(call) {
|
|
20
|
+
const f = call.func;
|
|
21
|
+
if (f?.kind !== "SelectorExpr") return false;
|
|
22
|
+
let recv = f.expr?._type;
|
|
23
|
+
if (recv?.kind === "namespace") return true;
|
|
24
|
+
if (recv?.kind === "pointer") recv = recv.base;
|
|
25
|
+
return typeof recv?.name === "string" && recv.name.includes(".");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function packCallArgs(call) {
|
|
29
|
+
if (call._multiForward || isStdlibCall(call)) return false;
|
|
30
|
+
const fn = variadicFuncType(call.func?._type);
|
|
31
|
+
if (!fn) return false;
|
|
32
|
+
const n = fn.params.length;
|
|
33
|
+
const args = call.args ?? [];
|
|
34
|
+
if (args.length === n && args[n - 1]?._spread) {
|
|
35
|
+
args[n - 1]._spread = false;
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
if (args.length < n - 1) return false;
|
|
39
|
+
const elem = fn.params[n - 1];
|
|
40
|
+
const sliceType = { kind: "slice", elem };
|
|
41
|
+
// No variadic arguments: the parameter is the nil slice, as in Go.
|
|
42
|
+
const rest = args.slice(n - 1);
|
|
43
|
+
const packed =
|
|
44
|
+
rest.length === 0
|
|
45
|
+
? {
|
|
46
|
+
kind: "TypeConversion",
|
|
47
|
+
targetType: sliceType,
|
|
48
|
+
expr: { kind: "Ident", name: "nil" },
|
|
49
|
+
_type: sliceType,
|
|
50
|
+
_line: call._line,
|
|
51
|
+
_col: call._col,
|
|
52
|
+
}
|
|
53
|
+
: null;
|
|
54
|
+
call.args = [
|
|
55
|
+
...args.slice(0, n - 1),
|
|
56
|
+
packed ?? {
|
|
57
|
+
kind: "CompositeLit",
|
|
58
|
+
typeExpr: { kind: "SliceType", elem: typeToTypeNode(elem) },
|
|
59
|
+
elems: args.slice(n - 1),
|
|
60
|
+
_type: sliceType,
|
|
61
|
+
_line: call._line,
|
|
62
|
+
_col: call._col,
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function sliceVariadicParam(params) {
|
|
69
|
+
const last = params?.[params.length - 1];
|
|
70
|
+
if (!last?.variadic) return false;
|
|
71
|
+
last.type = { kind: "SliceType", elem: last.type };
|
|
72
|
+
last.variadic = false;
|
|
73
|
+
last._variadic = true; // the JS facade keeps a rest parameter
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Rewrites variadic declarations and calls in place; true if anything changed. */
|
|
78
|
+
export function desugarVariadics(node) {
|
|
79
|
+
if (!node || typeof node !== "object") return false;
|
|
80
|
+
if (Array.isArray(node)) {
|
|
81
|
+
let changed = false;
|
|
82
|
+
for (const n of node) changed = desugarVariadics(n) || changed;
|
|
83
|
+
return changed;
|
|
84
|
+
}
|
|
85
|
+
let changed = false;
|
|
86
|
+
if (Array.isArray(node.params)) changed = sliceVariadicParam(node.params);
|
|
87
|
+
if (node.kind === "CallExpr") changed = packCallArgs(node) || changed;
|
|
88
|
+
for (const key of Object.keys(node)) {
|
|
89
|
+
if (key.charCodeAt(0) === 95) continue; // "_" side tables
|
|
90
|
+
changed = desugarVariadics(node[key]) || changed;
|
|
91
|
+
}
|
|
92
|
+
return changed;
|
|
93
|
+
}
|
package/src/backend/wasm/wat.js
CHANGED
|
@@ -8,7 +8,13 @@ function formatValType(t) {
|
|
|
8
8
|
if (t.kind === "ref") {
|
|
9
9
|
const nullStr = t.nullable ? "null " : "";
|
|
10
10
|
const target =
|
|
11
|
-
t.
|
|
11
|
+
t.typeName !== undefined
|
|
12
|
+
? t.typeName.startsWith("$")
|
|
13
|
+
? t.typeName
|
|
14
|
+
: `$${t.typeName}`
|
|
15
|
+
: t.typeIndex !== undefined
|
|
16
|
+
? `$t${t.typeIndex}`
|
|
17
|
+
: (t.heapType ?? "any");
|
|
12
18
|
return `(ref ${nullStr}${target})`;
|
|
13
19
|
}
|
|
14
20
|
}
|
|
@@ -16,7 +22,11 @@ function formatValType(t) {
|
|
|
16
22
|
}
|
|
17
23
|
|
|
18
24
|
function formatTypeEntry(t, id) {
|
|
19
|
-
const typeId =
|
|
25
|
+
const typeId = t.name
|
|
26
|
+
? ` ${t.name.startsWith("$") ? t.name : `$${t.name}`}`
|
|
27
|
+
: id !== undefined
|
|
28
|
+
? ` $t${id}`
|
|
29
|
+
: "";
|
|
20
30
|
if (t.form === "rec") {
|
|
21
31
|
const inners = t.types
|
|
22
32
|
.map(
|
|
@@ -26,6 +36,7 @@ function formatTypeEntry(t, id) {
|
|
|
26
36
|
.join("\n");
|
|
27
37
|
return `(rec\n${inners}\n )`;
|
|
28
38
|
}
|
|
39
|
+
let body;
|
|
29
40
|
if (t.form === "struct") {
|
|
30
41
|
const fields = (t.fields ?? [])
|
|
31
42
|
.map((f) => {
|
|
@@ -33,19 +44,29 @@ function formatTypeEntry(t, id) {
|
|
|
33
44
|
return f.mutable ? `(field (mut ${inner}))` : `(field ${inner})`;
|
|
34
45
|
})
|
|
35
46
|
.join(" ");
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
if (t.form === "array") {
|
|
47
|
+
body = fields ? `(struct ${fields})` : `(struct)`;
|
|
48
|
+
} else if (t.form === "array") {
|
|
39
49
|
const inner = formatValType(t.elemType ?? t.type);
|
|
40
50
|
const field = t.mutable ? `(mut ${inner})` : inner;
|
|
41
|
-
|
|
51
|
+
body = `(array ${field})`;
|
|
52
|
+
} else {
|
|
53
|
+
// func
|
|
54
|
+
const params = (t.params ?? []).map(formatValType).join(" ");
|
|
55
|
+
const results = (t.results ?? []).map(formatValType).join(" ");
|
|
56
|
+
const paramStr = params ? ` (param ${params})` : "";
|
|
57
|
+
const resultStr = results ? ` (result ${results})` : "";
|
|
58
|
+
body = `(func${paramStr}${resultStr})`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (t.sub) {
|
|
62
|
+
const superStr = t.superType
|
|
63
|
+
? ` ${t.superType.startsWith("$") ? t.superType : `$${t.superType}`}`
|
|
64
|
+
: t.superTypeIndex !== undefined
|
|
65
|
+
? ` $t${t.superTypeIndex}`
|
|
66
|
+
: "";
|
|
67
|
+
return `(type${typeId} (sub${superStr} ${body}))`;
|
|
42
68
|
}
|
|
43
|
-
|
|
44
|
-
const params = (t.params ?? []).map(formatValType).join(" ");
|
|
45
|
-
const results = (t.results ?? []).map(formatValType).join(" ");
|
|
46
|
-
const paramStr = params ? ` (param ${params})` : "";
|
|
47
|
-
const resultStr = results ? ` (result ${results})` : "";
|
|
48
|
-
return `(type${typeId} (func${paramStr}${resultStr}))`;
|
|
69
|
+
return `(type${typeId} ${body})`;
|
|
49
70
|
}
|
|
50
71
|
|
|
51
72
|
function formatInstruction(inst) {
|
|
@@ -56,11 +77,13 @@ function formatInstruction(inst) {
|
|
|
56
77
|
case "loop":
|
|
57
78
|
case "if": {
|
|
58
79
|
const bt = inst.blockType ?? inst.resultType;
|
|
80
|
+
if (typeof bt === "number") return `${op} (type $t${bt})`;
|
|
59
81
|
const res = bt && bt !== "void" ? ` (result ${formatValType(bt)})` : "";
|
|
60
82
|
return `${op}${res}`;
|
|
61
83
|
}
|
|
62
84
|
case "br":
|
|
63
85
|
case "br_if":
|
|
86
|
+
case "br_on_null":
|
|
64
87
|
return `${op} ${inst.depth ?? inst.index ?? 0}`;
|
|
65
88
|
case "br_table": {
|
|
66
89
|
const targets = (inst.targets ?? []).join(" ");
|
|
@@ -73,7 +96,7 @@ function formatInstruction(inst) {
|
|
|
73
96
|
return `${op} (type $t${inst.typeIndex ?? 0})`;
|
|
74
97
|
case "call_ref":
|
|
75
98
|
case "return_call_ref":
|
|
76
|
-
return `${op} (
|
|
99
|
+
return `${op} ${inst.typeName ? (inst.typeName.startsWith("$") ? inst.typeName : `$${inst.typeName}`) : `$t${inst.typeIndex ?? 0}`}`;
|
|
77
100
|
case "ref.func":
|
|
78
101
|
return `ref.func ${inst.funcIndex ?? inst.index ?? 0}`;
|
|
79
102
|
case "local.get":
|
|
@@ -169,12 +192,12 @@ function formatInstruction(inst) {
|
|
|
169
192
|
return `${op}${res}${catches ? ` ${catches}` : ""}`;
|
|
170
193
|
}
|
|
171
194
|
case "struct.new":
|
|
172
|
-
return `struct.new ${inst.typeIndex !== undefined ? `$t${inst.typeIndex}` : 0}`;
|
|
195
|
+
return `struct.new ${inst.typeName ? (inst.typeName.startsWith("$") ? inst.typeName : `$${inst.typeName}`) : inst.typeIndex !== undefined ? `$t${inst.typeIndex}` : 0}`;
|
|
173
196
|
case "struct.get":
|
|
174
197
|
case "struct.get_s":
|
|
175
198
|
case "struct.get_u":
|
|
176
199
|
case "struct.set":
|
|
177
|
-
return `${op} ${inst.typeIndex !== undefined ? `$t${inst.typeIndex}` : 0} ${inst.fieldIndex ?? 0}`;
|
|
200
|
+
return `${op} ${inst.typeName ? (inst.typeName.startsWith("$") ? inst.typeName : `$${inst.typeName}`) : inst.typeIndex !== undefined ? `$t${inst.typeIndex}` : 0} ${inst.fieldIndex ?? 0}`;
|
|
178
201
|
case "array.new":
|
|
179
202
|
case "array.new_default":
|
|
180
203
|
case "array.get":
|
|
@@ -193,11 +216,15 @@ function formatInstruction(inst) {
|
|
|
193
216
|
case "ref.cast":
|
|
194
217
|
case "ref.cast_null": {
|
|
195
218
|
const ht =
|
|
196
|
-
inst.
|
|
197
|
-
?
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
219
|
+
inst.typeName !== undefined
|
|
220
|
+
? inst.typeName.startsWith("$")
|
|
221
|
+
? inst.typeName
|
|
222
|
+
: `$${inst.typeName}`
|
|
223
|
+
: inst.typeIndex !== undefined
|
|
224
|
+
? `$t${inst.typeIndex}`
|
|
225
|
+
: inst.heapType !== undefined
|
|
226
|
+
? String(inst.heapType)
|
|
227
|
+
: 0;
|
|
201
228
|
return `${op} ${ht}`;
|
|
202
229
|
}
|
|
203
230
|
case "any.convert_extern":
|