@hew-lang/sandbox-vm 0.6.1 → 0.6.3

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.
Files changed (52) hide show
  1. package/README.md +48 -4
  2. package/cli/test-runner.mjs +4 -1
  3. package/dist/interpreter/interpreter.d.ts +1 -1
  4. package/dist/interpreter/interpreter.js +1 -1
  5. package/dist/interpreter/parity-runner.js +1 -1
  6. package/dist/interpreter/run-program.d.ts +3 -2
  7. package/dist/interpreter/run-program.js +5 -3
  8. package/dist/interpreter/schema-validator.d.ts +9 -4
  9. package/dist/interpreter/schema-validator.js +15 -49
  10. package/dist/interpreter/strings.d.ts +21 -0
  11. package/dist/interpreter/strings.js +191 -0
  12. package/dist/interpreter/types.d.ts +6 -0
  13. package/dist/interpreter/unicode-data.d.ts +4 -0
  14. package/dist/interpreter/unicode-data.js +1673 -0
  15. package/dist/interpreter/unicode.d.ts +3 -0
  16. package/dist/interpreter/unicode.js +35 -0
  17. package/dist/interpreter/v1/actor-identity.d.ts +3 -0
  18. package/dist/interpreter/v1/actor-identity.js +7 -0
  19. package/dist/interpreter/v1/actor-waits.d.ts +6 -0
  20. package/dist/interpreter/v1/actor-waits.js +33 -0
  21. package/dist/interpreter/v1/browser-directory.d.ts +107 -0
  22. package/dist/interpreter/v1/browser-directory.js +288 -0
  23. package/dist/interpreter/v1/calendar.d.ts +4 -0
  24. package/dist/interpreter/v1/calendar.js +64 -0
  25. package/dist/interpreter/v1/codecs.js +8 -11
  26. package/dist/interpreter/v1/exec.d.ts +2 -2
  27. package/dist/interpreter/v1/exec.js +1761 -489
  28. package/dist/interpreter/v1/instructions.generated.d.ts +1135 -0
  29. package/dist/interpreter/v1/instructions.generated.js +106 -0
  30. package/dist/interpreter/v1/package-graph.d.ts +2 -0
  31. package/dist/interpreter/v1/package-graph.js +729 -0
  32. package/dist/interpreter/v1/package-validator.generated.d.ts +3 -0
  33. package/dist/interpreter/v1/package-validator.generated.js +75902 -0
  34. package/dist/interpreter/v1/package.d.ts +5 -645
  35. package/dist/interpreter/v1/package.js +0 -12
  36. package/dist/interpreter/v1/pipes.d.ts +1 -0
  37. package/dist/interpreter/v1/pipes.js +9 -6
  38. package/dist/interpreter/v1/scheduler.d.ts +8 -1
  39. package/dist/interpreter/v1/scheduler.js +50 -11
  40. package/dist/interpreter/v1/shims.d.ts +260 -7
  41. package/dist/interpreter/v1/shims.js +647 -175
  42. package/dist/interpreter/v1/structural.d.ts +2 -28
  43. package/dist/interpreter/v1/timestamp.d.ts +7 -0
  44. package/dist/interpreter/v1/timestamp.js +54 -0
  45. package/dist/interpreter/v1/validate.d.ts +8 -2
  46. package/dist/interpreter/v1/validate.js +284 -200
  47. package/dist/interpreter/v1/value-operations.d.ts +11 -3
  48. package/dist/interpreter/v1/value-operations.js +70 -18
  49. package/dist/interpreter/values.d.ts +30 -8
  50. package/dist/interpreter/values.js +31 -5
  51. package/dist/interpreter/worker.js +4 -3
  52. package/package.json +4 -4
@@ -1,6 +1,10 @@
1
- import { type VmValue } from "../values.js";
1
+ import type { RuntimeContinuation } from "./shims.js";
2
+ import { type VmValue, type RcOwner } from "../values.js";
2
3
  import { type PackageV1 } from "./package.js";
3
4
  export type ValueRequest = {
5
+ kind: "fault";
6
+ message: string;
7
+ } | {
4
8
  kind: "call";
5
9
  callee: number;
6
10
  args: VmValue[];
@@ -13,8 +17,12 @@ export type ValueProgram = Generator<ValueRequest, VmValue, VmValue>;
13
17
  /** Execute the exact checked plan, including every derived component selection.
14
18
  * A user method yields to an ordinary VM frame and can suspend or fail. */
15
19
  export declare function selectedValue(pkg: PackageV1, id: number, args: VmValue[]): ValueProgram;
16
- export declare function sharedOperation(operation: string, args: VmValue[]): ValueProgram;
20
+ export declare function sharedOperation(operation: Extract<RuntimeContinuation, {
21
+ handler: "shared";
22
+ }>["operation"], args: VmValue[], owner: RcOwner | null, typeName: string, option: (value: VmValue | null) => VmValue): ValueProgram;
17
23
  /** Open-addressed collection operations preserve native callback ordering:
18
24
  * growth hashes occupied slots before probing the incoming key. Staged slots
19
25
  * borrow their owners until every fallible callback has completed. */
20
- export declare function collectionOperation(pkg: PackageV1, family: string, operation: string, args: VmValue[], callbacks: number[], shape: number | null | undefined): ValueProgram;
26
+ export declare function collectionOperation(pkg: PackageV1, request: Extract<RuntimeContinuation, {
27
+ handler: "collection";
28
+ }>, args: VmValue[], callbacks: number[], shape: number | null | undefined): ValueProgram;
@@ -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) => ({
@@ -81,7 +82,7 @@ export function* selectedValue(pkg, id, args) {
81
82
  const plan = pkg.value_capabilities[id];
82
83
  if (!plan)
83
84
  throw new Error(`selected value plan ${id} is missing`);
84
- if (plan.callable !== undefined)
85
+ if (plan.callable != null)
85
86
  return yield { kind: "call", callee: plan.callable, args };
86
87
  const [left, right] = args;
87
88
  if (!left)
@@ -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
- others = right.items;
153
- selections = fields.map(() => components[0]);
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 = OFFSET;
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
- return { kind: "rc", cell: { value: args[0], refs: 1 }, closed: false };
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,21 +308,24 @@ export function* sharedOperation(operation, args) {
263
308
  case "RcStrongCount":
264
309
  return integer(BigInt(receiver.cell.refs));
265
310
  case "RcWeakCount":
266
- return integer(0n);
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];
270
315
  yield { kind: "release", value: old };
271
316
  return UNIT;
272
317
  }
273
- default:
274
- throw new Error(`shared operation ${operation} has no executor`);
318
+ default: {
319
+ const exhaustive = operation;
320
+ throw new Error(`shared operation ${String(exhaustive)} has no executor`);
321
+ }
275
322
  }
276
323
  }
277
324
  /** Open-addressed collection operations preserve native callback ordering:
278
325
  * growth hashes occupied slots before probing the incoming key. Staged slots
279
326
  * borrow their owners until every fallible callback has completed. */
280
- export function* collectionOperation(pkg, family, operation, args, callbacks, shape) {
327
+ export function* collectionOperation(pkg, request, args, callbacks, shape) {
328
+ const { family, operation } = request;
281
329
  if (family === "Vector") {
282
330
  const receiver = args[0];
283
331
  if (receiver?.kind !== "vector")
@@ -337,7 +385,10 @@ export function* collectionOperation(pkg, family, operation, args, callbacks, sh
337
385
  };
338
386
  return receiver;
339
387
  }
340
- if (["Keys", "Values", "Elements", "Entries"].includes(operation)) {
388
+ if (operation === "Keys" ||
389
+ operation === "Values" ||
390
+ operation === "Elements" ||
391
+ operation === "Entries") {
341
392
  return {
342
393
  kind: "vector",
343
394
  elementType: "",
@@ -417,5 +468,6 @@ export function* collectionOperation(pkg, family, operation, args, callbacks, sh
417
468
  ? boolean(result.found)
418
469
  : option(pkg, shape, entry?.value));
419
470
  }
420
- throw new Error(`collection operation ${operation} has no executor`);
471
+ const exhaustive = operation;
472
+ throw new Error(`collection operation ${String(exhaustive)} has no executor`);
421
473
  }
@@ -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
- value: VmValue;
6
- refs: number;
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,15 @@ export type VmValue = {
38
51
  } | {
39
52
  kind: "task";
40
53
  id: string;
54
+ } | {
55
+ kind: "host_resource";
56
+ resource: number;
57
+ owner: import("./v1/browser-directory.js").BrowserFsOwner;
58
+ } | {
59
+ kind: "timer";
60
+ resource: number;
61
+ cancel(): void;
62
+ closed: boolean;
41
63
  } | {
42
64
  kind: "generator";
43
65
  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,24 @@ 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 "host_resource":
33
+ throw new Error("a filesystem owner cannot be copied");
34
+ case "timer":
35
+ throw new Error("a timer cancellation handle cannot be copied");
23
36
  case "unit":
24
37
  return UNIT;
25
38
  case "bool":
26
39
  case "i64":
27
40
  case "f64":
28
- case "string":
29
41
  case "actor":
30
42
  case "supervisor":
31
43
  case "monitor":
@@ -38,6 +50,8 @@ export function cloneValue(value) {
38
50
  case "duplex":
39
51
  case "function": // function values are immutable — identity clone
40
52
  return { ...value };
53
+ case "string":
54
+ return value.clone();
41
55
  case "regex":
42
56
  return {
43
57
  kind: "regex",
@@ -106,9 +120,9 @@ export function valueFromLiteral(value) {
106
120
  ? { kind: "i64", value: BigInt(value) }
107
121
  : { kind: "f64", value };
108
122
  case "string":
109
- return { kind: "string", value };
123
+ return stringValue(value);
110
124
  default:
111
- return { kind: "string", value: canonicalJson(value) };
125
+ return stringValue(canonicalJson(value));
112
126
  }
113
127
  }
114
128
  function taggedI64Literal(value) {
@@ -192,6 +206,10 @@ export function renderF64(value) {
192
206
  }
193
207
  export function renderStdout(value) {
194
208
  switch (value.kind) {
209
+ case "host_resource":
210
+ return "FilesystemOwner";
211
+ case "timer":
212
+ return "Timer";
195
213
  case "unit":
196
214
  return "()";
197
215
  case "bool":
@@ -232,7 +250,9 @@ export function renderStdout(value) {
232
250
  case "vector":
233
251
  return canonicalJson(value.items.map((item) => toJsonValue(item)));
234
252
  case "rc":
235
- return renderStdout(value.cell.value);
253
+ return "Rc";
254
+ case "weak":
255
+ return "Weak";
236
256
  case "map":
237
257
  return canonicalJson(mapEntriesJson(value));
238
258
  case "dyn":
@@ -245,6 +265,10 @@ export function renderStdout(value) {
245
265
  }
246
266
  export function toJsonValue(value) {
247
267
  switch (value.kind) {
268
+ case "host_resource":
269
+ return "FilesystemOwner";
270
+ case "timer":
271
+ return "Timer";
248
272
  case "unit":
249
273
  return null;
250
274
  case "bool":
@@ -280,7 +304,9 @@ export function toJsonValue(value) {
280
304
  case "vector":
281
305
  return value.items.map(toJsonValue);
282
306
  case "rc":
283
- return toJsonValue(value.cell.value);
307
+ return "Rc";
308
+ case "weak":
309
+ return "Weak";
284
310
  case "map":
285
311
  return mapEntriesJson(value);
286
312
  case "dyn":
@@ -3,18 +3,19 @@ import { buildPlaygroundState } from "./playground.js";
3
3
  import { runProgram } from "./run-program.js";
4
4
  import { BytecodeValidationError } from "./schema-validator.js";
5
5
  const worker = globalThis;
6
- worker.addEventListener("message", (event) => {
6
+ worker.addEventListener("message", async (event) => {
7
7
  const { id } = event.data;
8
8
  try {
9
9
  if (event.data.kind === "run") {
10
- const result = runProgram(event.data.source, event.data.stdin);
10
+ const result = await runProgram(event.data.source, event.data.stdin, undefined, event.data.options);
11
11
  worker.postMessage({ id, ok: true, result });
12
12
  return;
13
13
  }
14
14
  const { bytecode, options } = event.data;
15
- const trace = runBytecode(bytecode, {
15
+ const trace = await runBytecode(bytecode, {
16
16
  ...options,
17
17
  codecBridge: globalThis.__hewSandboxExecuteCodec,
18
+ calendarBridge: globalThis.__hewSandboxExecuteCalendar,
18
19
  });
19
20
  worker.postMessage({
20
21
  id,
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@hew-lang/sandbox-vm",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "hewSource": {
5
- "commit": "6a446f773391f45d2c0ce0a799240b8b9afef54e",
6
- "tag": "v0.6.1",
5
+ "commit": "97c1d865aa46afca1c75cf8ef2c1ea0d7f232471",
6
+ "tag": "v0.6.3",
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.1"
24
+ "@hew-lang/wasm": "0.6.3"
25
25
  },
26
26
  "keywords": [
27
27
  "hew",