@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.
- package/README.md +48 -4
- package/cli/test-runner.mjs +4 -1
- package/dist/interpreter/interpreter.d.ts +1 -1
- package/dist/interpreter/interpreter.js +1 -1
- package/dist/interpreter/parity-runner.js +1 -1
- package/dist/interpreter/run-program.d.ts +3 -2
- package/dist/interpreter/run-program.js +5 -3
- package/dist/interpreter/schema-validator.d.ts +9 -4
- package/dist/interpreter/schema-validator.js +15 -49
- package/dist/interpreter/strings.d.ts +21 -0
- package/dist/interpreter/strings.js +191 -0
- package/dist/interpreter/types.d.ts +6 -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/browser-directory.d.ts +107 -0
- package/dist/interpreter/v1/browser-directory.js +288 -0
- package/dist/interpreter/v1/calendar.d.ts +4 -0
- package/dist/interpreter/v1/calendar.js +64 -0
- package/dist/interpreter/v1/codecs.js +8 -11
- package/dist/interpreter/v1/exec.d.ts +2 -2
- package/dist/interpreter/v1/exec.js +1761 -489
- package/dist/interpreter/v1/instructions.generated.d.ts +1135 -0
- package/dist/interpreter/v1/instructions.generated.js +106 -0
- package/dist/interpreter/v1/package-graph.d.ts +2 -0
- package/dist/interpreter/v1/package-graph.js +729 -0
- package/dist/interpreter/v1/package-validator.generated.d.ts +3 -0
- package/dist/interpreter/v1/package-validator.generated.js +75902 -0
- package/dist/interpreter/v1/package.d.ts +5 -645
- package/dist/interpreter/v1/package.js +0 -12
- package/dist/interpreter/v1/pipes.d.ts +1 -0
- package/dist/interpreter/v1/pipes.js +9 -6
- package/dist/interpreter/v1/scheduler.d.ts +8 -1
- package/dist/interpreter/v1/scheduler.js +50 -11
- package/dist/interpreter/v1/shims.d.ts +260 -7
- package/dist/interpreter/v1/shims.js +647 -175
- package/dist/interpreter/v1/structural.d.ts +2 -28
- package/dist/interpreter/v1/timestamp.d.ts +7 -0
- package/dist/interpreter/v1/timestamp.js +54 -0
- package/dist/interpreter/v1/validate.d.ts +8 -2
- package/dist/interpreter/v1/validate.js +284 -200
- package/dist/interpreter/v1/value-operations.d.ts +11 -3
- package/dist/interpreter/v1/value-operations.js +70 -18
- package/dist/interpreter/values.d.ts +30 -8
- package/dist/interpreter/values.js +31 -5
- package/dist/interpreter/worker.js +4 -3
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
borrows, cleanup, checked failures, actor protocols and suspension edges.
|
|
6
6
|
|
|
7
7
|
Actors, tasks and pipes use resumable frames on a deterministic scheduler.
|
|
8
|
-
Timers advance virtual time.
|
|
9
|
-
|
|
8
|
+
Timers advance virtual time. Hosts can supply real origin-private filesystem
|
|
9
|
+
(OPFS) directory handles; network and ambient filesystem operations are refused
|
|
10
|
+
before execution. Other missing operations are reported separately as
|
|
10
11
|
`not_implemented`. The loader is the capability admission authority.
|
|
11
12
|
|
|
12
13
|
## Browser API
|
|
@@ -17,16 +18,43 @@ import { runBytecode } from "@hew-lang/sandbox-vm";
|
|
|
17
18
|
|
|
18
19
|
const { diagnostics, bytecode } = JSON.parse(compileToSandboxBytecode(source));
|
|
19
20
|
if (bytecode) {
|
|
20
|
-
const trace = runBytecode(bytecode, {
|
|
21
|
+
const trace = await runBytecode(bytecode, {
|
|
22
|
+
replay: { step_budget: 100000 },
|
|
23
|
+
});
|
|
21
24
|
}
|
|
22
25
|
```
|
|
23
26
|
|
|
24
27
|
Initialize the Wasm compiler using its generated default export before calling
|
|
25
28
|
it. `runProgram(source, stdin)` is also available for existing page bridges;
|
|
26
|
-
install `globalThis.compileToSandboxBytecode` first.
|
|
29
|
+
install `globalThis.compileToSandboxBytecode` first. Both entrypoints return a Promise. The page result includes
|
|
27
30
|
`stdout`, `exit_code`, `diagnostics`, `status`, `sandbox_rejections`,
|
|
28
31
|
`compiler_version` and `hew_version`.
|
|
29
32
|
|
|
33
|
+
To grant filesystem authority, pass only the directories the program needs:
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
const trace = await runBytecode(bytecode, {
|
|
37
|
+
directoryRoots: { read: inputDirectory, write: outputDirectory },
|
|
38
|
+
signal: controller.signal,
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Both roots are optional and default to no authority. The handles must belong to
|
|
43
|
+
this origin's OPFS; external picker-backed handles are refused because their
|
|
44
|
+
symlink confinement is not standardized. Existing Hew `fs.open_read_dir(".")`
|
|
45
|
+
and `fs.open_write_dir(".")` acquire the supplied roots. Relative traversal stays
|
|
46
|
+
beneath the current directory owner, including after narrowing with `subdir`.
|
|
47
|
+
Children remain usable after closing a parent. Each `file.write` publishes its
|
|
48
|
+
bytes before it returns, even while the file owner remains open.
|
|
49
|
+
|
|
50
|
+
`AbortSignal` requests cancellation through the source's authored cleanup edges.
|
|
51
|
+
Filesystem acquisition publishes its owner before observing cancellation, as on
|
|
52
|
+
native hosts. An in-progress file operation drains or aborts its browser writer
|
|
53
|
+
before cancellation releases its inputs. Pure execution retains the deterministic
|
|
54
|
+
virtual scheduler and runs until a host wait or completion. Pass the same options
|
|
55
|
+
as the fourth argument to `runProgram(source, stdin, files, options)`; signals are
|
|
56
|
+
for direct calls rather than worker message payloads.
|
|
57
|
+
|
|
30
58
|
A `sandbox_rejected` result includes rejection entries with `category`, `code`,
|
|
31
59
|
`capability`, `message` and `span`. Categories are `native_only`,
|
|
32
60
|
`not_implemented` and `invalid_package`. Compiler errors, language panics,
|
|
@@ -48,8 +76,24 @@ The [package contract](bytecode/package-v1.md) describes the SIR projection.
|
|
|
48
76
|
Source fixtures are compiled by the public compiler and executed to record
|
|
49
77
|
deterministic traces. Tests also compare executable sources with native Hew.
|
|
50
78
|
|
|
79
|
+
With `hew-wasm/pkg` and `hew-sandbox-vm/dist` built, serve the repository on
|
|
80
|
+
localhost and open `hew-sandbox-vm/test/browser/directory-handles.html` to run the
|
|
81
|
+
real browser filesystem controls. They compile the ordinary Hew source fixture,
|
|
82
|
+
use a temporary OPFS directory, verify disk contents, confinement and cancellation,
|
|
83
|
+
then remove their entries. The page accepts a `compiler` module URL query parameter
|
|
84
|
+
to reuse another built compiler package.
|
|
85
|
+
|
|
86
|
+
`RunOptions.argv` supplies process arguments, including the executable name at
|
|
87
|
+
index zero. It defaults to `["main.hew"]` and is copied for each run. For example,
|
|
88
|
+
`runBytecode(bytecode, { argv: ["greet", "--", "Zoë", ""] })` supplies those exact
|
|
89
|
+
strings to `std.os.args()`. The VM never reads Node or browser process arguments.
|
|
90
|
+
|
|
51
91
|
Typed serialization executes the shared Rust codecs in `@hew-lang/wasm`.
|
|
52
92
|
Pass its initialized `executeCodec` export as `runBytecode`'s `codecBridge`
|
|
53
93
|
option. The Node test host loads that package automatically; in a source
|
|
54
94
|
checkout, run `make wasm` to build `hew-wasm/pkg` first. An explicit
|
|
55
95
|
`HEW_WASM_MODULE` module URL can select another build of the same package.
|
|
96
|
+
|
|
97
|
+
UTC calendar operations also require the initialized `executeCalendar`
|
|
98
|
+
export as `calendarBridge`. Both hosts use the shared Chrono calendar engine;
|
|
99
|
+
the bridge preserves integer seconds and nanoseconds exactly.
|
package/cli/test-runner.mjs
CHANGED
|
@@ -22,6 +22,7 @@ try {
|
|
|
22
22
|
const args = parseArgs(process.argv.slice(2));
|
|
23
23
|
const packageValue = JSON.parse(fs.readFileSync(args.path, "utf8"));
|
|
24
24
|
let codecBridge;
|
|
25
|
+
let calendarBridge;
|
|
25
26
|
if (packageValue.codecs?.length) {
|
|
26
27
|
const local = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../hew-wasm/pkg/hew_wasm.js");
|
|
27
28
|
const location = process.env.HEW_WASM_MODULE ?? (fs.existsSync(local) ? pathToFileURL(local).href : "@hew-lang/wasm");
|
|
@@ -31,11 +32,13 @@ try {
|
|
|
31
32
|
await wasm.default({ module_or_path: fs.readFileSync(new URL(resolved.replace(/\.js$/, "_bg.wasm"))) });
|
|
32
33
|
}
|
|
33
34
|
codecBridge = wasm.executeCodec ?? wasm.default?.executeCodec;
|
|
35
|
+
calendarBridge = wasm.executeCalendar ?? wasm.default?.executeCalendar;
|
|
34
36
|
if (typeof codecBridge !== "function") throw new Error("hew-wasm executeCodec export is missing; rebuild the Wasm package");
|
|
35
37
|
}
|
|
36
38
|
const propertyPath = reportPath ? path.join(path.dirname(reportPath), "property.cbor") : null;
|
|
37
|
-
const trace = runBytecode(packageValue, {
|
|
39
|
+
const trace = await runBytecode(packageValue, {
|
|
38
40
|
codecBridge,
|
|
41
|
+
calendarBridge,
|
|
39
42
|
propertyInput: propertyPath && fs.existsSync(propertyPath) ? [...fs.readFileSync(propertyPath)] : [],
|
|
40
43
|
fixtureId: "hew-test",
|
|
41
44
|
traceId: "trace:hew-test",
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { RunOptions, SandboxTrace } from "./types.js";
|
|
2
|
-
export declare function runBytecode(input: unknown, options?: RunOptions): SandboxTrace
|
|
2
|
+
export declare function runBytecode(input: unknown, options?: RunOptions): Promise<SandboxTrace>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { validateBytecodePackage } from "./schema-validator.js";
|
|
2
2
|
import { runPackageV1 } from "./v1/exec.js";
|
|
3
3
|
import { TraceBuilder } from "./trace.js";
|
|
4
|
-
export function runBytecode(input, options = {}) {
|
|
4
|
+
export async function runBytecode(input, options = {}) {
|
|
5
5
|
let bytecode;
|
|
6
6
|
try {
|
|
7
7
|
bytecode = validateBytecodePackage(input);
|
|
@@ -45,7 +45,7 @@ function parseArgs(argv) {
|
|
|
45
45
|
try {
|
|
46
46
|
const args = parseArgs(process.argv.slice(2));
|
|
47
47
|
const bytecode = JSON.parse(fs.readFileSync(args.bytecodePath, "utf8"));
|
|
48
|
-
const trace = runBytecode(bytecode, {
|
|
48
|
+
const trace = await runBytecode(bytecode, {
|
|
49
49
|
replay: {
|
|
50
50
|
seed: args.seed,
|
|
51
51
|
step_budget: DEFAULT_STEP_BUDGET,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PackageV1 } from "./v1/package.js";
|
|
2
|
-
import type { JsonValue, RuntimeStatus, SandboxRejection } from "./types.js";
|
|
2
|
+
import type { RunOptions, JsonValue, RuntimeStatus, SandboxRejection } from "./types.js";
|
|
3
3
|
export interface Diagnostic {
|
|
4
4
|
severity: string;
|
|
5
5
|
phase: string;
|
|
@@ -24,7 +24,8 @@ type SandboxCompiler = (source: string, profile?: string, files?: Map<string, st
|
|
|
24
24
|
declare global {
|
|
25
25
|
var __hewSandboxCompileToSandboxBytecode: SandboxCompiler | undefined;
|
|
26
26
|
var __hewSandboxExecuteCodec: ((request: string) => string) | undefined;
|
|
27
|
+
var __hewSandboxExecuteCalendar: ((request: string) => string) | undefined;
|
|
27
28
|
var compileToSandboxBytecode: SandboxCompiler | undefined;
|
|
28
29
|
}
|
|
29
|
-
export declare function runProgram(source: string, stdin: string, files?: Map<string, string | Uint8Array
|
|
30
|
+
export declare function runProgram(source: string, stdin: string, files?: Map<string, string | Uint8Array>, options?: RunOptions): Promise<RunProgramResult>;
|
|
30
31
|
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { runBytecode } from "./interpreter.js";
|
|
2
2
|
const SANDBOX_PROFILE = "sandbox-vm-export";
|
|
3
|
-
export function runProgram(source, stdin, files) {
|
|
3
|
+
export async function runProgram(source, stdin, files, options = {}) {
|
|
4
4
|
const compileOutput = compileSource(source, files);
|
|
5
5
|
if (hasErrorDiagnostics(compileOutput.diagnostics) ||
|
|
6
6
|
compileOutput.bytecode === null) {
|
|
@@ -15,9 +15,11 @@ export function runProgram(source, stdin, files) {
|
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
17
|
const bytecode = compileOutput.bytecode;
|
|
18
|
-
const trace = runBytecode(bytecode, {
|
|
18
|
+
const trace = await runBytecode(bytecode, {
|
|
19
|
+
...options,
|
|
19
20
|
fixtureId: "page-run",
|
|
20
|
-
codecBridge: globalThis.__hewSandboxExecuteCodec,
|
|
21
|
+
codecBridge: options.codecBridge ?? globalThis.__hewSandboxExecuteCodec,
|
|
22
|
+
calendarBridge: options.calendarBridge ?? globalThis.__hewSandboxExecuteCalendar,
|
|
21
23
|
traceId: "trace:page-run",
|
|
22
24
|
replay: { inputs: [{ kind: "stdin", data: stdin }] },
|
|
23
25
|
});
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { PackageV1 } from "./v1/package.js";
|
|
2
|
+
declare const checkedPackage: unique symbol;
|
|
3
|
+
export type CheckedPackage = PackageV1 & {
|
|
4
|
+
readonly [checkedPackage]: true;
|
|
5
|
+
};
|
|
2
6
|
export declare class BytecodeValidationError extends Error {
|
|
3
7
|
readonly errors: string[];
|
|
4
8
|
constructor(errors: string[]);
|
|
5
9
|
}
|
|
6
|
-
/**
|
|
7
|
-
*
|
|
8
|
-
export declare function validateBytecodePackage(input: unknown):
|
|
10
|
+
/** Decode an isolated package before capability admission or any host effect.
|
|
11
|
+
* Cloning prevents a caller or host callback from changing the checked input. */
|
|
12
|
+
export declare function validateBytecodePackage(input: unknown): CheckedPackage;
|
|
13
|
+
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { validPackage } from "./v1/package-validator.generated.js";
|
|
2
|
+
import { validatePackageGraph } from "./v1/package-graph.js";
|
|
2
3
|
export class BytecodeValidationError extends Error {
|
|
3
4
|
errors;
|
|
4
5
|
constructor(errors) {
|
|
@@ -7,55 +8,20 @@ export class BytecodeValidationError extends Error {
|
|
|
7
8
|
this.name = "BytecodeValidationError";
|
|
8
9
|
}
|
|
9
10
|
}
|
|
10
|
-
/**
|
|
11
|
-
*
|
|
11
|
+
/** Decode an isolated package before capability admission or any host effect.
|
|
12
|
+
* Cloning prevents a caller or host callback from changing the checked input. */
|
|
12
13
|
export function validateBytecodePackage(input) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
errors.push(`${key} must be a string`);
|
|
19
|
-
}
|
|
20
|
-
for (const key of [
|
|
21
|
-
"functions",
|
|
22
|
-
"strings",
|
|
23
|
-
"bytes",
|
|
24
|
-
"regex_patterns",
|
|
25
|
-
"aggregates",
|
|
26
|
-
"variants",
|
|
27
|
-
"runtime_families",
|
|
28
|
-
"externs",
|
|
29
|
-
"suspend_kinds",
|
|
30
|
-
"value_capabilities",
|
|
31
|
-
"closures",
|
|
32
|
-
"vtables",
|
|
33
|
-
]) {
|
|
34
|
-
if (!Array.isArray(input[key]))
|
|
35
|
-
errors.push(`${key} must be an array`);
|
|
36
|
-
}
|
|
37
|
-
if (errors.length)
|
|
38
|
-
throw new BytecodeValidationError(errors);
|
|
39
|
-
for (const fn of input.functions) {
|
|
40
|
-
if (!fn ||
|
|
41
|
-
!Number.isInteger(fn.id) ||
|
|
42
|
-
!Array.isArray(fn.params) ||
|
|
43
|
-
!Array.isArray(fn.places) ||
|
|
44
|
-
!Array.isArray(fn.blocks)) {
|
|
45
|
-
errors.push("malformed function");
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
for (const block of fn.blocks) {
|
|
49
|
-
if (!block ||
|
|
50
|
-
!Number.isInteger(block.id) ||
|
|
51
|
-
!Array.isArray(block.params) ||
|
|
52
|
-
!Array.isArray(block.ops) ||
|
|
53
|
-
!block.term ||
|
|
54
|
-
typeof block.term.op !== "string")
|
|
55
|
-
errors.push("malformed block");
|
|
14
|
+
try {
|
|
15
|
+
const decoded = structuredClone(input);
|
|
16
|
+
if (!validPackage(decoded)) {
|
|
17
|
+
const reasons = (validPackage.errors ?? []).map((issue) => `${issue.instancePath || "/"} ${issue.message ?? "invalid value"}`);
|
|
18
|
+
throw new Error(reasons.join("; ") || "malformed sandbox bytecode package");
|
|
56
19
|
}
|
|
20
|
+
const pkg = decoded;
|
|
21
|
+
validatePackageGraph(pkg);
|
|
22
|
+
return pkg;
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
throw new BytecodeValidationError([String(error)]);
|
|
57
26
|
}
|
|
58
|
-
if (errors.length)
|
|
59
|
-
throw new BytecodeValidationError(errors);
|
|
60
|
-
return input;
|
|
61
27
|
}
|
|
@@ -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,7 +117,13 @@ export interface SandboxTrace {
|
|
|
117
117
|
};
|
|
118
118
|
}
|
|
119
119
|
export interface RunOptions {
|
|
120
|
+
/** Explicit browser filesystem authority; absent roots grant no access. */
|
|
121
|
+
directoryRoots?: import("./v1/browser-directory.js").DirectoryRoots;
|
|
122
|
+
signal?: AbortSignal;
|
|
123
|
+
/** Supplied process arguments including argv[0]. Defaults to ["main.hew"]. */
|
|
124
|
+
argv?: readonly string[];
|
|
120
125
|
codecBridge?: (request: string) => string;
|
|
126
|
+
calendarBridge?: (request: string) => string;
|
|
121
127
|
propertyInput?: number[];
|
|
122
128
|
fixtureId?: string;
|
|
123
129
|
traceId?: string;
|