@hew-lang/sandbox-vm 0.6.0-rc7 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
- import { UNIT, cloneValue } from "../values.js";
2
- import { runtimeTag } from "./package.js";
1
+ import { UNIT, cloneValue, compareUtf8 } from "../values.js";
2
+ import { runtimeTag, } from "./package.js";
3
3
  import { ShimFault } from "./shims.js";
4
4
  const OFFSET = 0xcbf29ce484222325n;
5
5
  const PRIME = 0x100000001b3n;
@@ -20,6 +20,38 @@ function bytesHash(bytes) {
20
20
  state = mix(state, BigInt(byte));
21
21
  return state;
22
22
  }
23
+ function orderingResult(capability, comparison) {
24
+ switch (capability) {
25
+ case "Less":
26
+ return boolean(comparison < 0);
27
+ case "LessEqual":
28
+ return boolean(comparison <= 0);
29
+ case "Greater":
30
+ return boolean(comparison > 0);
31
+ case "GreaterEqual":
32
+ return boolean(comparison >= 0);
33
+ }
34
+ }
35
+ function scalarOrdering(left, right, ty, capability) {
36
+ if (left.kind === "f64") {
37
+ if (right?.kind !== "f64" ||
38
+ Number.isNaN(left.value) ||
39
+ Number.isNaN(right.value))
40
+ return boolean(false);
41
+ return orderingResult(capability, left.value < right.value ? -1 : left.value > right.value ? 1 : 0);
42
+ }
43
+ if (!right || right.kind !== left.kind)
44
+ return boolean(false);
45
+ const signed = /^(?:i(?:8|16|32|64)|isize|duration)$/.test(ty);
46
+ const width = Number(ty.match(/^[iu](8|16|32|64)$/)?.[1] ?? 64);
47
+ const lhs = left.kind === "i64" && signed
48
+ ? BigInt.asIntN(width, left.value)
49
+ : bits(left, ty);
50
+ const rhs = right.kind === "i64" && signed
51
+ ? BigInt.asIntN(width, right.value)
52
+ : bits(right, ty);
53
+ return orderingResult(capability, lhs < rhs ? -1 : lhs > rhs ? 1 : 0);
54
+ }
23
55
  function bits(value, ty) {
24
56
  switch (value.kind) {
25
57
  case "unit":
@@ -55,22 +87,35 @@ export function* selectedValue(pkg, id, args) {
55
87
  if (!left)
56
88
  throw new Error("selected value operation has no receiver");
57
89
  const hash = plan.capability === "Hash";
90
+ const equality = plan.capability === "Eq";
91
+ const ordering = plan.capability === "Less" ||
92
+ plan.capability === "LessEqual" ||
93
+ plan.capability === "Greater" ||
94
+ plan.capability === "GreaterEqual"
95
+ ? plan.capability
96
+ : undefined;
58
97
  if (plan.ty === "string") {
59
98
  if (left.kind !== "string")
60
99
  throw new Error("string capability has no string");
61
- return hash
62
- ? integer(bytesHash(new TextEncoder().encode(left.value)))
63
- : boolean(right?.kind === "string" && left.value === right.value);
100
+ if (hash)
101
+ return integer(bytesHash(new TextEncoder().encode(left.value)));
102
+ if (right?.kind !== "string")
103
+ return boolean(false);
104
+ if (equality)
105
+ return boolean(left.value === right.value);
106
+ return orderingResult(ordering, compareUtf8(left.value, right.value));
64
107
  }
65
108
  if (plan.ty === "bytes") {
66
109
  if (left.kind !== "vector")
67
110
  throw new Error("bytes capability has no bytes");
68
111
  const bytes = left.items.map((value) => Number(bits(value, "u8")));
69
- return hash
70
- ? integer(bytesHash(bytes))
71
- : boolean(right?.kind === "vector" &&
112
+ if (hash)
113
+ return integer(bytesHash(bytes));
114
+ if (equality)
115
+ return boolean(right?.kind === "vector" &&
72
116
  bytes.length === right.items.length &&
73
117
  bytes.every((value, i) => BigInt(value) === bits(right.items[i], "u8")));
118
+ throw new Error("bytes ordering is outside the checked capability domain");
74
119
  }
75
120
  const components = plan.components ?? [];
76
121
  let fields;
@@ -88,7 +133,11 @@ export function* selectedValue(pkg, id, args) {
88
133
  else if (left.kind === "enum") {
89
134
  if (hash)
90
135
  throw new Error("derived enum Hash is outside the checked capability domain");
91
- if (right?.kind !== "enum" || left.tag !== right.tag)
136
+ if (right?.kind !== "enum")
137
+ return boolean(false);
138
+ if (ordering && left.tag !== right.tag)
139
+ return orderingResult(ordering, left.tag < right.tag ? -1 : 1);
140
+ if (left.tag !== right.tag)
92
141
  return boolean(false);
93
142
  fields = left.payload;
94
143
  others = right.payload;
@@ -104,13 +153,40 @@ export function* selectedValue(pkg, id, args) {
104
153
  selections = fields.map(() => components[0]);
105
154
  }
106
155
  else {
107
- return hash
108
- ? integer(bits(left, plan.ty))
109
- : boolean(!!right && bits(left, plan.ty) === bits(right, plan.ty));
156
+ if (hash)
157
+ return integer(bits(left, plan.ty));
158
+ if (equality)
159
+ return boolean(!!right && bits(left, plan.ty) === bits(right, plan.ty));
160
+ return scalarOrdering(left, right, plan.ty, ordering);
110
161
  }
111
162
  let state = OFFSET;
112
163
  for (const [at, field] of fields.entries()) {
113
- const result = yield* selectedValue(pkg, selections[at], hash ? [field] : [field, others[at]]);
164
+ const selection = selections[at];
165
+ if (!selection)
166
+ throw new Error("derived value operation lacks a component selection");
167
+ if (ordering) {
168
+ const selected = selection.ordering;
169
+ if (!selected)
170
+ throw new Error("derived ordering lacks exact component predicates");
171
+ const pair = [field, others[at]];
172
+ const less = yield* selectedValue(pkg, selected.less, pair);
173
+ if (less.kind !== "bool")
174
+ throw new Error("Less returned a non-boolean value");
175
+ if (less.value)
176
+ return boolean(ordering === "Less" || ordering === "LessEqual");
177
+ const greater = yield* selectedValue(pkg, selected.greater, pair);
178
+ if (greater.kind !== "bool")
179
+ throw new Error("Greater returned a non-boolean value");
180
+ if (greater.value)
181
+ return boolean(ordering === "Greater" || ordering === "GreaterEqual");
182
+ const comparable = yield* selectedValue(pkg, selected.less_equal, pair);
183
+ if (comparable.kind !== "bool")
184
+ throw new Error("LessEqual returned a non-boolean value");
185
+ if (!comparable.value)
186
+ return boolean(false);
187
+ continue;
188
+ }
189
+ const result = yield* selectedValue(pkg, selection.selected, hash ? [field] : [field, others[at]]);
114
190
  if (hash) {
115
191
  if (result.kind !== "i64")
116
192
  throw new Error("Hash returned a non-integer value");
@@ -123,7 +199,11 @@ export function* selectedValue(pkg, id, args) {
123
199
  return boolean(false);
124
200
  }
125
201
  }
126
- return hash ? integer(state) : boolean(true);
202
+ if (hash)
203
+ return integer(state);
204
+ if (equality)
205
+ return boolean(true);
206
+ return boolean(ordering === "LessEqual" || ordering === "GreaterEqual");
127
207
  }
128
208
  function ordered(entries) {
129
209
  return new Map([...entries].sort(([a], [b]) => Number(a) - Number(b)));
@@ -102,10 +102,15 @@ export type VmValue = {
102
102
  * owned by the closure, so `cloneValue` clones it. */
103
103
  | {
104
104
  kind: "closure";
105
- body: number;
105
+ entries: ReadonlyArray<{
106
+ body: number;
107
+ specialization: number | null;
108
+ }>;
109
+ specialization: number | null;
106
110
  environment: VmValue;
107
111
  };
108
112
  export declare const UNIT: VmValue;
113
+ export declare function compareUtf8(a: string, b: string): number;
109
114
  export declare function cloneValue(value: VmValue): VmValue;
110
115
  export declare function valueFromLiteral(value: JsonValue): VmValue;
111
116
  export declare function renderF64(value: number): string;
@@ -1,4 +1,17 @@
1
1
  export const UNIT = { kind: "unit" };
2
+ const UTF8_ENCODER = new TextEncoder();
3
+ export function compareUtf8(a, b) {
4
+ const left = UTF8_ENCODER.encode(a);
5
+ const right = UTF8_ENCODER.encode(b);
6
+ const length = Math.min(left.length, right.length);
7
+ for (let i = 0; i < length; i++) {
8
+ if (left[i] < right[i])
9
+ return -1;
10
+ if (left[i] > right[i])
11
+ return 1;
12
+ }
13
+ return left.length < right.length ? -1 : left.length > right.length ? 1 : 0;
14
+ }
2
15
  const I64_LITERAL_DECIMAL = /^(?:0|-?[1-9]\d*)$/;
3
16
  export function cloneValue(value) {
4
17
  switch (value.kind) {
@@ -61,7 +74,8 @@ export function cloneValue(value) {
61
74
  case "closure":
62
75
  return {
63
76
  kind: "closure",
64
- body: value.body,
77
+ entries: value.entries,
78
+ specialization: value.specialization,
65
79
  environment: cloneValue(value.environment),
66
80
  };
67
81
  case "map":
@@ -226,7 +240,7 @@ export function renderStdout(value) {
226
240
  case "function":
227
241
  return value.id;
228
242
  case "closure":
229
- return `closure:${value.body}`;
243
+ return `closure:${value.entries.find((entry) => entry.specialization === value.specialization)?.body ?? "generic"}`;
230
244
  }
231
245
  }
232
246
  export function toJsonValue(value) {
@@ -274,7 +288,7 @@ export function toJsonValue(value) {
274
288
  case "function":
275
289
  return value.id;
276
290
  case "closure":
277
- return `closure:${value.body}`;
291
+ return `closure:${value.entries.find((entry) => entry.specialization === value.specialization)?.body ?? "generic"}`;
278
292
  }
279
293
  }
280
294
  /// A map renders as its entries in insertion order, each a `[key, value]` pair.
@@ -12,14 +12,32 @@ worker.addEventListener("message", (event) => {
12
12
  return;
13
13
  }
14
14
  const { bytecode, options } = event.data;
15
- const trace = runBytecode(bytecode, options);
16
- worker.postMessage({ id, ok: true, trace, playground: buildPlaygroundState(trace) });
15
+ const trace = runBytecode(bytecode, {
16
+ ...options,
17
+ codecBridge: globalThis.__hewSandboxExecuteCodec,
18
+ });
19
+ worker.postMessage({
20
+ id,
21
+ ok: true,
22
+ trace,
23
+ playground: buildPlaygroundState(trace),
24
+ });
17
25
  }
18
26
  catch (error) {
19
27
  if (error instanceof BytecodeValidationError) {
20
- worker.postMessage({ id, ok: false, error: "bytecode_validation", diagnostics: error.errors });
28
+ worker.postMessage({
29
+ id,
30
+ ok: false,
31
+ error: "bytecode_validation",
32
+ diagnostics: error.errors,
33
+ });
21
34
  return;
22
35
  }
23
- worker.postMessage({ id, ok: false, error: "interpreter_error", message: String(error) });
36
+ worker.postMessage({
37
+ id,
38
+ ok: false,
39
+ error: "interpreter_error",
40
+ message: String(error),
41
+ });
24
42
  }
25
43
  });
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@hew-lang/sandbox-vm",
3
- "version": "0.6.0-rc7",
3
+ "version": "0.6.1",
4
4
  "hewSource": {
5
- "commit": "f09d3403587d1cf1bc33d00444bc76ae96861c17",
6
- "tag": "v0.6.0-rc7",
5
+ "commit": "6a446f773391f45d2c0ce0a799240b8b9afef54e",
6
+ "tag": "v0.6.1",
7
7
  "dirty": false
8
8
  },
9
9
  "description": "Deterministic TypeScript interpreter for the Hew educational sandbox",
@@ -17,8 +17,12 @@
17
17
  }
18
18
  },
19
19
  "files": [
20
- "dist/"
20
+ "dist/",
21
+ "cli/"
21
22
  ],
23
+ "dependencies": {
24
+ "@hew-lang/wasm": "0.6.1"
25
+ },
22
26
  "keywords": [
23
27
  "hew",
24
28
  "sandbox",