@hew-lang/sandbox-vm 0.6.0-rc7 → 0.6.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 +6 -0
- package/cli/test-runner.mjs +84 -0
- package/dist/interpreter/parity-runner.js +9 -0
- package/dist/interpreter/run-program.d.ts +3 -2
- package/dist/interpreter/run-program.js +5 -4
- package/dist/interpreter/trace.d.ts +1 -0
- package/dist/interpreter/trace.js +4 -0
- package/dist/interpreter/types.d.ts +3 -0
- package/dist/interpreter/v1/codecs.d.ts +9 -0
- package/dist/interpreter/v1/codecs.js +266 -0
- package/dist/interpreter/v1/exec.js +238 -34
- package/dist/interpreter/v1/package.d.ts +38 -10
- package/dist/interpreter/v1/shims.js +273 -9
- package/dist/interpreter/v1/validate.js +33 -4
- package/dist/interpreter/v1/value-operations.js +103 -13
- package/dist/interpreter/worker.js +22 -4
- package/package.json +8 -4
package/README.md
CHANGED
|
@@ -47,3 +47,9 @@ make sandbox-fixtures-check
|
|
|
47
47
|
The [package contract](bytecode/package-v1.md) describes the SIR projection.
|
|
48
48
|
Source fixtures are compiled by the public compiler and executed to record
|
|
49
49
|
deterministic traces. Tests also compare executable sources with native Hew.
|
|
50
|
+
|
|
51
|
+
Typed serialization executes the shared Rust codecs in `@hew-lang/wasm`.
|
|
52
|
+
Pass its initialized `executeCodec` export as `runBytecode`'s `codecBridge`
|
|
53
|
+
option. The Node test host loads that package automatically; in a source
|
|
54
|
+
checkout, run `make wasm` to build `hew-wasm/pkg` first. An explicit
|
|
55
|
+
`HEW_WASM_MODULE` module URL can select another build of the same package.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
+
import { classifyHewFault, runBytecode, traceToDownloadJson } from "../dist/interpreter/index.js";
|
|
6
|
+
|
|
7
|
+
function parseArgs(args) {
|
|
8
|
+
if (args.length !== 7 || args[1] !== "--schedule" || args[3] !== "--seed" || args[5] !== "--step-budget") {
|
|
9
|
+
throw new Error("usage: test-runner <bytecode.json> --schedule fifo|random --seed N --step-budget N");
|
|
10
|
+
}
|
|
11
|
+
const schedule = args[2];
|
|
12
|
+
if (schedule !== "fifo" && schedule !== "random") throw new Error(`unknown schedule: ${schedule}`);
|
|
13
|
+
const seed = BigInt(args[4]);
|
|
14
|
+
const budget = Number(args[6]);
|
|
15
|
+
if (!Number.isSafeInteger(budget) || budget <= 0) throw new Error("step budget must be a positive safe integer");
|
|
16
|
+
return { path: args[0], schedule, seed, budget };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const reportPath = process.env.HEW_TEST_REPORT;
|
|
20
|
+
const tracePath = process.env.HEW_TEST_TRACE_PATH;
|
|
21
|
+
try {
|
|
22
|
+
const args = parseArgs(process.argv.slice(2));
|
|
23
|
+
const packageValue = JSON.parse(fs.readFileSync(args.path, "utf8"));
|
|
24
|
+
let codecBridge;
|
|
25
|
+
if (packageValue.codecs?.length) {
|
|
26
|
+
const local = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../hew-wasm/pkg/hew_wasm.js");
|
|
27
|
+
const location = process.env.HEW_WASM_MODULE ?? (fs.existsSync(local) ? pathToFileURL(local).href : "@hew-lang/wasm");
|
|
28
|
+
const wasm = await import(location);
|
|
29
|
+
if (typeof wasm.default === "function") {
|
|
30
|
+
const resolved = location.startsWith("file:") ? location : import.meta.resolve(location);
|
|
31
|
+
await wasm.default({ module_or_path: fs.readFileSync(new URL(resolved.replace(/\.js$/, "_bg.wasm"))) });
|
|
32
|
+
}
|
|
33
|
+
codecBridge = wasm.executeCodec ?? wasm.default?.executeCodec;
|
|
34
|
+
if (typeof codecBridge !== "function") throw new Error("hew-wasm executeCodec export is missing; rebuild the Wasm package");
|
|
35
|
+
}
|
|
36
|
+
const propertyPath = reportPath ? path.join(path.dirname(reportPath), "property.cbor") : null;
|
|
37
|
+
const trace = runBytecode(packageValue, {
|
|
38
|
+
codecBridge,
|
|
39
|
+
propertyInput: propertyPath && fs.existsSync(propertyPath) ? [...fs.readFileSync(propertyPath)] : [],
|
|
40
|
+
fixtureId: "hew-test",
|
|
41
|
+
traceId: "trace:hew-test",
|
|
42
|
+
schedulerPolicy: args.schedule === "random" ? "chaos" : "round_robin",
|
|
43
|
+
replay: {
|
|
44
|
+
seed: args.seed.toString(),
|
|
45
|
+
step_budget: args.budget,
|
|
46
|
+
virtual_clock: { epoch_ms: 0, tick_ms: 1, current_ms: 0 },
|
|
47
|
+
inputs: [],
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
if (tracePath) fs.writeFileSync(tracePath, traceToDownloadJson(trace));
|
|
51
|
+
const final = trace.final_state;
|
|
52
|
+
const rejection = final.sandbox_rejections[0];
|
|
53
|
+
const fault = final.runtime_failures[0];
|
|
54
|
+
const hewFault = classifyHewFault(trace);
|
|
55
|
+
const passed = trace.result === "ok" && final.exit_code === 0;
|
|
56
|
+
const outcome = passed ? "passed" : rejection ? "refused" : fault ? "fault" : "exit";
|
|
57
|
+
const message = rejection?.message ?? fault?.message ?? (passed ? null : `VM result: ${trace.result}`);
|
|
58
|
+
const faultKind = hewFault?.kind ?? fault?.kind ?? null;
|
|
59
|
+
const report = {
|
|
60
|
+
version: 1,
|
|
61
|
+
coverage: final.coverage ?? {},
|
|
62
|
+
outcome,
|
|
63
|
+
status: passed ? 0 : 1,
|
|
64
|
+
fault_kind: faultKind,
|
|
65
|
+
fault_code: hewFault?.code ?? null,
|
|
66
|
+
message,
|
|
67
|
+
site_offset: null,
|
|
68
|
+
assertion: fault?.assertion ?? null,
|
|
69
|
+
schedule: args.schedule,
|
|
70
|
+
seed: args.seed.toString(),
|
|
71
|
+
steps: final.step_count,
|
|
72
|
+
virtual_time_ms: final.virtual_clock.current_ms,
|
|
73
|
+
};
|
|
74
|
+
if (reportPath) fs.writeFileSync(reportPath, JSON.stringify(report));
|
|
75
|
+
process.stdout.write(final.stdout.join(""));
|
|
76
|
+
process.stderr.write(final.stderr.join(""));
|
|
77
|
+
if (message) process.stderr.write(`${message}\n`);
|
|
78
|
+
process.exitCode = report.status;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
81
|
+
if (reportPath) fs.writeFileSync(reportPath, JSON.stringify({ version: 1, outcome: "launch", status: 1, message }));
|
|
82
|
+
process.stderr.write(`hew test VM: ${message}\n`);
|
|
83
|
+
process.exitCode = 1;
|
|
84
|
+
}
|
|
@@ -57,6 +57,15 @@ try {
|
|
|
57
57
|
});
|
|
58
58
|
process.stdout.write(trace.final_state.stdout.join(""));
|
|
59
59
|
process.stderr.write(trace.final_state.stderr.join(""));
|
|
60
|
+
if (trace.final_state.exit_code === null &&
|
|
61
|
+
trace.final_state.stderr.length === 0) {
|
|
62
|
+
process.stderr.write(`parity-runner: ${JSON.stringify({
|
|
63
|
+
status: trace.result,
|
|
64
|
+
diagnostics: trace.final_state.diagnostics,
|
|
65
|
+
rejections: trace.final_state.sandbox_rejections,
|
|
66
|
+
failures: trace.final_state.runtime_failures,
|
|
67
|
+
})}\n`);
|
|
68
|
+
}
|
|
60
69
|
process.exitCode = trace.final_state.exit_code ?? 1;
|
|
61
70
|
}
|
|
62
71
|
catch (error) {
|
|
@@ -20,10 +20,11 @@ interface CompileOutput {
|
|
|
20
20
|
diagnostics: Diagnostic[];
|
|
21
21
|
bytecode: PackageV1 | null;
|
|
22
22
|
}
|
|
23
|
-
type SandboxCompiler = (source: string, profile?: string) => string | CompileOutput;
|
|
23
|
+
type SandboxCompiler = (source: string, profile?: string, files?: Map<string, string | Uint8Array>) => string | CompileOutput;
|
|
24
24
|
declare global {
|
|
25
25
|
var __hewSandboxCompileToSandboxBytecode: SandboxCompiler | undefined;
|
|
26
|
+
var __hewSandboxExecuteCodec: ((request: string) => string) | undefined;
|
|
26
27
|
var compileToSandboxBytecode: SandboxCompiler | undefined;
|
|
27
28
|
}
|
|
28
|
-
export declare function runProgram(source: string, stdin: string): RunProgramResult;
|
|
29
|
+
export declare function runProgram(source: string, stdin: string, files?: Map<string, string | Uint8Array>): RunProgramResult;
|
|
29
30
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { runBytecode } from "./interpreter.js";
|
|
2
2
|
const SANDBOX_PROFILE = "sandbox-vm-export";
|
|
3
|
-
export function runProgram(source, stdin) {
|
|
4
|
-
const compileOutput = compileSource(source);
|
|
3
|
+
export function runProgram(source, stdin, files) {
|
|
4
|
+
const compileOutput = compileSource(source, files);
|
|
5
5
|
if (hasErrorDiagnostics(compileOutput.diagnostics) ||
|
|
6
6
|
compileOutput.bytecode === null) {
|
|
7
7
|
return {
|
|
@@ -17,6 +17,7 @@ export function runProgram(source, stdin) {
|
|
|
17
17
|
const bytecode = compileOutput.bytecode;
|
|
18
18
|
const trace = runBytecode(bytecode, {
|
|
19
19
|
fixtureId: "page-run",
|
|
20
|
+
codecBridge: globalThis.__hewSandboxExecuteCodec,
|
|
20
21
|
traceId: "trace:page-run",
|
|
21
22
|
replay: { inputs: [{ kind: "stdin", data: stdin }] },
|
|
22
23
|
});
|
|
@@ -30,13 +31,13 @@ export function runProgram(source, stdin) {
|
|
|
30
31
|
diagnostics: [...compileOutput.diagnostics, ...runtimeDiagnostics(trace)],
|
|
31
32
|
};
|
|
32
33
|
}
|
|
33
|
-
function compileSource(source) {
|
|
34
|
+
function compileSource(source, files) {
|
|
34
35
|
const compiler = globalThis.__hewSandboxCompileToSandboxBytecode ??
|
|
35
36
|
globalThis.compileToSandboxBytecode;
|
|
36
37
|
if (!compiler) {
|
|
37
38
|
throw new Error("compile_to_sandbox_bytecode WASM bridge is not initialized");
|
|
38
39
|
}
|
|
39
|
-
const output = compiler(source, SANDBOX_PROFILE);
|
|
40
|
+
const output = compiler(source, SANDBOX_PROFILE, files);
|
|
40
41
|
const parsed = typeof output === "string" ? JSON.parse(output) : output;
|
|
41
42
|
if (!isCompileOutput(parsed)) {
|
|
42
43
|
throw new Error("compile_to_sandbox_bytecode returned an invalid payload");
|
|
@@ -21,6 +21,7 @@ export declare class TraceBuilder {
|
|
|
21
21
|
private readonly traceId;
|
|
22
22
|
readonly replay: ReplayConfig;
|
|
23
23
|
private readonly sandboxVersion;
|
|
24
|
+
readonly coverage: Record<string, number>;
|
|
24
25
|
readonly stdout: string[];
|
|
25
26
|
readonly stderr: string[];
|
|
26
27
|
readonly runtimeFailures: RuntimeFailure[];
|
|
@@ -5,6 +5,7 @@ export class TraceBuilder {
|
|
|
5
5
|
traceId;
|
|
6
6
|
replay;
|
|
7
7
|
sandboxVersion;
|
|
8
|
+
coverage = {};
|
|
8
9
|
stdout = [];
|
|
9
10
|
stderr = [];
|
|
10
11
|
runtimeFailures = [];
|
|
@@ -174,6 +175,9 @@ export class TraceBuilder {
|
|
|
174
175
|
replay: this.replay,
|
|
175
176
|
events: this.events,
|
|
176
177
|
final_state: {
|
|
178
|
+
...(Object.keys(this.coverage).length > 0
|
|
179
|
+
? { coverage: this.coverage }
|
|
180
|
+
: {}),
|
|
177
181
|
status: this.status,
|
|
178
182
|
exit_code: this.status === "ok" ? this.exitCode : null,
|
|
179
183
|
step_count: this.stepCount,
|
|
@@ -91,6 +91,7 @@ export interface SandboxTrace {
|
|
|
91
91
|
replay: ReplayConfig;
|
|
92
92
|
events: TraceEvent[];
|
|
93
93
|
final_state: {
|
|
94
|
+
coverage?: Record<string, number>;
|
|
94
95
|
status: RuntimeStatus;
|
|
95
96
|
exit_code: number | null;
|
|
96
97
|
step_count: number;
|
|
@@ -116,6 +117,8 @@ export interface SandboxTrace {
|
|
|
116
117
|
};
|
|
117
118
|
}
|
|
118
119
|
export interface RunOptions {
|
|
120
|
+
codecBridge?: (request: string) => string;
|
|
121
|
+
propertyInput?: number[];
|
|
119
122
|
fixtureId?: string;
|
|
120
123
|
traceId?: string;
|
|
121
124
|
replay?: Partial<ReplayConfig>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type VmValue } from "../values.js";
|
|
2
|
+
import { type PackageV1, type TermV1 } from "./package.js";
|
|
3
|
+
import { type ValueProgram } from "./value-operations.js";
|
|
4
|
+
export type CodecBridge = (request: string) => string;
|
|
5
|
+
type CodecTerm = Extract<TermV1, {
|
|
6
|
+
op: "wire.codec";
|
|
7
|
+
}>;
|
|
8
|
+
export declare function executeCodec(pkg: PackageV1, term: CodecTerm, input: VmValue, bridge: CodecBridge | undefined, propertyInput: number[]): ValueProgram;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { UNIT } from "../values.js";
|
|
2
|
+
import { runtimeTag } from "./package.js";
|
|
3
|
+
import { collectionOperation } from "./value-operations.js";
|
|
4
|
+
class DecodeFailure extends Error {
|
|
5
|
+
}
|
|
6
|
+
class Duplicate extends Error {
|
|
7
|
+
detail;
|
|
8
|
+
constructor(detail) {
|
|
9
|
+
super("duplicate decoded key");
|
|
10
|
+
this.detail = detail;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function array(value) {
|
|
14
|
+
if (!Array.isArray(value))
|
|
15
|
+
throw new Error("codec bridge returned a non-positional value");
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
function fields(value) {
|
|
19
|
+
if (value.kind === "record")
|
|
20
|
+
return value.fields;
|
|
21
|
+
if (value.kind === "vector")
|
|
22
|
+
return value.items;
|
|
23
|
+
throw new Error(`codec aggregate received ${value.kind}`);
|
|
24
|
+
}
|
|
25
|
+
function toBridge(pkg, shape, id, value) {
|
|
26
|
+
const node = shape.nodes[id];
|
|
27
|
+
if (node === "Unit")
|
|
28
|
+
return null;
|
|
29
|
+
if (node === "Bool" && value.kind === "bool")
|
|
30
|
+
return value.value;
|
|
31
|
+
if ((node === "Char" || node === "String") && value.kind === "string")
|
|
32
|
+
return value.value;
|
|
33
|
+
if (node === "Bytes")
|
|
34
|
+
return fields(value).map((byte) => {
|
|
35
|
+
if (byte.kind !== "i64")
|
|
36
|
+
throw new Error("invalid byte");
|
|
37
|
+
return Number(byte.value);
|
|
38
|
+
});
|
|
39
|
+
if (typeof node === "string")
|
|
40
|
+
throw new Error(`invalid codec scalar ${node}`);
|
|
41
|
+
if ("Float" in node) {
|
|
42
|
+
if (value.kind !== "f64")
|
|
43
|
+
throw new Error("codec float has wrong VM kind");
|
|
44
|
+
const number = value.value;
|
|
45
|
+
return Object.is(number, -0) ? "-0" : String(number);
|
|
46
|
+
}
|
|
47
|
+
if ("Signed" in node || "Unsigned" in node) {
|
|
48
|
+
if (value.kind !== "i64")
|
|
49
|
+
throw new Error("codec integer has wrong VM kind");
|
|
50
|
+
return ("Unsigned" in node ? BigInt.asUintN(64, value.value) : value.value).toString();
|
|
51
|
+
}
|
|
52
|
+
if ("Tuple" in node)
|
|
53
|
+
return fields(value).map((field, i) => toBridge(pkg, shape, node.Tuple[i], field));
|
|
54
|
+
if ("Record" in node)
|
|
55
|
+
return fields(value).map((field, i) => toBridge(pkg, shape, node.Record[i][1], field));
|
|
56
|
+
if ("Array" in node || "Sequence" in node) {
|
|
57
|
+
const child = "Array" in node ? node.Array.element : node.Sequence;
|
|
58
|
+
return fields(value).map((item) => toBridge(pkg, shape, child, item));
|
|
59
|
+
}
|
|
60
|
+
if ("Option" in node) {
|
|
61
|
+
if (value.kind !== "enum")
|
|
62
|
+
throw new Error("codec Option is not a variant");
|
|
63
|
+
return value.tag === runtimeTag(pkg.variants[value.shape], "OptionNone")
|
|
64
|
+
? null
|
|
65
|
+
: [toBridge(pkg, shape, node.Option, value.payload[0])];
|
|
66
|
+
}
|
|
67
|
+
if ("Variant" in node) {
|
|
68
|
+
if (value.kind !== "enum")
|
|
69
|
+
throw new Error("codec enum is not a variant");
|
|
70
|
+
const child = node.Variant[value.tag][1];
|
|
71
|
+
if (child === null)
|
|
72
|
+
return [value.tag, null];
|
|
73
|
+
const payload = shape.grouped_payloads[child]
|
|
74
|
+
? { kind: "record", typeId: "", fields: value.payload }
|
|
75
|
+
: value.payload[0];
|
|
76
|
+
return [value.tag, toBridge(pkg, shape, child, payload)];
|
|
77
|
+
}
|
|
78
|
+
if (value.kind !== "map")
|
|
79
|
+
throw new Error("codec collection is not a map");
|
|
80
|
+
if ("Set" in node)
|
|
81
|
+
return [...value.entries.values()].map((entry) => [
|
|
82
|
+
toBridge(pkg, shape, node.Set, entry.key),
|
|
83
|
+
null,
|
|
84
|
+
]);
|
|
85
|
+
return [...value.entries.values()].map((entry) => [
|
|
86
|
+
toBridge(pkg, shape, node.Map.key, entry.key),
|
|
87
|
+
toBridge(pkg, shape, node.Map.value, entry.value),
|
|
88
|
+
]);
|
|
89
|
+
}
|
|
90
|
+
function* fromBridge(pkg, shape, id, value, context) {
|
|
91
|
+
if (value !== null &&
|
|
92
|
+
typeof value === "object" &&
|
|
93
|
+
!Array.isArray(value) &&
|
|
94
|
+
value.decode_error)
|
|
95
|
+
throw new DecodeFailure();
|
|
96
|
+
const node = shape.nodes[id];
|
|
97
|
+
const name = shape.names[id] ?? "";
|
|
98
|
+
const aggregate = shape.aggregates[id] ?? undefined;
|
|
99
|
+
const variant = shape.variants[id];
|
|
100
|
+
if (node === "Unit")
|
|
101
|
+
return UNIT;
|
|
102
|
+
if (node === "Bool")
|
|
103
|
+
return { kind: "bool", value: Boolean(value) };
|
|
104
|
+
if (node === "Char" || node === "String")
|
|
105
|
+
return { kind: "string", value: String(value) };
|
|
106
|
+
if (node === "Bytes")
|
|
107
|
+
return {
|
|
108
|
+
kind: "vector",
|
|
109
|
+
elementType: "u8",
|
|
110
|
+
items: array(value).map((byte) => ({
|
|
111
|
+
kind: "i64",
|
|
112
|
+
value: BigInt(String(byte)),
|
|
113
|
+
})),
|
|
114
|
+
};
|
|
115
|
+
if (typeof node === "string")
|
|
116
|
+
throw new Error(`invalid codec scalar ${node}`);
|
|
117
|
+
if ("Float" in node) {
|
|
118
|
+
const number = value === "inf" ? Infinity : value === "-inf" ? -Infinity : Number(value);
|
|
119
|
+
return { kind: "f64", value: number };
|
|
120
|
+
}
|
|
121
|
+
if ("Signed" in node || "Unsigned" in node)
|
|
122
|
+
return { kind: "i64", value: BigInt(String(value)) };
|
|
123
|
+
if ("Option" in node) {
|
|
124
|
+
if (variant === null || variant === undefined)
|
|
125
|
+
throw new Error("codec Option lacks its shape");
|
|
126
|
+
return {
|
|
127
|
+
kind: "enum",
|
|
128
|
+
typeId: name,
|
|
129
|
+
shape: variant,
|
|
130
|
+
tag: runtimeTag(pkg.variants[variant], value === null ? "OptionNone" : "OptionSome"),
|
|
131
|
+
payload: value === null
|
|
132
|
+
? []
|
|
133
|
+
: [
|
|
134
|
+
yield* fromBridge(pkg, shape, node.Option, array(value)[0], context),
|
|
135
|
+
],
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if ("Variant" in node) {
|
|
139
|
+
if (variant === null || variant === undefined)
|
|
140
|
+
throw new Error("codec enum lacks its shape");
|
|
141
|
+
const pair = array(value), tag = Number(pair[0]), child = node.Variant[tag][1];
|
|
142
|
+
let payload = [];
|
|
143
|
+
if (child !== null) {
|
|
144
|
+
const decoded = yield* fromBridge(pkg, shape, child, pair[1], context);
|
|
145
|
+
payload = shape.grouped_payloads[child] ? fields(decoded) : [decoded];
|
|
146
|
+
}
|
|
147
|
+
return { kind: "enum", typeId: name, shape: variant, tag, payload };
|
|
148
|
+
}
|
|
149
|
+
if ("Map" in node || "Set" in node) {
|
|
150
|
+
const family = "Map" in node ? "Map" : "Set";
|
|
151
|
+
const map = yield* collectionOperation(pkg, family, "New", [], [], null);
|
|
152
|
+
for (const item of array(value)) {
|
|
153
|
+
const itemFields = array(item);
|
|
154
|
+
const pair = "Map" in node ? itemFields : [itemFields[0], null];
|
|
155
|
+
const key = yield* fromBridge(pkg, shape, "Map" in node ? node.Map.key : node.Set, pair[0], context);
|
|
156
|
+
const member = "Map" in node
|
|
157
|
+
? yield* fromBridge(pkg, shape, node.Map.value, pair[1], context)
|
|
158
|
+
: UNIT;
|
|
159
|
+
if (map.kind !== "map")
|
|
160
|
+
throw new Error("codec collection has no map storage");
|
|
161
|
+
const size = map.entries.size;
|
|
162
|
+
yield* collectionOperation(pkg, family, "Insert", [map, key, member], context.callbacks[id] ?? [], null);
|
|
163
|
+
if (!context.propertyInput && map.entries.size === size)
|
|
164
|
+
throw new Duplicate(itemFields["Map" in node ? 2 : 1]);
|
|
165
|
+
}
|
|
166
|
+
return map;
|
|
167
|
+
}
|
|
168
|
+
const values = [];
|
|
169
|
+
for (const [i, item] of array(value).entries()) {
|
|
170
|
+
const child = "Tuple" in node
|
|
171
|
+
? node.Tuple[i]
|
|
172
|
+
: "Record" in node
|
|
173
|
+
? node.Record[i][1]
|
|
174
|
+
: "Array" in node
|
|
175
|
+
? node.Array.element
|
|
176
|
+
: node.Sequence;
|
|
177
|
+
values.push(yield* fromBridge(pkg, shape, child, item, context));
|
|
178
|
+
}
|
|
179
|
+
return "Array" in node || "Sequence" in node
|
|
180
|
+
? { kind: "vector", elementType: "", items: values }
|
|
181
|
+
: { kind: "record", typeId: name, shape: aggregate, fields: values };
|
|
182
|
+
}
|
|
183
|
+
export function* executeCodec(pkg, term, input, bridge, propertyInput) {
|
|
184
|
+
if (!bridge)
|
|
185
|
+
throw new Error("typed codecs require the hew-wasm executeCodec bridge");
|
|
186
|
+
const encoded = pkg.codecs?.[term.codec];
|
|
187
|
+
if (encoded === undefined)
|
|
188
|
+
throw new Error("codec shape is absent");
|
|
189
|
+
const shape = JSON.parse(encoded);
|
|
190
|
+
const request = {
|
|
191
|
+
shape: encoded,
|
|
192
|
+
format: term.format,
|
|
193
|
+
direction: term.direction,
|
|
194
|
+
error_root: term.decoded?.error_root,
|
|
195
|
+
...(term.direction === "encode"
|
|
196
|
+
? { value: toBridge(pkg, shape, shape.root, input) }
|
|
197
|
+
: term.direction === "test_input"
|
|
198
|
+
? {
|
|
199
|
+
input: propertyInput,
|
|
200
|
+
ordinal: input.kind === "i64" ? Number(input.value) : -1,
|
|
201
|
+
}
|
|
202
|
+
: {
|
|
203
|
+
input: input.kind === "string"
|
|
204
|
+
? [...new TextEncoder().encode(input.value)]
|
|
205
|
+
: fields(input).map((byte) => byte.kind === "i64" ? Number(byte.value) : -1),
|
|
206
|
+
}),
|
|
207
|
+
};
|
|
208
|
+
let result = JSON.parse(bridge(JSON.stringify(request)));
|
|
209
|
+
if (result.failure !== undefined)
|
|
210
|
+
throw new Error(result.failure);
|
|
211
|
+
if (term.direction === "encode") {
|
|
212
|
+
const bytes = array(result.value).map(Number);
|
|
213
|
+
return term.format === 0 || term.format === 4
|
|
214
|
+
? {
|
|
215
|
+
kind: "vector",
|
|
216
|
+
elementType: "u8",
|
|
217
|
+
items: bytes.map((byte) => ({ kind: "i64", value: BigInt(byte) })),
|
|
218
|
+
}
|
|
219
|
+
: {
|
|
220
|
+
kind: "string",
|
|
221
|
+
value: new TextDecoder("utf-8", {
|
|
222
|
+
fatal: true,
|
|
223
|
+
ignoreBOM: true,
|
|
224
|
+
}).decode(new Uint8Array(bytes)),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
const root = result.ok ? shape.root : term.decoded.error_root;
|
|
228
|
+
const context = {
|
|
229
|
+
callbacks: term.callbacks,
|
|
230
|
+
propertyInput: term.direction === "test_input",
|
|
231
|
+
};
|
|
232
|
+
let decoded;
|
|
233
|
+
try {
|
|
234
|
+
decoded = yield* fromBridge(pkg, shape, root, result.value, context);
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
if (!term.decoded)
|
|
238
|
+
throw error;
|
|
239
|
+
if (error instanceof DecodeFailure && result.error !== undefined) {
|
|
240
|
+
result = { ok: false, value: result.error };
|
|
241
|
+
}
|
|
242
|
+
else if (error instanceof Duplicate) {
|
|
243
|
+
result = JSON.parse(bridge(JSON.stringify({
|
|
244
|
+
...request,
|
|
245
|
+
direction: "error",
|
|
246
|
+
input: [],
|
|
247
|
+
duplicate: error.detail,
|
|
248
|
+
})));
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
throw error;
|
|
252
|
+
}
|
|
253
|
+
if (result.failure !== undefined)
|
|
254
|
+
throw new Error(result.failure);
|
|
255
|
+
decoded = yield* fromBridge(pkg, shape, term.decoded.error_root, result.value, context);
|
|
256
|
+
}
|
|
257
|
+
if (!term.decoded)
|
|
258
|
+
return decoded;
|
|
259
|
+
return {
|
|
260
|
+
kind: "enum",
|
|
261
|
+
typeId: pkg.variants[term.decoded.shape].name,
|
|
262
|
+
shape: term.decoded.shape,
|
|
263
|
+
tag: result.ok ? term.decoded.ok : term.decoded.error,
|
|
264
|
+
payload: [decoded],
|
|
265
|
+
};
|
|
266
|
+
}
|