@hew-lang/sandbox-vm 0.6.1 → 0.6.2
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 +5 -0
- package/dist/interpreter/strings.d.ts +21 -0
- package/dist/interpreter/strings.js +191 -0
- package/dist/interpreter/types.d.ts +2 -0
- package/dist/interpreter/unicode-data.d.ts +4 -0
- package/dist/interpreter/unicode-data.js +1673 -0
- package/dist/interpreter/unicode.d.ts +3 -0
- package/dist/interpreter/unicode.js +35 -0
- package/dist/interpreter/v1/actor-identity.d.ts +3 -0
- package/dist/interpreter/v1/actor-identity.js +7 -0
- package/dist/interpreter/v1/actor-waits.d.ts +6 -0
- package/dist/interpreter/v1/actor-waits.js +33 -0
- package/dist/interpreter/v1/codecs.js +6 -9
- package/dist/interpreter/v1/exec.js +1336 -320
- package/dist/interpreter/v1/package.d.ts +39 -4
- package/dist/interpreter/v1/pipes.d.ts +1 -0
- package/dist/interpreter/v1/pipes.js +9 -6
- package/dist/interpreter/v1/scheduler.d.ts +3 -0
- package/dist/interpreter/v1/scheduler.js +18 -8
- package/dist/interpreter/v1/shims.d.ts +10 -0
- package/dist/interpreter/v1/shims.js +226 -34
- package/dist/interpreter/v1/validate.js +103 -7
- package/dist/interpreter/v1/value-operations.d.ts +5 -2
- package/dist/interpreter/v1/value-operations.js +57 -12
- package/dist/interpreter/values.d.ts +26 -8
- package/dist/interpreter/values.js +25 -5
- package/package.json +4 -4
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { SUPPORTED_NATIVE_IO, SUPPORTED_SUSPEND_KINDS, resolveExternShim, resolveRuntimeShim, } from "./shims.js";
|
|
12
12
|
const UNSUPPORTED = "sandbox.capability.unsupported";
|
|
13
13
|
const NATIVE_FAMILIES = new Set(["AsyncIo", "FileRead", "Tcp"]);
|
|
14
|
-
const NATIVE_SUSPENSIONS = new Set(["NativeIo", "
|
|
14
|
+
const NATIVE_SUSPENSIONS = new Set(["NativeIo", "Read", "Accept"]);
|
|
15
15
|
const OPS = new Set([
|
|
16
16
|
"coverage.hit",
|
|
17
17
|
"const.int",
|
|
@@ -64,6 +64,7 @@ const OPS = new Set([
|
|
|
64
64
|
"task_scope.enter",
|
|
65
65
|
"task_scope.close",
|
|
66
66
|
"task.spawn",
|
|
67
|
+
"task.invoke",
|
|
67
68
|
"task.race",
|
|
68
69
|
]);
|
|
69
70
|
const TERMS = new Set([
|
|
@@ -92,6 +93,7 @@ const TERMS = new Set([
|
|
|
92
93
|
"unreachable",
|
|
93
94
|
]);
|
|
94
95
|
const ACTOR_OPS = new Set([
|
|
96
|
+
"identity",
|
|
95
97
|
"spawn",
|
|
96
98
|
"self_handle",
|
|
97
99
|
"stop",
|
|
@@ -102,6 +104,7 @@ const ACTOR_OPS = new Set([
|
|
|
102
104
|
"call_take",
|
|
103
105
|
"submit",
|
|
104
106
|
"stream_start",
|
|
107
|
+
"timer_schedule",
|
|
105
108
|
"supervisor_spawn",
|
|
106
109
|
"supervisor_child",
|
|
107
110
|
"supervisor_pool_view",
|
|
@@ -114,13 +117,11 @@ function capabilityMessage(capability, native) {
|
|
|
114
117
|
NativeIo: "Host input and output",
|
|
115
118
|
Read: "Host input and output",
|
|
116
119
|
Accept: "Accepting network connections",
|
|
117
|
-
RemoteAsk: "Remote actor calls",
|
|
118
120
|
};
|
|
119
121
|
const features = {
|
|
120
122
|
Vector: "This vector operation",
|
|
121
123
|
Map: "This map operation",
|
|
122
124
|
Set: "This set operation",
|
|
123
|
-
"actor.periodic": "Periodic actor handlers",
|
|
124
125
|
"actor.local_observation": "Actor links and monitors",
|
|
125
126
|
"actor.heap_limit": "Per-actor heap limits",
|
|
126
127
|
"actor.ingress_adapter": "Actor ingress adapters",
|
|
@@ -150,6 +151,7 @@ export function admitPackage(pkg) {
|
|
|
150
151
|
span: null,
|
|
151
152
|
};
|
|
152
153
|
}
|
|
154
|
+
const timerResources = new Set();
|
|
153
155
|
for (const function_ of pkg.functions) {
|
|
154
156
|
for (const block of function_.blocks) {
|
|
155
157
|
if (block.ops.some((op) => op.op === "actor.ingress_adapter"))
|
|
@@ -182,6 +184,66 @@ export function admitPackage(pkg) {
|
|
|
182
184
|
span: null,
|
|
183
185
|
};
|
|
184
186
|
}
|
|
187
|
+
const term = block.term;
|
|
188
|
+
if (term.op === "runtime.call" &&
|
|
189
|
+
pkg.runtime_families[term.family]?.family === "BytesDecodeUtf8") {
|
|
190
|
+
const shapes = term.variant_result_shapes;
|
|
191
|
+
const error = shapes && pkg.aggregates[shapes.error];
|
|
192
|
+
const length = shapes && pkg.variants[shapes.error_len];
|
|
193
|
+
const result = term.result_shape === null
|
|
194
|
+
? undefined
|
|
195
|
+
: pkg.variants[term.result_shape];
|
|
196
|
+
if (!error ||
|
|
197
|
+
error.fields.length !== 2 ||
|
|
198
|
+
!length ||
|
|
199
|
+
!result ||
|
|
200
|
+
length.runtime_tags?.OptionSome === undefined ||
|
|
201
|
+
length.runtime_tags?.OptionNone === undefined ||
|
|
202
|
+
result.runtime_tags?.ResultOk === undefined ||
|
|
203
|
+
result.runtime_tags?.ResultErr === undefined)
|
|
204
|
+
return {
|
|
205
|
+
category: "invalid_package",
|
|
206
|
+
code: "sandbox.package.variant_result_shapes",
|
|
207
|
+
capability: null,
|
|
208
|
+
message: "UTF-8 decode lacks its checked result carrier",
|
|
209
|
+
span: null,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
if (term.op === "actor.call" && term.operation.op === "timer_schedule") {
|
|
213
|
+
const operation = term.operation;
|
|
214
|
+
const handler = pkg.actors
|
|
215
|
+
?.find((actor) => actor.id === operation.actor)
|
|
216
|
+
?.handlers.find((handler) => handler.message_id === operation.message);
|
|
217
|
+
const resource = pkg.resources?.[operation.resource];
|
|
218
|
+
if (!handler ||
|
|
219
|
+
handler.streams ||
|
|
220
|
+
term.args.length !== 5 ||
|
|
221
|
+
!["reject", "drop_newest"].includes(operation.policy) ||
|
|
222
|
+
resource?.kind !== "opaque" ||
|
|
223
|
+
resource.close === undefined ||
|
|
224
|
+
!pkg.functions.some((fn) => fn.id === resource.close))
|
|
225
|
+
return {
|
|
226
|
+
category: "invalid_package",
|
|
227
|
+
code: "sandbox.package.invalid_timer",
|
|
228
|
+
capability: "actor.timer_schedule",
|
|
229
|
+
message: "timer schedule lacks its checked handler, timing or non-waiting admission",
|
|
230
|
+
span: null,
|
|
231
|
+
};
|
|
232
|
+
timerResources.add(operation.resource);
|
|
233
|
+
}
|
|
234
|
+
if (term.op === "suspend") {
|
|
235
|
+
const mandatoryClose = (pkg.resources ?? []).some((resource) => resource.close === function_.id);
|
|
236
|
+
if (term.kind === "Safepoint"
|
|
237
|
+
? (term.cancel == null) !== mandatoryClose
|
|
238
|
+
: term.cancel == null)
|
|
239
|
+
return {
|
|
240
|
+
category: "invalid_package",
|
|
241
|
+
code: "sandbox.package.cancellation_ancestry",
|
|
242
|
+
capability: null,
|
|
243
|
+
message: "safepoint cancellation ancestry must match its authored close lifecycle",
|
|
244
|
+
span: null,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
185
247
|
const unknown = block.ops.find((op) => !OPS.has(op.op))?.op ??
|
|
186
248
|
(!TERMS.has(block.term.op) ? block.term.op : null);
|
|
187
249
|
if (unknown)
|
|
@@ -194,7 +256,7 @@ export function admitPackage(pkg) {
|
|
|
194
256
|
};
|
|
195
257
|
}
|
|
196
258
|
}
|
|
197
|
-
for (const resource of pkg.resources ?? []) {
|
|
259
|
+
for (const [index, resource] of (pkg.resources ?? []).entries()) {
|
|
198
260
|
if (resource.kind === "record" &&
|
|
199
261
|
(resource.shape === undefined ||
|
|
200
262
|
!pkg.aggregates.some((shape) => shape.id === resource.shape)))
|
|
@@ -205,15 +267,49 @@ export function admitPackage(pkg) {
|
|
|
205
267
|
message: "record resource has no exact aggregate shape",
|
|
206
268
|
span: null,
|
|
207
269
|
};
|
|
208
|
-
if (resource.kind === "opaque")
|
|
270
|
+
if (resource.kind === "opaque" && !timerResources.has(index))
|
|
209
271
|
return unavailable("resource.opaque_close");
|
|
210
272
|
if (resource.kind === "nominal" &&
|
|
211
273
|
!resolveExternShim(resource.release ?? ""))
|
|
212
274
|
return unavailable("resource.external_close");
|
|
213
275
|
}
|
|
214
276
|
for (const actor of pkg.actors ?? []) {
|
|
215
|
-
if (actor.
|
|
216
|
-
return
|
|
277
|
+
if (typeof actor.name !== "string" || actor.name.length === 0)
|
|
278
|
+
return {
|
|
279
|
+
category: "invalid_package",
|
|
280
|
+
code: "sandbox.package.actor_name",
|
|
281
|
+
capability: null,
|
|
282
|
+
message: "actor lacks its declared diagnostic name",
|
|
283
|
+
span: null,
|
|
284
|
+
};
|
|
285
|
+
if (actor.crash != null &&
|
|
286
|
+
(actor.crash_info == null ||
|
|
287
|
+
!pkg.aggregates[actor.crash_info] ||
|
|
288
|
+
actor.crash_kind == null ||
|
|
289
|
+
!pkg.variants[actor.crash_kind] ||
|
|
290
|
+
actor.crash_action == null ||
|
|
291
|
+
!pkg.variants[actor.crash_action]))
|
|
292
|
+
return {
|
|
293
|
+
category: "invalid_package",
|
|
294
|
+
code: "sandbox.package.crash_hook_shapes",
|
|
295
|
+
capability: null,
|
|
296
|
+
message: "crash hook lacks its exact info, kind or action shape",
|
|
297
|
+
span: null,
|
|
298
|
+
};
|
|
299
|
+
if (actor.handlers.some((handler) => handler.every_ns != null &&
|
|
300
|
+
(!Number.isInteger(handler.every_ns) ||
|
|
301
|
+
handler.every_ns < 1_000_000 ||
|
|
302
|
+
handler.params !== 0 ||
|
|
303
|
+
handler.streams ||
|
|
304
|
+
handler.fallible ||
|
|
305
|
+
!pkg.functions.some((fn) => fn.id === handler.callable && fn.params.length === 1))))
|
|
306
|
+
return {
|
|
307
|
+
category: "invalid_package",
|
|
308
|
+
code: "sandbox.package.invalid_periodic",
|
|
309
|
+
capability: "actor.periodic",
|
|
310
|
+
message: "periodic handler lacks its checked interval or zero-argument body",
|
|
311
|
+
span: null,
|
|
312
|
+
};
|
|
217
313
|
if (actor.exit != null || actor.down != null)
|
|
218
314
|
return unavailable("actor.local_observation");
|
|
219
315
|
if (actor.max_heap_bytes != null)
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { type VmValue } from "../values.js";
|
|
1
|
+
import { type VmValue, type RcOwner } from "../values.js";
|
|
2
2
|
import { type PackageV1 } from "./package.js";
|
|
3
3
|
export type ValueRequest = {
|
|
4
|
+
kind: "fault";
|
|
5
|
+
message: string;
|
|
6
|
+
} | {
|
|
4
7
|
kind: "call";
|
|
5
8
|
callee: number;
|
|
6
9
|
args: VmValue[];
|
|
@@ -13,7 +16,7 @@ export type ValueProgram = Generator<ValueRequest, VmValue, VmValue>;
|
|
|
13
16
|
/** Execute the exact checked plan, including every derived component selection.
|
|
14
17
|
* A user method yields to an ordinary VM frame and can suspend or fail. */
|
|
15
18
|
export declare function selectedValue(pkg: PackageV1, id: number, args: VmValue[]): ValueProgram;
|
|
16
|
-
export declare function sharedOperation(operation: string, args: VmValue[]): ValueProgram;
|
|
19
|
+
export declare function sharedOperation(operation: string, args: VmValue[], owner: RcOwner | null, typeName: string, option: (value: VmValue | null) => VmValue): ValueProgram;
|
|
17
20
|
/** Open-addressed collection operations preserve native callback ordering:
|
|
18
21
|
* growth hashes occupied slots before probing the incoming key. Staged slots
|
|
19
22
|
* borrow their owners until every fallible callback has completed. */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { UNIT, cloneValue, compareUtf8 } from "../values.js";
|
|
1
|
+
import { UNIT, cloneValue, compareUtf8, } from "../values.js";
|
|
2
2
|
import { runtimeTag, } from "./package.js";
|
|
3
3
|
import { ShimFault } from "./shims.js";
|
|
4
|
+
import { actorIdentical } from "./actor-identity.js";
|
|
4
5
|
const OFFSET = 0xcbf29ce484222325n;
|
|
5
6
|
const PRIME = 0x100000001b3n;
|
|
6
7
|
const integer = (value) => ({
|
|
@@ -117,10 +118,16 @@ export function* selectedValue(pkg, id, args) {
|
|
|
117
118
|
bytes.every((value, i) => BigInt(value) === bits(right.items[i], "u8")));
|
|
118
119
|
throw new Error("bytes ordering is outside the checked capability domain");
|
|
119
120
|
}
|
|
121
|
+
if (left.kind === "actor" || left.kind === "supervisor") {
|
|
122
|
+
if (equality)
|
|
123
|
+
return boolean(actorIdentical(left, right));
|
|
124
|
+
throw new Error("actor capability is outside the checked equality domain");
|
|
125
|
+
}
|
|
120
126
|
const components = plan.components ?? [];
|
|
121
127
|
let fields;
|
|
122
128
|
let others = [];
|
|
123
129
|
let selections;
|
|
130
|
+
let vectorHashLength;
|
|
124
131
|
if (left.kind === "record") {
|
|
125
132
|
fields = left.fields;
|
|
126
133
|
selections = components;
|
|
@@ -144,13 +151,20 @@ export function* selectedValue(pkg, id, args) {
|
|
|
144
151
|
selections = plan.variants?.[left.tag] ?? [];
|
|
145
152
|
}
|
|
146
153
|
else if (left.kind === "vector") {
|
|
147
|
-
if (hash)
|
|
148
|
-
throw new Error("derived vector Hash is outside the checked capability domain");
|
|
149
|
-
if (right?.kind !== "vector" || left.items.length !== right.items.length)
|
|
150
|
-
return boolean(false);
|
|
151
154
|
fields = left.items;
|
|
152
|
-
|
|
153
|
-
|
|
155
|
+
if (hash) {
|
|
156
|
+
const elementSelection = components[0];
|
|
157
|
+
if (!elementSelection)
|
|
158
|
+
throw new Error("derived vector Hash lacks its checked element selection");
|
|
159
|
+
selections = fields.map(() => elementSelection);
|
|
160
|
+
vectorHashLength = fields.length;
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
if (right?.kind !== "vector" || left.items.length !== right.items.length)
|
|
164
|
+
return boolean(false);
|
|
165
|
+
others = right.items;
|
|
166
|
+
selections = fields.map(() => components[0]);
|
|
167
|
+
}
|
|
154
168
|
}
|
|
155
169
|
else {
|
|
156
170
|
if (hash)
|
|
@@ -159,7 +173,9 @@ export function* selectedValue(pkg, id, args) {
|
|
|
159
173
|
return boolean(!!right && bits(left, plan.ty) === bits(right, plan.ty));
|
|
160
174
|
return scalarOrdering(left, right, plan.ty, ordering);
|
|
161
175
|
}
|
|
162
|
-
let state =
|
|
176
|
+
let state = vectorHashLength === undefined
|
|
177
|
+
? OFFSET
|
|
178
|
+
: mix(OFFSET, BigInt(vectorHashLength));
|
|
163
179
|
for (const [at, field] of fields.entries()) {
|
|
164
180
|
const selection = selections[at];
|
|
165
181
|
if (!selection)
|
|
@@ -247,15 +263,44 @@ function option(pkg, shape, value) {
|
|
|
247
263
|
payload: value === undefined ? [] : [value],
|
|
248
264
|
};
|
|
249
265
|
}
|
|
250
|
-
export function* sharedOperation(operation, args) {
|
|
251
|
-
if (operation === "RcNew")
|
|
252
|
-
|
|
266
|
+
export function* sharedOperation(operation, args, owner, typeName, option) {
|
|
267
|
+
if (operation === "RcNew") {
|
|
268
|
+
if (owner?.closed)
|
|
269
|
+
throw new Error("Rc allocation after actor cleanup quiesced");
|
|
270
|
+
const cell = {
|
|
271
|
+
value: args[0],
|
|
272
|
+
refs: 1,
|
|
273
|
+
weak: 0,
|
|
274
|
+
dying: false,
|
|
275
|
+
owner,
|
|
276
|
+
typeName,
|
|
277
|
+
};
|
|
278
|
+
owner?.blocks.add(cell);
|
|
279
|
+
return { kind: "rc", cell, closed: false };
|
|
280
|
+
}
|
|
253
281
|
const receiver = args[0];
|
|
282
|
+
if (operation === "WeakCloneRc" || operation === "WeakUpgradeRc") {
|
|
283
|
+
if (receiver?.kind !== "weak" || receiver.closed)
|
|
284
|
+
throw new Error("shared operation has no live Weak");
|
|
285
|
+
if (operation === "WeakCloneRc")
|
|
286
|
+
return cloneValue(receiver);
|
|
287
|
+
if (receiver.cell.dying || receiver.cell.refs === 0)
|
|
288
|
+
return option(null);
|
|
289
|
+
receiver.cell.refs += 1;
|
|
290
|
+
return option({ kind: "rc", cell: receiver.cell, closed: false });
|
|
291
|
+
}
|
|
254
292
|
if (receiver?.kind !== "rc" || receiver.closed)
|
|
255
293
|
throw new Error("shared operation has no live Rc");
|
|
294
|
+
if (!receiver.cell || receiver.cell.dying) {
|
|
295
|
+
yield { kind: "fault", message: "Rc payload is being released" };
|
|
296
|
+
return UNIT;
|
|
297
|
+
}
|
|
256
298
|
switch (operation) {
|
|
257
299
|
case "RcClone":
|
|
258
300
|
return cloneValue(receiver);
|
|
301
|
+
case "RcDowngrade":
|
|
302
|
+
receiver.cell.weak += 1;
|
|
303
|
+
return { kind: "weak", cell: receiver.cell, closed: false };
|
|
259
304
|
case "RcGet":
|
|
260
305
|
return cloneValue(receiver.cell.value);
|
|
261
306
|
case "RcIsUnique":
|
|
@@ -263,7 +308,7 @@ export function* sharedOperation(operation, args) {
|
|
|
263
308
|
case "RcStrongCount":
|
|
264
309
|
return integer(BigInt(receiver.cell.refs));
|
|
265
310
|
case "RcWeakCount":
|
|
266
|
-
return integer(
|
|
311
|
+
return integer(BigInt(receiver.cell.weak));
|
|
267
312
|
case "RcSet": {
|
|
268
313
|
const old = receiver.cell.value;
|
|
269
314
|
receiver.cell.value = args[1];
|
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
import type { JsonValue } from "./types.js";
|
|
2
|
+
import { IndexedString } from "./strings.js";
|
|
3
|
+
export { stringValue } from "./strings.js";
|
|
4
|
+
/** One creator-owned membership list; payload destruction uses the ordinary VM release path. */
|
|
5
|
+
export interface RcOwner {
|
|
6
|
+
blocks: Set<RcCell>;
|
|
7
|
+
closed: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface RcCell {
|
|
10
|
+
value: VmValue;
|
|
11
|
+
refs: number;
|
|
12
|
+
weak: number;
|
|
13
|
+
dying: boolean;
|
|
14
|
+
owner: RcOwner | null;
|
|
15
|
+
typeName: string;
|
|
16
|
+
}
|
|
2
17
|
export type VmValue = {
|
|
3
18
|
kind: "rc";
|
|
4
|
-
cell:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
19
|
+
cell: RcCell | null;
|
|
20
|
+
closed: boolean;
|
|
21
|
+
} | {
|
|
22
|
+
kind: "weak";
|
|
23
|
+
cell: RcCell;
|
|
8
24
|
closed: boolean;
|
|
9
25
|
} | {
|
|
10
26
|
kind: "unit";
|
|
@@ -17,10 +33,7 @@ export type VmValue = {
|
|
|
17
33
|
} | {
|
|
18
34
|
kind: "f64";
|
|
19
35
|
value: number;
|
|
20
|
-
} | {
|
|
21
|
-
kind: "string";
|
|
22
|
-
value: string;
|
|
23
|
-
} | {
|
|
36
|
+
} | IndexedString | {
|
|
24
37
|
kind: "actor";
|
|
25
38
|
id: string;
|
|
26
39
|
} | {
|
|
@@ -38,6 +51,11 @@ export type VmValue = {
|
|
|
38
51
|
} | {
|
|
39
52
|
kind: "task";
|
|
40
53
|
id: string;
|
|
54
|
+
} | {
|
|
55
|
+
kind: "timer";
|
|
56
|
+
resource: number;
|
|
57
|
+
cancel(): void;
|
|
58
|
+
closed: boolean;
|
|
41
59
|
} | {
|
|
42
60
|
kind: "generator";
|
|
43
61
|
id: string;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { stringValue } from "./strings.js";
|
|
2
|
+
export { stringValue } from "./strings.js";
|
|
1
3
|
export const UNIT = { kind: "unit" };
|
|
2
4
|
const UTF8_ENCODER = new TextEncoder();
|
|
3
5
|
export function compareUtf8(a, b) {
|
|
@@ -18,14 +20,22 @@ export function cloneValue(value) {
|
|
|
18
20
|
case "rc":
|
|
19
21
|
if (value.closed)
|
|
20
22
|
throw new Error("cannot copy a released Rc");
|
|
23
|
+
if (!value.cell || value.cell.dying)
|
|
24
|
+
return { kind: "rc", cell: null, closed: false };
|
|
21
25
|
value.cell.refs += 1;
|
|
22
26
|
return { kind: "rc", cell: value.cell, closed: false };
|
|
27
|
+
case "weak":
|
|
28
|
+
if (value.closed)
|
|
29
|
+
throw new Error("cannot copy a released Weak");
|
|
30
|
+
value.cell.weak += 1;
|
|
31
|
+
return { kind: "weak", cell: value.cell, closed: false };
|
|
32
|
+
case "timer":
|
|
33
|
+
throw new Error("a timer cancellation handle cannot be copied");
|
|
23
34
|
case "unit":
|
|
24
35
|
return UNIT;
|
|
25
36
|
case "bool":
|
|
26
37
|
case "i64":
|
|
27
38
|
case "f64":
|
|
28
|
-
case "string":
|
|
29
39
|
case "actor":
|
|
30
40
|
case "supervisor":
|
|
31
41
|
case "monitor":
|
|
@@ -38,6 +48,8 @@ export function cloneValue(value) {
|
|
|
38
48
|
case "duplex":
|
|
39
49
|
case "function": // function values are immutable — identity clone
|
|
40
50
|
return { ...value };
|
|
51
|
+
case "string":
|
|
52
|
+
return value.clone();
|
|
41
53
|
case "regex":
|
|
42
54
|
return {
|
|
43
55
|
kind: "regex",
|
|
@@ -106,9 +118,9 @@ export function valueFromLiteral(value) {
|
|
|
106
118
|
? { kind: "i64", value: BigInt(value) }
|
|
107
119
|
: { kind: "f64", value };
|
|
108
120
|
case "string":
|
|
109
|
-
return
|
|
121
|
+
return stringValue(value);
|
|
110
122
|
default:
|
|
111
|
-
return
|
|
123
|
+
return stringValue(canonicalJson(value));
|
|
112
124
|
}
|
|
113
125
|
}
|
|
114
126
|
function taggedI64Literal(value) {
|
|
@@ -192,6 +204,8 @@ export function renderF64(value) {
|
|
|
192
204
|
}
|
|
193
205
|
export function renderStdout(value) {
|
|
194
206
|
switch (value.kind) {
|
|
207
|
+
case "timer":
|
|
208
|
+
return "Timer";
|
|
195
209
|
case "unit":
|
|
196
210
|
return "()";
|
|
197
211
|
case "bool":
|
|
@@ -232,7 +246,9 @@ export function renderStdout(value) {
|
|
|
232
246
|
case "vector":
|
|
233
247
|
return canonicalJson(value.items.map((item) => toJsonValue(item)));
|
|
234
248
|
case "rc":
|
|
235
|
-
return
|
|
249
|
+
return "Rc";
|
|
250
|
+
case "weak":
|
|
251
|
+
return "Weak";
|
|
236
252
|
case "map":
|
|
237
253
|
return canonicalJson(mapEntriesJson(value));
|
|
238
254
|
case "dyn":
|
|
@@ -245,6 +261,8 @@ export function renderStdout(value) {
|
|
|
245
261
|
}
|
|
246
262
|
export function toJsonValue(value) {
|
|
247
263
|
switch (value.kind) {
|
|
264
|
+
case "timer":
|
|
265
|
+
return "Timer";
|
|
248
266
|
case "unit":
|
|
249
267
|
return null;
|
|
250
268
|
case "bool":
|
|
@@ -280,7 +298,9 @@ export function toJsonValue(value) {
|
|
|
280
298
|
case "vector":
|
|
281
299
|
return value.items.map(toJsonValue);
|
|
282
300
|
case "rc":
|
|
283
|
-
return
|
|
301
|
+
return "Rc";
|
|
302
|
+
case "weak":
|
|
303
|
+
return "Weak";
|
|
284
304
|
case "map":
|
|
285
305
|
return mapEntriesJson(value);
|
|
286
306
|
case "dyn":
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hew-lang/sandbox-vm",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"hewSource": {
|
|
5
|
-
"commit": "
|
|
6
|
-
"tag": "v0.6.
|
|
5
|
+
"commit": "dd0911184af12136eda7e89052e5b564d56ba643",
|
|
6
|
+
"tag": "v0.6.2",
|
|
7
7
|
"dirty": false
|
|
8
8
|
},
|
|
9
9
|
"description": "Deterministic TypeScript interpreter for the Hew educational sandbox",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"cli/"
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@hew-lang/wasm": "0.6.
|
|
24
|
+
"@hew-lang/wasm": "0.6.2"
|
|
25
25
|
},
|
|
26
26
|
"keywords": [
|
|
27
27
|
"hew",
|