@hew-lang/sandbox-vm 0.6.0 → 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 CHANGED
@@ -48,6 +48,11 @@ 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
50
 
51
+ `RunOptions.argv` supplies process arguments, including the executable name at
52
+ index zero. It defaults to `["main.hew"]` and is copied for each run. For example,
53
+ `runBytecode(bytecode, { argv: ["greet", "--", "Zoë", ""] })` supplies those exact
54
+ strings to `std.os.args()`. The VM never reads Node or browser process arguments.
55
+
51
56
  Typed serialization executes the shared Rust codecs in `@hew-lang/wasm`.
52
57
  Pass its initialized `executeCodec` export as `runBytecode`'s `codecBridge`
53
58
  option. The Node test host loads that package automatically; in a source
@@ -59,6 +59,7 @@ var SandboxExitCode;
59
59
  SandboxExitCode[SandboxExitCode["ExhaustivenessFallthrough"] = 208] = "ExhaustivenessFallthrough";
60
60
  })(SandboxExitCode || (SandboxExitCode = {}));
61
61
  const TRAP_EXIT_CODES = {
62
+ actor_refused: SandboxExitCode.InternalError,
62
63
  integer_overflow: SandboxExitCode.IntegerOverflow,
63
64
  shift_out_of_range: SandboxExitCode.ShiftOutOfRange,
64
65
  divide_by_zero: SandboxExitCode.DivideByZero,
@@ -0,0 +1,21 @@
1
+ export declare class IndexedString {
2
+ readonly kind = "string";
3
+ private storage;
4
+ private constructor();
5
+ static fromText(text: string): IndexedString;
6
+ private live;
7
+ get value(): string;
8
+ get scalarLength(): number;
9
+ get byteLength(): number;
10
+ encoded(): Uint8Array;
11
+ clone(): IndexedString;
12
+ release(): void;
13
+ scalarOffset(index: number): number | undefined;
14
+ at(index: number): string | undefined;
15
+ slice(start: number, end: number): IndexedString | undefined;
16
+ private scalarIndex;
17
+ find(needle: IndexedString, start?: number): number | undefined;
18
+ /** Consume this receiver, retaining the suffix's borrowed snapshot. */
19
+ append(suffix: IndexedString): IndexedString;
20
+ }
21
+ export declare function stringValue(value: string): IndexedString;
@@ -0,0 +1,191 @@
1
+ /** The VM's owned UTF-8 string storage. Scalar positions use the same
2
+ * bounded-stride index as native strings; no prefix is rescanned on access. */
3
+ const INDEX_STRIDE = 32;
4
+ const ENCODER = new TextEncoder();
5
+ const DECODER = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
6
+ function scalarWidth(first) {
7
+ return first < 0x80 ? 1 : first < 0xe0 ? 2 : first < 0xf0 ? 3 : 4;
8
+ }
9
+ function indexStorage(storage, byte, scalar) {
10
+ const offsets = storage.offsets;
11
+ while (byte < storage.byteLength) {
12
+ if (scalar % INDEX_STRIDE === 0)
13
+ offsets[scalar / INDEX_STRIDE] = byte;
14
+ byte += scalarWidth(storage.bytes[byte]);
15
+ scalar++;
16
+ }
17
+ }
18
+ function newStorage(bytes, capacity = bytes.length) {
19
+ const buffer = new Uint8Array(capacity);
20
+ buffer.set(bytes);
21
+ let scalars = 0;
22
+ for (let at = 0; at < bytes.length; at += scalarWidth(bytes[at]))
23
+ scalars++;
24
+ const storage = {
25
+ bytes: buffer,
26
+ byteLength: bytes.length,
27
+ scalarLength: scalars,
28
+ offsets: scalars === bytes.length ? null : [],
29
+ owners: 1,
30
+ };
31
+ if (storage.offsets)
32
+ indexStorage(storage, 0, 0);
33
+ return storage;
34
+ }
35
+ export class IndexedString {
36
+ kind = "string";
37
+ storage;
38
+ constructor(storage) {
39
+ this.storage = storage;
40
+ }
41
+ static fromText(text) {
42
+ return new IndexedString(newStorage(ENCODER.encode(text)));
43
+ }
44
+ live() {
45
+ if (!this.storage)
46
+ throw new Error("cannot read a released string");
47
+ return this.storage;
48
+ }
49
+ get value() {
50
+ return DECODER.decode(this.encoded());
51
+ }
52
+ get scalarLength() {
53
+ return this.live().scalarLength;
54
+ }
55
+ get byteLength() {
56
+ return this.live().byteLength;
57
+ }
58
+ encoded() {
59
+ const storage = this.live();
60
+ return storage.bytes.subarray(0, storage.byteLength);
61
+ }
62
+ clone() {
63
+ const storage = this.live();
64
+ storage.owners++;
65
+ return new IndexedString(storage);
66
+ }
67
+ release() {
68
+ if (this.storage) {
69
+ this.storage.owners--;
70
+ this.storage = null;
71
+ }
72
+ }
73
+ scalarOffset(index) {
74
+ const storage = this.live();
75
+ if (!Number.isSafeInteger(index) ||
76
+ index < 0 ||
77
+ index > storage.scalarLength)
78
+ return undefined;
79
+ if (index === storage.scalarLength)
80
+ return storage.byteLength;
81
+ if (!storage.offsets)
82
+ return index;
83
+ let byte = storage.offsets[Math.floor(index / INDEX_STRIDE)];
84
+ for (let tail = index % INDEX_STRIDE; tail > 0; tail--)
85
+ byte += scalarWidth(storage.bytes[byte]);
86
+ return byte;
87
+ }
88
+ at(index) {
89
+ if (index === this.scalarLength)
90
+ return undefined;
91
+ const byte = this.scalarOffset(index);
92
+ if (byte === undefined)
93
+ return undefined;
94
+ const storage = this.live();
95
+ return DECODER.decode(storage.bytes.subarray(byte, byte + scalarWidth(storage.bytes[byte])));
96
+ }
97
+ slice(start, end) {
98
+ const from = this.scalarOffset(start);
99
+ const to = this.scalarOffset(end);
100
+ if (from === undefined || to === undefined || from > to)
101
+ return undefined;
102
+ return new IndexedString(newStorage(this.live().bytes.subarray(from, to)));
103
+ }
104
+ scalarIndex(byte) {
105
+ const storage = this.live();
106
+ if (!storage.offsets)
107
+ return byte;
108
+ if (byte === storage.byteLength)
109
+ return storage.scalarLength;
110
+ let low = 0;
111
+ let high = storage.offsets.length;
112
+ while (low + 1 < high) {
113
+ const mid = Math.floor((low + high) / 2);
114
+ if (storage.offsets[mid] <= byte)
115
+ low = mid;
116
+ else
117
+ high = mid;
118
+ }
119
+ let scalar = low * INDEX_STRIDE;
120
+ for (let at = storage.offsets[low]; at < byte; at += scalarWidth(storage.bytes[at]))
121
+ scalar++;
122
+ return scalar;
123
+ }
124
+ find(needle, start = 0) {
125
+ const from = this.scalarOffset(start);
126
+ if (from === undefined)
127
+ return undefined;
128
+ const bytes = this.encoded();
129
+ const pattern = needle.encoded();
130
+ if (pattern.length === 0)
131
+ return start;
132
+ // KMP searches only the suffix requested by the caller. Decoding the
133
+ // complete value for String.indexOf would rescan its prefix on every call.
134
+ const prefixes = new Array(pattern.length).fill(0);
135
+ for (let i = 1, matched = 0; i < pattern.length; i++) {
136
+ while (matched && pattern[i] !== pattern[matched])
137
+ matched = prefixes[matched - 1];
138
+ if (pattern[i] === pattern[matched])
139
+ matched++;
140
+ prefixes[i] = matched;
141
+ }
142
+ for (let i = from, matched = 0; i < bytes.length; i++) {
143
+ while (matched && bytes[i] !== pattern[matched])
144
+ matched = prefixes[matched - 1];
145
+ if (bytes[i] === pattern[matched])
146
+ matched++;
147
+ if (matched === pattern.length)
148
+ return this.scalarIndex(i + 1 - matched);
149
+ }
150
+ return undefined;
151
+ }
152
+ /** Consume this receiver, retaining the suffix's borrowed snapshot. */
153
+ append(suffix) {
154
+ const bytes = suffix.encoded();
155
+ const added = suffix.scalarLength;
156
+ if (!bytes.length)
157
+ return this;
158
+ let storage = this.live();
159
+ const oldLength = storage.byteLength;
160
+ const oldScalars = storage.scalarLength;
161
+ const newLength = oldLength + bytes.length;
162
+ const capacity = newLength > storage.bytes.length
163
+ ? Math.max(newLength, storage.bytes.length * 2, 16)
164
+ : storage.bytes.length;
165
+ if (storage.owners !== 1) {
166
+ storage.owners--;
167
+ storage = newStorage(this.encoded(), capacity);
168
+ this.storage = storage;
169
+ }
170
+ else if (capacity !== storage.bytes.length) {
171
+ const buffer = new Uint8Array(capacity);
172
+ buffer.set(storage.bytes.subarray(0, oldLength));
173
+ storage.bytes = buffer;
174
+ }
175
+ storage.bytes.set(bytes, oldLength);
176
+ storage.byteLength = newLength;
177
+ storage.scalarLength += added;
178
+ if (storage.scalarLength !== newLength) {
179
+ if (storage.offsets)
180
+ indexStorage(storage, oldLength, oldScalars);
181
+ else {
182
+ storage.offsets = [];
183
+ indexStorage(storage, 0, 0);
184
+ }
185
+ }
186
+ return this;
187
+ }
188
+ }
189
+ export function stringValue(value) {
190
+ return IndexedString.fromText(value);
191
+ }
@@ -2,7 +2,7 @@ export type JsonValue = null | boolean | number | string | JsonValue[] | {
2
2
  [key: string]: JsonValue;
3
3
  };
4
4
  export type SandboxBytecodePackage = import("./v1/package.js").PackageV1;
5
- export type TrapKind = "integer_overflow" | "shift_out_of_range" | "divide_by_zero" | "invalid_local" | "invalid_block" | "invalid_call" | "invalid_enum_tag" | "invalid_record_field" | "vector_bounds" | "string_bounds" | "regex_compile" | "capability_missing" | "budget_exhausted" | "panic" | "unsupported_instruction" | "internal_error";
5
+ export type TrapKind = "actor_refused" | "integer_overflow" | "shift_out_of_range" | "divide_by_zero" | "invalid_local" | "invalid_block" | "invalid_call" | "invalid_enum_tag" | "invalid_record_field" | "vector_bounds" | "string_bounds" | "regex_compile" | "capability_missing" | "budget_exhausted" | "panic" | "unsupported_instruction" | "internal_error";
6
6
  export type RuntimeStatus = "ok" | "compile_error" | "sandbox_rejected" | "runtime_failure" | "budget_exhausted" | "panic" | "trap";
7
7
  export interface RuntimeFailure {
8
8
  kind: "panic" | "trap" | "budget_exhausted" | "unsupported" | "internal_error";
@@ -117,6 +117,8 @@ export interface SandboxTrace {
117
117
  };
118
118
  }
119
119
  export interface RunOptions {
120
+ /** Supplied process arguments including argv[0]. Defaults to ["main.hew"]. */
121
+ argv?: readonly string[];
120
122
  codecBridge?: (request: string) => string;
121
123
  propertyInput?: number[];
122
124
  fixtureId?: string;
@@ -0,0 +1,4 @@
1
+ export declare const CATEGORY_RANGES: readonly number[];
2
+ export declare const UPPER_MAPPING: readonly number[];
3
+ export declare const LOWER_MAPPING: readonly number[];
4
+ export declare const TITLE_MAPPING: readonly number[];