@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
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
|
|
@@ -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
|
+
}
|
|
@@ -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;
|