@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 +5 -0
- package/dist/interpreter/run-program.js +1 -0
- package/dist/interpreter/strings.d.ts +21 -0
- package/dist/interpreter/strings.js +191 -0
- package/dist/interpreter/types.d.ts +3 -1
- 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 +1489 -332
- package/dist/interpreter/v1/package.d.ts +46 -6
- 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 +243 -42
- 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 +58 -23
- package/dist/interpreter/values.d.ts +32 -9
- package/dist/interpreter/values.js +42 -8
- package/package.json +4 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type Own = "none" | "owned" | "guaranteed";
|
|
2
2
|
export type BoundaryDecision = "borrow" | "borrow_mut" | "copy" | "move" | "snapshot";
|
|
3
|
-
export type TrapName = "integer_overflow" | "divide_by_zero" | "shift_out_of_range" | "vector_bounds";
|
|
3
|
+
export type TrapName = "integer_overflow" | "divide_by_zero" | "shift_out_of_range" | "vector_bounds" | "actor_refused";
|
|
4
4
|
export interface SpanV1 {
|
|
5
5
|
start: number;
|
|
6
6
|
end: number;
|
|
@@ -34,7 +34,7 @@ export interface VariantShape {
|
|
|
34
34
|
cases: VariantCase[];
|
|
35
35
|
runtime_tags?: Partial<Record<RuntimeVariantRole, number>>;
|
|
36
36
|
}
|
|
37
|
-
export type RuntimeVariantRole = "OptionSome" | "OptionNone" | "ResultOk" | "ResultErr" | "ActorErrorRejected" | "ActorErrorFailed" | "ActorErrorTrapped" | "ActorErrorDead" | "ActorErrorTimedOut" | "ActorErrorNodeNotRunning" | "ActorErrorRoutingFailed" | "ActorErrorEncodeFailed" | "ActorErrorConnectionDropped" | "ActorErrorPartition" | "SendErrorFull" | "SendErrorClosed" | "SendErrorPartition" | "SendErrorStaleRef" | "SendErrorLocalShutdown" | "SendErrorCancelled" | "SendErrorVersionMismatch" | "SendErrorBackpressure" | "SendErrorDead" | "SendErrorWriteTimedOut" | "DeliveryAccepted" | "DeliveryDiscarded" | "LinkErrorNoContext";
|
|
37
|
+
export type RuntimeVariantRole = "CrashKindFault" | "CrashKindHeapExceeded" | "CrashKindPartitionDetected" | "CrashKindHandlerFailed" | "CrashKindTimerOverdue" | "OptionSome" | "OptionNone" | "ResultOk" | "ResultErr" | "ActorErrorRejected" | "ActorErrorFailed" | "ActorErrorTrapped" | "ActorErrorRefused" | "CallerActor" | "CallerTask" | "CallerTimer" | "CallerNode" | "ActorErrorDead" | "ActorErrorTimedOut" | "ActorErrorNodeNotRunning" | "ActorErrorRoutingFailed" | "ActorErrorEncodeFailed" | "ActorErrorConnectionDropped" | "ActorErrorPartition" | "SendErrorFull" | "SendErrorClosed" | "SendErrorPartition" | "SendErrorStaleRef" | "SendErrorLocalShutdown" | "SendErrorCancelled" | "SendErrorVersionMismatch" | "SendErrorBackpressure" | "SendErrorDead" | "SendErrorUnauthorized" | "SendErrorInvalidMessage" | "SendErrorTimedOut" | "SendErrorWriteTimedOut" | "DeliveryAccepted" | "DeliveryDiscarded" | "LinkErrorNoContext";
|
|
38
38
|
export declare function runtimeTag(shape: VariantShape, role: RuntimeVariantRole): number;
|
|
39
39
|
export interface RuntimeFamilyEntry {
|
|
40
40
|
id: number;
|
|
@@ -93,7 +93,10 @@ export interface ValueComponentCapabilities {
|
|
|
93
93
|
}
|
|
94
94
|
export interface ClosureShape {
|
|
95
95
|
id: number;
|
|
96
|
-
|
|
96
|
+
entries: Array<{
|
|
97
|
+
body: number;
|
|
98
|
+
specialization: number | null;
|
|
99
|
+
}>;
|
|
97
100
|
fields: number;
|
|
98
101
|
}
|
|
99
102
|
export interface VtableShape {
|
|
@@ -107,6 +110,7 @@ export interface VtableShape {
|
|
|
107
110
|
}>;
|
|
108
111
|
}
|
|
109
112
|
export interface FunctionV1 {
|
|
113
|
+
actor_construction?: boolean;
|
|
110
114
|
id: number;
|
|
111
115
|
name: string;
|
|
112
116
|
params: ValueDef[];
|
|
@@ -326,6 +330,7 @@ export type OpV1 = (OpBase & {
|
|
|
326
330
|
op: "callable.coerce";
|
|
327
331
|
dst: number;
|
|
328
332
|
source: Operand;
|
|
333
|
+
specialization: number | null;
|
|
329
334
|
}) | (OpBase & {
|
|
330
335
|
op: "dyn.make";
|
|
331
336
|
dst: number;
|
|
@@ -365,6 +370,25 @@ export type OpV1 = (OpBase & {
|
|
|
365
370
|
dst: number;
|
|
366
371
|
scope: number;
|
|
367
372
|
callable: Operand;
|
|
373
|
+
}) | (OpBase & {
|
|
374
|
+
op: "task.invoke";
|
|
375
|
+
dst: number;
|
|
376
|
+
scope: number;
|
|
377
|
+
callee: {
|
|
378
|
+
direct: number;
|
|
379
|
+
} | {
|
|
380
|
+
mutable: number;
|
|
381
|
+
receiver: BoundaryOperand;
|
|
382
|
+
staged: boolean;
|
|
383
|
+
} | {
|
|
384
|
+
dyn: number;
|
|
385
|
+
method: string;
|
|
386
|
+
receiver: BoundaryOperand;
|
|
387
|
+
} | {
|
|
388
|
+
receiver: BoundaryOperand;
|
|
389
|
+
specialization: number | null;
|
|
390
|
+
};
|
|
391
|
+
args: BoundaryOperand[];
|
|
368
392
|
});
|
|
369
393
|
export type OpName = OpV1["op"];
|
|
370
394
|
interface TermBase {
|
|
@@ -426,6 +450,7 @@ export type TermV1 = (TermBase & {
|
|
|
426
450
|
}) | (TermBase & CallShape & {
|
|
427
451
|
op: "indirect.call";
|
|
428
452
|
callee: BoundaryOperand;
|
|
453
|
+
specialization: number | null;
|
|
429
454
|
}) | (TermBase & CallShape & {
|
|
430
455
|
op: "dyn.call";
|
|
431
456
|
receiver: BoundaryOperand;
|
|
@@ -447,12 +472,17 @@ export type TermV1 = (TermBase & {
|
|
|
447
472
|
callbacks: Record<string, number[]>;
|
|
448
473
|
}) | (TermBase & CallShape & {
|
|
449
474
|
op: "runtime.call";
|
|
475
|
+
shared_payload_type?: string;
|
|
450
476
|
family: number;
|
|
451
477
|
structural?: number | null;
|
|
452
478
|
callbacks?: number[];
|
|
453
479
|
releases_contents?: boolean;
|
|
454
480
|
keeps_inputs_on_failure?: boolean;
|
|
455
481
|
result_member_shapes?: Array<number | null>;
|
|
482
|
+
variant_result_shapes?: {
|
|
483
|
+
error: number;
|
|
484
|
+
error_len: number;
|
|
485
|
+
};
|
|
456
486
|
result_shape: number | null;
|
|
457
487
|
}) | (TermBase & CallShape & {
|
|
458
488
|
op: "extern.call";
|
|
@@ -515,7 +545,7 @@ export type TermV1 = (TermBase & {
|
|
|
515
545
|
inputs: BoundaryOperand[];
|
|
516
546
|
result: CallResult;
|
|
517
547
|
resumes: Edge[];
|
|
518
|
-
cancel: Edge;
|
|
548
|
+
cancel: Edge | null;
|
|
519
549
|
unwind: Edge | null;
|
|
520
550
|
});
|
|
521
551
|
export type TermName = TermV1["op"];
|
|
@@ -530,6 +560,8 @@ export interface ActorProtocol {
|
|
|
530
560
|
sealed: boolean;
|
|
531
561
|
}
|
|
532
562
|
export type ActorOperation = {
|
|
563
|
+
op: "identity";
|
|
564
|
+
} | {
|
|
533
565
|
op: "spawn" | "self_handle" | "stop" | "terminate" | "await_stopped" | "await_restarted";
|
|
534
566
|
actor: number;
|
|
535
567
|
} | {
|
|
@@ -543,6 +575,12 @@ export type ActorOperation = {
|
|
|
543
575
|
op: "stream_start";
|
|
544
576
|
actor: number;
|
|
545
577
|
message: number;
|
|
578
|
+
} | {
|
|
579
|
+
op: "timer_schedule";
|
|
580
|
+
actor: number;
|
|
581
|
+
message: number;
|
|
582
|
+
resource: number;
|
|
583
|
+
policy: "reject" | "drop_newest";
|
|
546
584
|
} | {
|
|
547
585
|
op: "local_observation";
|
|
548
586
|
kind: string;
|
|
@@ -553,10 +591,10 @@ export type ActorOperation = {
|
|
|
553
591
|
op: "supervisor_child" | "supervisor_pool_view";
|
|
554
592
|
supervisor: number;
|
|
555
593
|
child: number;
|
|
556
|
-
owner_is_role: boolean;
|
|
557
594
|
};
|
|
558
595
|
export interface ActorShape {
|
|
559
596
|
id: number;
|
|
597
|
+
name: string;
|
|
560
598
|
state_fields: Array<{
|
|
561
599
|
mutable: boolean;
|
|
562
600
|
deferred: boolean;
|
|
@@ -566,6 +604,7 @@ export interface ActorShape {
|
|
|
566
604
|
stop: number[];
|
|
567
605
|
crash?: number;
|
|
568
606
|
crash_info?: number;
|
|
607
|
+
crash_kind?: number;
|
|
569
608
|
crash_action?: number;
|
|
570
609
|
exit?: number;
|
|
571
610
|
down?: number;
|
|
@@ -604,7 +643,8 @@ export interface SupervisorShape {
|
|
|
604
643
|
};
|
|
605
644
|
restart: string;
|
|
606
645
|
stop_ns?: number | null;
|
|
607
|
-
|
|
646
|
+
stop_config?: number;
|
|
647
|
+
is_pool?: boolean;
|
|
608
648
|
spawn: number;
|
|
609
649
|
}>;
|
|
610
650
|
}
|
|
@@ -12,6 +12,7 @@ export declare class Pipes {
|
|
|
12
12
|
private half;
|
|
13
13
|
private get;
|
|
14
14
|
cloneSink(value: VmValue): VmValue;
|
|
15
|
+
isClosed(value: VmValue): boolean;
|
|
15
16
|
close(value: VmValue, fault?: string | null): VmValue[];
|
|
16
17
|
peerClosed(value: VmValue): boolean;
|
|
17
18
|
readable(value: VmValue): boolean;
|
|
@@ -29,10 +29,10 @@ export class Pipes {
|
|
|
29
29
|
throw new Error("pipe pair does not exist");
|
|
30
30
|
return this.half(pipe, kind);
|
|
31
31
|
}
|
|
32
|
-
half(pipe, kind) {
|
|
32
|
+
half(pipe, kind, open = true) {
|
|
33
33
|
const id = `pipe-half:${++this.next}`;
|
|
34
|
-
this.halves.set(id, { pipe, kind, open
|
|
35
|
-
if (kind === "sink")
|
|
34
|
+
this.halves.set(id, { pipe, kind, open });
|
|
35
|
+
if (kind === "sink" && open)
|
|
36
36
|
pipe.sinks += 1;
|
|
37
37
|
return { kind, channelId: id };
|
|
38
38
|
}
|
|
@@ -46,9 +46,12 @@ export class Pipes {
|
|
|
46
46
|
}
|
|
47
47
|
cloneSink(value) {
|
|
48
48
|
const half = this.get(value);
|
|
49
|
-
if (
|
|
50
|
-
throw new Error("cannot clone a
|
|
51
|
-
return this.half(half.pipe, "sink");
|
|
49
|
+
if (half.kind !== "sink")
|
|
50
|
+
throw new Error("cannot clone a stream as a sink");
|
|
51
|
+
return this.half(half.pipe, "sink", half.open);
|
|
52
|
+
}
|
|
53
|
+
isClosed(value) {
|
|
54
|
+
return !this.get(value).open;
|
|
52
55
|
}
|
|
53
56
|
close(value, fault = null) {
|
|
54
57
|
const half = this.get(value);
|
|
@@ -11,5 +11,8 @@ export declare class FrameScheduler {
|
|
|
11
11
|
constructor(trace: TraceBuilder, policy: "round_robin" | "chaos");
|
|
12
12
|
enqueue(id: string, resume: () => void): void;
|
|
13
13
|
after(nanos: bigint, wake: () => void): () => void;
|
|
14
|
+
hasRunnableWork(): boolean;
|
|
15
|
+
advanceSlice(): void;
|
|
16
|
+
private fireTimers;
|
|
14
17
|
run(): void;
|
|
15
18
|
}
|
|
@@ -42,19 +42,29 @@ export class FrameScheduler {
|
|
|
42
42
|
this.timers.splice(index, 1);
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
|
+
hasRunnableWork() {
|
|
46
|
+
return this.ready.length > 0;
|
|
47
|
+
}
|
|
48
|
+
advanceSlice() {
|
|
49
|
+
this.trace.advanceVirtualClock(this.trace.replay.virtual_clock.tick_ms, null);
|
|
50
|
+
this.fireTimers();
|
|
51
|
+
}
|
|
52
|
+
fireTimers() {
|
|
53
|
+
for (let index = 0; index < this.timers.length;) {
|
|
54
|
+
if (this.timers[index].at <= this.trace.virtualTimeMs) {
|
|
55
|
+
this.timers.splice(index, 1)[0].wake();
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
index += 1;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
45
62
|
run() {
|
|
46
63
|
while (this.ready.length > 0 || this.timers.length > 0) {
|
|
47
64
|
if (this.ready.length === 0) {
|
|
48
65
|
const next = Math.min(...this.timers.map((timer) => timer.at));
|
|
49
66
|
this.trace.advanceVirtualClock(next - this.trace.virtualTimeMs, null);
|
|
50
|
-
|
|
51
|
-
if (this.timers[index].at <= next) {
|
|
52
|
-
this.timers.splice(index, 1)[0].wake();
|
|
53
|
-
}
|
|
54
|
-
else {
|
|
55
|
-
index += 1;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
67
|
+
this.fireTimers();
|
|
58
68
|
}
|
|
59
69
|
if (this.ready.length === 0)
|
|
60
70
|
continue;
|
|
@@ -7,9 +7,13 @@ export declare class ShimFault extends Error {
|
|
|
7
7
|
constructor(trap: TrapName, message: string);
|
|
8
8
|
}
|
|
9
9
|
export interface ShimHost {
|
|
10
|
+
readonly argv: readonly string[];
|
|
10
11
|
pipes?: Pipes;
|
|
11
12
|
newPipe?(capacity: number): VmValue;
|
|
12
13
|
closePipe?(value: VmValue): void;
|
|
14
|
+
generatorStream?(generator: VmValue): VmValue;
|
|
15
|
+
encoderSink?(sink: VmValue, encoder: VmValue): VmValue;
|
|
16
|
+
cloneSink?(sink: VmValue): VmValue;
|
|
13
17
|
releaseValue?(value: VmValue): void;
|
|
14
18
|
writeStdout(text: string): void;
|
|
15
19
|
writeStderr(text: string): void;
|
|
@@ -22,3 +26,9 @@ export declare function resolveRuntimeShim(entry: RuntimeFamilyEntry): RuntimeSh
|
|
|
22
26
|
export declare function resolveExternShim(symbol: string): RuntimeShim | undefined;
|
|
23
27
|
export declare const SUPPORTED_SUSPEND_KINDS: ReadonlySet<string>;
|
|
24
28
|
export declare const SUPPORTED_NATIVE_IO: ReadonlySet<string>;
|
|
29
|
+
export declare function decodeUtf8Checked(value: VmValue): {
|
|
30
|
+
value: string;
|
|
31
|
+
} | {
|
|
32
|
+
validUpTo: number;
|
|
33
|
+
errorLen: number | null;
|
|
34
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { charProperty, charCase } from "../unicode.js";
|
|
1
2
|
/// The VM's runtime-family and extern shim table.
|
|
2
3
|
///
|
|
3
4
|
/// This table is the runtime-family admission authority: a package may name a
|
|
@@ -6,7 +7,7 @@
|
|
|
6
7
|
/// reject list to keep in step with this one. `FileRead` is refused by its
|
|
7
8
|
/// absence, and a `NativeIo` suspension by an operation missing from
|
|
8
9
|
/// [`SUPPORTED_NATIVE_IO`].
|
|
9
|
-
import { UNIT, cloneValue, renderStdout } from "../values.js";
|
|
10
|
+
import { UNIT, cloneValue, compareUtf8, stringValue, renderStdout, } from "../values.js";
|
|
10
11
|
import { seededMt } from "./mt19937.js";
|
|
11
12
|
/// A language-visible fault raised inside a shim. The executor routes it to the
|
|
12
13
|
/// call's `unwind` edge, or ends the program when the call names none.
|
|
@@ -24,23 +25,26 @@ export class ShimFault extends Error {
|
|
|
24
25
|
export function resolveRuntimeShim(entry) {
|
|
25
26
|
switch (entry.family) {
|
|
26
27
|
case "SupervisorPool":
|
|
27
|
-
return ["Member", "Get"].includes(String(entry.detail))
|
|
28
|
+
return ["Len", "Member", "Get"].includes(String(entry.detail))
|
|
28
29
|
? () => {
|
|
29
30
|
throw new Error("SupervisorPool requires its resumable executor");
|
|
30
31
|
}
|
|
31
32
|
: undefined;
|
|
33
|
+
case "ActorCaller":
|
|
34
|
+
case "ActorRefusalLog":
|
|
32
35
|
case "StructuralFormat":
|
|
33
36
|
return () => {
|
|
34
37
|
throw new Error("StructuralFormat requires its resumable executor");
|
|
35
38
|
};
|
|
36
39
|
case "FileRead":
|
|
37
|
-
return entry.detail === "LastError"
|
|
38
|
-
? () => ({ kind: "string", value: "" })
|
|
39
|
-
: undefined;
|
|
40
|
+
return entry.detail === "LastError" ? () => str("") : undefined;
|
|
40
41
|
case "Print":
|
|
41
42
|
return printShim(entry.detail);
|
|
42
43
|
case "RcNew":
|
|
43
44
|
case "RcClone":
|
|
45
|
+
case "RcDowngrade":
|
|
46
|
+
case "WeakCloneRc":
|
|
47
|
+
case "WeakUpgradeRc":
|
|
44
48
|
case "RcGet":
|
|
45
49
|
case "RcIsUnique":
|
|
46
50
|
case "RcSet":
|
|
@@ -60,7 +64,11 @@ export function resolveRuntimeShim(entry) {
|
|
|
60
64
|
? () => {
|
|
61
65
|
throw new Error("array mutation requires its resumable executor");
|
|
62
66
|
}
|
|
63
|
-
:
|
|
67
|
+
: entry.detail === "IndexBorrow"
|
|
68
|
+
? (_host, args) => sequenceElement(args)
|
|
69
|
+
: ["Len", "Index"].includes(String(entry.detail))
|
|
70
|
+
? VECTOR_SHIMS[detailName(entry.detail)]
|
|
71
|
+
: undefined;
|
|
64
72
|
case "Map":
|
|
65
73
|
case "Set":
|
|
66
74
|
return [
|
|
@@ -104,6 +112,7 @@ export const SUPPORTED_SUSPEND_KINDS = new Set([
|
|
|
104
112
|
"GeneratorNext",
|
|
105
113
|
"Yield",
|
|
106
114
|
"Sleep",
|
|
115
|
+
"Safepoint",
|
|
107
116
|
"Ask",
|
|
108
117
|
"Await",
|
|
109
118
|
"Join",
|
|
@@ -138,19 +147,15 @@ const UNIT_FAMILY_SHIMS = {
|
|
|
138
147
|
StringToBytes: (_host, args) => ({
|
|
139
148
|
kind: "vector",
|
|
140
149
|
elementType: "u8",
|
|
141
|
-
items: [...
|
|
142
|
-
}),
|
|
143
|
-
StringToUppercase: (_host, args) => ({
|
|
144
|
-
kind: "string",
|
|
145
|
-
value: text(args, 0).toUpperCase(),
|
|
146
|
-
}),
|
|
147
|
-
StringToLowercase: (_host, args) => ({
|
|
148
|
-
kind: "string",
|
|
149
|
-
value: text(args, 0).toLowerCase(),
|
|
150
|
+
items: [...indexed(args, 0).encoded()].map((byte) => int(BigInt(byte))),
|
|
150
151
|
}),
|
|
152
|
+
StringToUppercase: (_host, args) => str(text(args, 0).toUpperCase()),
|
|
153
|
+
StringToLowercase: (_host, args) => str(text(args, 0).toLowerCase()),
|
|
151
154
|
StreamPairSink: (host, args) => host.pipes.extract(arg(args, 0), "sink"),
|
|
152
155
|
StreamPairStream: (host, args) => host.pipes.extract(arg(args, 0), "stream"),
|
|
153
|
-
|
|
156
|
+
StreamFromGenerator: (host, args) => host.generatorStream(arg(args, 0)),
|
|
157
|
+
SinkFromEncoder: (host, args) => host.encoderSink(arg(args, 0), arg(args, 1)),
|
|
158
|
+
SinkClone: (host, args) => host.cloneSink(arg(args, 0)),
|
|
154
159
|
SinkClose: (host, args) => {
|
|
155
160
|
host.closePipe(arg(args, 0));
|
|
156
161
|
return UNIT;
|
|
@@ -160,28 +165,92 @@ const UNIT_FAMILY_SHIMS = {
|
|
|
160
165
|
return UNIT;
|
|
161
166
|
},
|
|
162
167
|
StringConcat: (_host, args) => str(`${text(args, 0)}${text(args, 1)}`),
|
|
163
|
-
StringClone: (_host, args) =>
|
|
168
|
+
StringClone: (_host, args) => cloneValue(arg(args, 0)),
|
|
164
169
|
StringEquals: (_host, args) => bool(text(args, 0) === text(args, 1)),
|
|
165
|
-
|
|
170
|
+
StringCompare: (_host, args) => int(BigInt(compareUtf8(text(args, 0), text(args, 1)))),
|
|
171
|
+
StringStartsWith: (_host, args) => bool(text(args, 0).startsWith(text(args, 1))),
|
|
172
|
+
StringEndsWith: (_host, args) => bool(text(args, 0).endsWith(text(args, 1))),
|
|
173
|
+
StringContains: (_host, args) => bool(indexed(args, 0).find(indexed(args, 1)) !== undefined),
|
|
166
174
|
StringTrim: (_host, args) => str(text(args, 0).replace(/^\p{White_Space}+|\p{White_Space}+$/gu, "")),
|
|
167
|
-
|
|
168
|
-
|
|
175
|
+
StringLen: (_host, args) => int(BigInt(indexed(args, 0).scalarLength)),
|
|
176
|
+
StringByteLen: (_host, args) => int(BigInt(indexed(args, 0).byteLength)),
|
|
169
177
|
StringIndex: (_host, args) => {
|
|
170
|
-
const
|
|
178
|
+
const value = indexed(args, 0);
|
|
171
179
|
const at = index(args, 1);
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
return str(
|
|
180
|
+
const scalar = value.at(at);
|
|
181
|
+
if (scalar === undefined)
|
|
182
|
+
throw new ShimFault("vector_bounds", `string index ${at} out of bounds for length ${value.scalarLength}`);
|
|
183
|
+
return str(scalar);
|
|
184
|
+
},
|
|
185
|
+
StringGet: (host, args, shape) => {
|
|
186
|
+
if (!shape)
|
|
187
|
+
throw new TypeError("string get requires its Option shape");
|
|
188
|
+
const scalar = indexed(args, 0).at(index(args, 1));
|
|
189
|
+
return scalar === undefined
|
|
190
|
+
? host.enumValue(shape, "OptionNone", [])
|
|
191
|
+
: host.enumValue(shape, "OptionSome", [str(scalar)]);
|
|
192
|
+
},
|
|
193
|
+
StringFind: (host, args, shape) => {
|
|
194
|
+
if (!shape)
|
|
195
|
+
throw new TypeError("string find requires its Option shape");
|
|
196
|
+
const found = indexed(args, 0).find(indexed(args, 1));
|
|
197
|
+
return found === undefined
|
|
198
|
+
? host.enumValue(shape, "OptionNone", [])
|
|
199
|
+
: host.enumValue(shape, "OptionSome", [int(BigInt(found))]);
|
|
200
|
+
},
|
|
201
|
+
StringFindFrom: (host, args, shape) => {
|
|
202
|
+
if (!shape)
|
|
203
|
+
throw new TypeError("string find_from requires its Option shape");
|
|
204
|
+
const found = indexed(args, 0).find(indexed(args, 1), index(args, 2));
|
|
205
|
+
return found === undefined
|
|
206
|
+
? host.enumValue(shape, "OptionNone", [])
|
|
207
|
+
: host.enumValue(shape, "OptionSome", [int(BigInt(found))]);
|
|
208
|
+
},
|
|
209
|
+
StringAppend: (_host, args) => indexed(args, 0).append(indexed(args, 1)),
|
|
210
|
+
StringPush: (_host, args) => indexed(args, 0).append(indexed(args, 1)),
|
|
211
|
+
StringClear: (_host, args) => {
|
|
212
|
+
indexed(args, 0).release();
|
|
213
|
+
return str("");
|
|
214
|
+
},
|
|
215
|
+
StringIsEmpty: (_host, args) => bool(indexed(args, 0).scalarLength === 0),
|
|
216
|
+
StringSplit: (_host, args) => {
|
|
217
|
+
const value = indexed(args, 0).value;
|
|
218
|
+
const separator = indexed(args, 1).value;
|
|
219
|
+
const parts = separator === "" ? Array.from(value) : value.split(separator);
|
|
220
|
+
return { kind: "vector", elementType: "string", items: parts.map(str) };
|
|
176
221
|
},
|
|
177
|
-
StringIsEmpty: (_host, args) => bool(text(args, 0).length === 0),
|
|
178
222
|
StringReplace: (_host, args) => str(text(args, 0).split(text(args, 1)).join(text(args, 2))),
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
return
|
|
223
|
+
StringRepeat: (_host, args) => str(text(args, 0).repeat(Math.max(0, index(args, 1)))),
|
|
224
|
+
StringGetRange: (host, args, shape) => {
|
|
225
|
+
if (!shape)
|
|
226
|
+
throw new TypeError("string range get requires its Option shape");
|
|
227
|
+
const value = stringRange(args);
|
|
228
|
+
return value === undefined
|
|
229
|
+
? host.enumValue(shape, "OptionNone", [])
|
|
230
|
+
: host.enumValue(shape, "OptionSome", [value]);
|
|
231
|
+
},
|
|
232
|
+
StringSliceRange: (_host, args) => {
|
|
233
|
+
const value = stringRange(args);
|
|
234
|
+
if (value === undefined)
|
|
235
|
+
throw new ShimFault("vector_bounds", "string range out of bounds");
|
|
236
|
+
return value;
|
|
237
|
+
},
|
|
238
|
+
StringSliceCodepoints: (_host, args) => {
|
|
239
|
+
const value = indexed(args, 0);
|
|
240
|
+
const start = index(args, 1);
|
|
241
|
+
const end = index(args, 2);
|
|
242
|
+
const sliced = value.slice(start, end);
|
|
243
|
+
if (!sliced)
|
|
244
|
+
throw new ShimFault("vector_bounds", `string slice ${start}..${end} out of bounds for length ${value.scalarLength}`);
|
|
245
|
+
return sliced;
|
|
246
|
+
},
|
|
247
|
+
StringSliceCodepointsFrom: (_host, args) => {
|
|
248
|
+
const value = indexed(args, 0);
|
|
249
|
+
const start = index(args, 1);
|
|
250
|
+
const sliced = value.slice(start, value.scalarLength);
|
|
251
|
+
if (!sliced)
|
|
252
|
+
throw new ShimFault("vector_bounds", `string slice ${start}.. out of bounds for length ${value.scalarLength}`);
|
|
253
|
+
return sliced;
|
|
185
254
|
},
|
|
186
255
|
I64ToString: (_host, args) => str(renderStdout(arg(args, 0))),
|
|
187
256
|
U64ToString: (_host, args) => str(renderStdout(arg(args, 0))),
|
|
@@ -190,8 +259,41 @@ const UNIT_FAMILY_SHIMS = {
|
|
|
190
259
|
U8ToString: (_host, args) => str(renderStdout(arg(args, 0))),
|
|
191
260
|
BoolToString: (_host, args) => str(renderStdout(arg(args, 0))),
|
|
192
261
|
F64ToString: (_host, args) => str(renderStdout(arg(args, 0))),
|
|
262
|
+
CharIsDigit: (_host, args) => bool(charProperty(text(args, 0), 0)),
|
|
263
|
+
CharIsLetter: (_host, args) => bool(charProperty(text(args, 0), 1)),
|
|
264
|
+
CharIsAlphanumeric: (_host, args) => bool(charProperty(text(args, 0), 2)),
|
|
265
|
+
CharIsWhitespace: (_host, args) => bool(charProperty(text(args, 0), 3)),
|
|
266
|
+
CharIsUpper: (_host, args) => bool(charProperty(text(args, 0), 4)),
|
|
267
|
+
CharIsLower: (_host, args) => bool(charProperty(text(args, 0), 5)),
|
|
268
|
+
CharIsPunctuation: (_host, args) => bool(charProperty(text(args, 0), 6)),
|
|
269
|
+
CharIsControl: (_host, args) => bool(charProperty(text(args, 0), 7)),
|
|
270
|
+
CharIsAscii: (_host, args) => bool(charProperty(text(args, 0), 8)),
|
|
271
|
+
CharLenUtf8: (_host, args) => int(BigInt(indexed(args, 0).byteLength)),
|
|
272
|
+
CharToUpper: (_host, args) => str(charCase(text(args, 0), "upper")),
|
|
273
|
+
CharToLower: (_host, args) => str(charCase(text(args, 0), "lower")),
|
|
274
|
+
CharToTitle: (_host, args) => str(charCase(text(args, 0), "title")),
|
|
275
|
+
CharToDigit: (host, args, shape) => {
|
|
276
|
+
if (!shape)
|
|
277
|
+
throw new TypeError("char to_digit requires its Option shape");
|
|
278
|
+
const radix = index(args, 1);
|
|
279
|
+
const scalar = text(args, 0).codePointAt(0);
|
|
280
|
+
const digit = scalar >= 48 && scalar <= 57
|
|
281
|
+
? scalar - 48
|
|
282
|
+
: scalar >= 65 && scalar <= 90
|
|
283
|
+
? scalar - 65 + 10
|
|
284
|
+
: scalar >= 97 && scalar <= 122
|
|
285
|
+
? scalar - 97 + 10
|
|
286
|
+
: -1;
|
|
287
|
+
return radix < 2 || radix > 36 || digit < 0 || digit >= radix
|
|
288
|
+
? host.enumValue(shape, "OptionNone", [])
|
|
289
|
+
: host.enumValue(shape, "OptionSome", [int(BigInt(digit))]);
|
|
290
|
+
},
|
|
291
|
+
CharFromI64: checkedChar,
|
|
292
|
+
CharFromU64: checkedChar,
|
|
193
293
|
CharToString: (_host, args) => str(renderStdout(arg(args, 0))),
|
|
194
|
-
BytesDecodeUtf8: (
|
|
294
|
+
BytesDecodeUtf8: () => {
|
|
295
|
+
throw new Error("UTF-8 decode requires its checked result carrier");
|
|
296
|
+
},
|
|
195
297
|
// TextDecoder replaces each maximal invalid subsequence with U+FFFD, as
|
|
196
298
|
// native lossy decoding does.
|
|
197
299
|
BytesDecodeUtf8Lossy: (_host, args) => str(decodeUtf8(arg(args, 0))),
|
|
@@ -316,15 +418,15 @@ const VECTOR_SHIMS = {
|
|
|
316
418
|
? host.enumValue(shape, "OptionSome", [cloneValue(items[at])])
|
|
317
419
|
: host.enumValue(shape, "OptionNone", []);
|
|
318
420
|
},
|
|
319
|
-
Index: (_host, args) =>
|
|
421
|
+
Index: (_host, args) => cloneValue(sequenceElement(args)),
|
|
422
|
+
IndexBorrow: (_host, args) => sequenceElement(args),
|
|
423
|
+
Len: (_host, args) => int(BigInt(vec(args, 0).items.length)),
|
|
424
|
+
Join: (_host, args) => {
|
|
320
425
|
const items = vec(args, 0).items;
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
}
|
|
325
|
-
return cloneValue(items[at]);
|
|
426
|
+
return str(items
|
|
427
|
+
.map((_, at) => indexed(items, at).value)
|
|
428
|
+
.join(indexed(args, 1).value));
|
|
326
429
|
},
|
|
327
|
-
Len: (_host, args) => int(BigInt(vec(args, 0).items.length)),
|
|
328
430
|
Slice: (_host, args) => {
|
|
329
431
|
const items = vec(args, 0).items;
|
|
330
432
|
const start = index(args, 1);
|
|
@@ -347,6 +449,14 @@ const VECTOR_SHIMS = {
|
|
|
347
449
|
};
|
|
348
450
|
},
|
|
349
451
|
};
|
|
452
|
+
function sequenceElement(args) {
|
|
453
|
+
const items = vec(args, 0).items;
|
|
454
|
+
const at = index(args, 1);
|
|
455
|
+
if (at < 0 || at >= items.length) {
|
|
456
|
+
throw new ShimFault("vector_bounds", `vector index ${at} out of bounds for length ${items.length}`);
|
|
457
|
+
}
|
|
458
|
+
return items[at];
|
|
459
|
+
}
|
|
350
460
|
function byteVector(items) {
|
|
351
461
|
return { kind: "vector", elementType: "u8", items: items.map(cloneValue) };
|
|
352
462
|
}
|
|
@@ -656,6 +766,24 @@ function bigMax(left, right) {
|
|
|
656
766
|
}
|
|
657
767
|
// ── externs ─────────────────────────────────────────────────────────────────
|
|
658
768
|
const EXTERN_SHIMS = {
|
|
769
|
+
hew_args_count: (host) => int(BigInt(host.argv.length)),
|
|
770
|
+
// A native null managed string is canonical empty. Each result owns fresh
|
|
771
|
+
// string storage; releasing it cannot change the host's supplied arguments.
|
|
772
|
+
hew_args_get: (host, args) => str(host.argv[index(args, 0)] ?? ""),
|
|
773
|
+
hew_actor_timer_cancel: (_host, args) => {
|
|
774
|
+
const timer = arg(args, 0);
|
|
775
|
+
if (timer.kind !== "timer" || timer.closed)
|
|
776
|
+
throw new Error("invalid timer handle");
|
|
777
|
+
timer.cancel();
|
|
778
|
+
return UNIT;
|
|
779
|
+
},
|
|
780
|
+
hew_actor_timer_release: (_host, args) => {
|
|
781
|
+
const timer = arg(args, 0);
|
|
782
|
+
if (timer.kind !== "timer" || timer.closed)
|
|
783
|
+
throw new Error("invalid timer handle");
|
|
784
|
+
timer.closed = true;
|
|
785
|
+
return UNIT;
|
|
786
|
+
},
|
|
659
787
|
hew_msg_envelope_release: (host, args) => {
|
|
660
788
|
host.releaseValue(arg(args, 0));
|
|
661
789
|
return UNIT;
|
|
@@ -666,7 +794,7 @@ const EXTERN_SHIMS = {
|
|
|
666
794
|
host.pipes.freePair(arg(args, 0));
|
|
667
795
|
return UNIT;
|
|
668
796
|
},
|
|
669
|
-
hew_stream_last_error: () => (
|
|
797
|
+
hew_stream_last_error: () => str(""),
|
|
670
798
|
hew_regex_new: (host, args) => compileRegex(host, args),
|
|
671
799
|
hew_regex_clone: (_host, args) => cloneValue(arg(args, 0)),
|
|
672
800
|
// Releasing a handle the VM traces by reference is nothing to do.
|
|
@@ -712,6 +840,47 @@ const EXTERN_SHIMS = {
|
|
|
712
840
|
return int(high <= low ? low : low + host.prng.randbelow(high - low));
|
|
713
841
|
},
|
|
714
842
|
};
|
|
843
|
+
export function decodeUtf8Checked(value) {
|
|
844
|
+
if (value.kind !== "vector")
|
|
845
|
+
throw new TypeError("UTF-8 input must be bytes");
|
|
846
|
+
const bytes = Uint8Array.from(value.items.map((item) => Number(integer([item], 0))));
|
|
847
|
+
let at = 0;
|
|
848
|
+
while (at < bytes.length) {
|
|
849
|
+
const first = bytes[at];
|
|
850
|
+
if (first < 128) {
|
|
851
|
+
at++;
|
|
852
|
+
continue;
|
|
853
|
+
}
|
|
854
|
+
const width = first >= 194 && first <= 223
|
|
855
|
+
? 2
|
|
856
|
+
: first >= 224 && first <= 239
|
|
857
|
+
? 3
|
|
858
|
+
: first >= 240 && first <= 244
|
|
859
|
+
? 4
|
|
860
|
+
: 0;
|
|
861
|
+
if (width === 0)
|
|
862
|
+
return { validUpTo: at, errorLen: 1 };
|
|
863
|
+
for (let offset = 1; offset < width; offset++) {
|
|
864
|
+
if (at + offset >= bytes.length)
|
|
865
|
+
return { validUpTo: at, errorLen: null };
|
|
866
|
+
const next = bytes[at + offset];
|
|
867
|
+
const lower = offset === 1 && first === 224
|
|
868
|
+
? 160
|
|
869
|
+
: offset === 1 && first === 240
|
|
870
|
+
? 144
|
|
871
|
+
: 128;
|
|
872
|
+
const upper = offset === 1 && first === 237
|
|
873
|
+
? 159
|
|
874
|
+
: offset === 1 && first === 244
|
|
875
|
+
? 143
|
|
876
|
+
: 191;
|
|
877
|
+
if (next < lower || next > upper)
|
|
878
|
+
return { validUpTo: at, errorLen: offset };
|
|
879
|
+
}
|
|
880
|
+
at += width;
|
|
881
|
+
}
|
|
882
|
+
return { value: new TextDecoder("utf-8", { ignoreBOM: true }).decode(bytes) };
|
|
883
|
+
}
|
|
715
884
|
/// `bytes` is a vector of byte values, so decoding walks its items.
|
|
716
885
|
function decodeUtf8(value) {
|
|
717
886
|
if (value.kind === "string") {
|
|
@@ -741,6 +910,12 @@ function detailName(detail) {
|
|
|
741
910
|
function arg(args, at) {
|
|
742
911
|
return args[at] ?? UNIT;
|
|
743
912
|
}
|
|
913
|
+
function indexed(args, at) {
|
|
914
|
+
const value = arg(args, at);
|
|
915
|
+
if (value.kind !== "string")
|
|
916
|
+
throw new TypeError(`runtime call expected a string operand, got ${value.kind}`);
|
|
917
|
+
return value;
|
|
918
|
+
}
|
|
744
919
|
function text(args, at) {
|
|
745
920
|
const value = arg(args, at);
|
|
746
921
|
return value.kind === "string" ? value.value : renderStdout(value);
|
|
@@ -770,7 +945,7 @@ function comparable(value) {
|
|
|
770
945
|
return `${value.kind}:${renderStdout(value)}`;
|
|
771
946
|
}
|
|
772
947
|
function str(value) {
|
|
773
|
-
return
|
|
948
|
+
return stringValue(value);
|
|
774
949
|
}
|
|
775
950
|
function bool(value) {
|
|
776
951
|
return { kind: "bool", value };
|
|
@@ -778,3 +953,29 @@ function bool(value) {
|
|
|
778
953
|
function int(value) {
|
|
779
954
|
return { kind: "i64", value };
|
|
780
955
|
}
|
|
956
|
+
function checkedChar(host, args, shape) {
|
|
957
|
+
if (!shape)
|
|
958
|
+
throw new TypeError("try_to_char requires its Option shape");
|
|
959
|
+
const value = integer(args, 0);
|
|
960
|
+
return value < 0n ||
|
|
961
|
+
value > 0x10ffffn ||
|
|
962
|
+
(value >= 0xd800n && value <= 0xdfffn)
|
|
963
|
+
? host.enumValue(shape, "OptionNone", [])
|
|
964
|
+
: host.enumValue(shape, "OptionSome", [
|
|
965
|
+
str(String.fromCodePoint(Number(value))),
|
|
966
|
+
]);
|
|
967
|
+
}
|
|
968
|
+
function stringRange(args) {
|
|
969
|
+
const value = indexed(args, 0);
|
|
970
|
+
const inclusive = arg(args, 3);
|
|
971
|
+
const openEnd = arg(args, 4);
|
|
972
|
+
if (inclusive.kind !== "bool" || openEnd.kind !== "bool")
|
|
973
|
+
throw new TypeError("string range flags must be bool");
|
|
974
|
+
const start = integer(args, 1);
|
|
975
|
+
const end = openEnd.value
|
|
976
|
+
? BigInt(value.scalarLength)
|
|
977
|
+
: integer(args, 2) + (inclusive.value ? 1n : 0n);
|
|
978
|
+
if (start < 0n || end < start || end > BigInt(value.scalarLength))
|
|
979
|
+
return undefined;
|
|
980
|
+
return value.slice(Number(start), Number(end));
|
|
981
|
+
}
|