@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
|
@@ -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" | "ActorErrorRefused" | "CallerActor" | "CallerTask" | "CallerTimer" | "CallerNode" | "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;
|
|
@@ -110,6 +110,7 @@ export interface VtableShape {
|
|
|
110
110
|
}>;
|
|
111
111
|
}
|
|
112
112
|
export interface FunctionV1 {
|
|
113
|
+
actor_construction?: boolean;
|
|
113
114
|
id: number;
|
|
114
115
|
name: string;
|
|
115
116
|
params: ValueDef[];
|
|
@@ -369,6 +370,25 @@ export type OpV1 = (OpBase & {
|
|
|
369
370
|
dst: number;
|
|
370
371
|
scope: number;
|
|
371
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[];
|
|
372
392
|
});
|
|
373
393
|
export type OpName = OpV1["op"];
|
|
374
394
|
interface TermBase {
|
|
@@ -452,12 +472,17 @@ export type TermV1 = (TermBase & {
|
|
|
452
472
|
callbacks: Record<string, number[]>;
|
|
453
473
|
}) | (TermBase & CallShape & {
|
|
454
474
|
op: "runtime.call";
|
|
475
|
+
shared_payload_type?: string;
|
|
455
476
|
family: number;
|
|
456
477
|
structural?: number | null;
|
|
457
478
|
callbacks?: number[];
|
|
458
479
|
releases_contents?: boolean;
|
|
459
480
|
keeps_inputs_on_failure?: boolean;
|
|
460
481
|
result_member_shapes?: Array<number | null>;
|
|
482
|
+
variant_result_shapes?: {
|
|
483
|
+
error: number;
|
|
484
|
+
error_len: number;
|
|
485
|
+
};
|
|
461
486
|
result_shape: number | null;
|
|
462
487
|
}) | (TermBase & CallShape & {
|
|
463
488
|
op: "extern.call";
|
|
@@ -520,7 +545,7 @@ export type TermV1 = (TermBase & {
|
|
|
520
545
|
inputs: BoundaryOperand[];
|
|
521
546
|
result: CallResult;
|
|
522
547
|
resumes: Edge[];
|
|
523
|
-
cancel: Edge;
|
|
548
|
+
cancel: Edge | null;
|
|
524
549
|
unwind: Edge | null;
|
|
525
550
|
});
|
|
526
551
|
export type TermName = TermV1["op"];
|
|
@@ -535,6 +560,8 @@ export interface ActorProtocol {
|
|
|
535
560
|
sealed: boolean;
|
|
536
561
|
}
|
|
537
562
|
export type ActorOperation = {
|
|
563
|
+
op: "identity";
|
|
564
|
+
} | {
|
|
538
565
|
op: "spawn" | "self_handle" | "stop" | "terminate" | "await_stopped" | "await_restarted";
|
|
539
566
|
actor: number;
|
|
540
567
|
} | {
|
|
@@ -548,6 +575,12 @@ export type ActorOperation = {
|
|
|
548
575
|
op: "stream_start";
|
|
549
576
|
actor: number;
|
|
550
577
|
message: number;
|
|
578
|
+
} | {
|
|
579
|
+
op: "timer_schedule";
|
|
580
|
+
actor: number;
|
|
581
|
+
message: number;
|
|
582
|
+
resource: number;
|
|
583
|
+
policy: "reject" | "drop_newest";
|
|
551
584
|
} | {
|
|
552
585
|
op: "local_observation";
|
|
553
586
|
kind: string;
|
|
@@ -558,10 +591,10 @@ export type ActorOperation = {
|
|
|
558
591
|
op: "supervisor_child" | "supervisor_pool_view";
|
|
559
592
|
supervisor: number;
|
|
560
593
|
child: number;
|
|
561
|
-
owner_is_role: boolean;
|
|
562
594
|
};
|
|
563
595
|
export interface ActorShape {
|
|
564
596
|
id: number;
|
|
597
|
+
name: string;
|
|
565
598
|
state_fields: Array<{
|
|
566
599
|
mutable: boolean;
|
|
567
600
|
deferred: boolean;
|
|
@@ -571,6 +604,7 @@ export interface ActorShape {
|
|
|
571
604
|
stop: number[];
|
|
572
605
|
crash?: number;
|
|
573
606
|
crash_info?: number;
|
|
607
|
+
crash_kind?: number;
|
|
574
608
|
crash_action?: number;
|
|
575
609
|
exit?: number;
|
|
576
610
|
down?: number;
|
|
@@ -609,7 +643,8 @@ export interface SupervisorShape {
|
|
|
609
643
|
};
|
|
610
644
|
restart: string;
|
|
611
645
|
stop_ns?: number | null;
|
|
612
|
-
|
|
646
|
+
stop_config?: number;
|
|
647
|
+
is_pool?: boolean;
|
|
613
648
|
spawn: number;
|
|
614
649
|
}>;
|
|
615
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, compareUtf8, 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,7 +25,7 @@ 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
|
}
|
|
@@ -36,13 +37,14 @@ export function resolveRuntimeShim(entry) {
|
|
|
36
37
|
throw new Error("StructuralFormat requires its resumable executor");
|
|
37
38
|
};
|
|
38
39
|
case "FileRead":
|
|
39
|
-
return entry.detail === "LastError"
|
|
40
|
-
? () => ({ kind: "string", value: "" })
|
|
41
|
-
: undefined;
|
|
40
|
+
return entry.detail === "LastError" ? () => str("") : undefined;
|
|
42
41
|
case "Print":
|
|
43
42
|
return printShim(entry.detail);
|
|
44
43
|
case "RcNew":
|
|
45
44
|
case "RcClone":
|
|
45
|
+
case "RcDowngrade":
|
|
46
|
+
case "WeakCloneRc":
|
|
47
|
+
case "WeakUpgradeRc":
|
|
46
48
|
case "RcGet":
|
|
47
49
|
case "RcIsUnique":
|
|
48
50
|
case "RcSet":
|
|
@@ -110,6 +112,7 @@ export const SUPPORTED_SUSPEND_KINDS = new Set([
|
|
|
110
112
|
"GeneratorNext",
|
|
111
113
|
"Yield",
|
|
112
114
|
"Sleep",
|
|
115
|
+
"Safepoint",
|
|
113
116
|
"Ask",
|
|
114
117
|
"Await",
|
|
115
118
|
"Join",
|
|
@@ -144,19 +147,15 @@ const UNIT_FAMILY_SHIMS = {
|
|
|
144
147
|
StringToBytes: (_host, args) => ({
|
|
145
148
|
kind: "vector",
|
|
146
149
|
elementType: "u8",
|
|
147
|
-
items: [...
|
|
148
|
-
}),
|
|
149
|
-
StringToUppercase: (_host, args) => ({
|
|
150
|
-
kind: "string",
|
|
151
|
-
value: text(args, 0).toUpperCase(),
|
|
152
|
-
}),
|
|
153
|
-
StringToLowercase: (_host, args) => ({
|
|
154
|
-
kind: "string",
|
|
155
|
-
value: text(args, 0).toLowerCase(),
|
|
150
|
+
items: [...indexed(args, 0).encoded()].map((byte) => int(BigInt(byte))),
|
|
156
151
|
}),
|
|
152
|
+
StringToUppercase: (_host, args) => str(text(args, 0).toUpperCase()),
|
|
153
|
+
StringToLowercase: (_host, args) => str(text(args, 0).toLowerCase()),
|
|
157
154
|
StreamPairSink: (host, args) => host.pipes.extract(arg(args, 0), "sink"),
|
|
158
155
|
StreamPairStream: (host, args) => host.pipes.extract(arg(args, 0), "stream"),
|
|
159
|
-
|
|
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)),
|
|
160
159
|
SinkClose: (host, args) => {
|
|
161
160
|
host.closePipe(arg(args, 0));
|
|
162
161
|
return UNIT;
|
|
@@ -166,29 +165,92 @@ const UNIT_FAMILY_SHIMS = {
|
|
|
166
165
|
return UNIT;
|
|
167
166
|
},
|
|
168
167
|
StringConcat: (_host, args) => str(`${text(args, 0)}${text(args, 1)}`),
|
|
169
|
-
StringClone: (_host, args) =>
|
|
168
|
+
StringClone: (_host, args) => cloneValue(arg(args, 0)),
|
|
170
169
|
StringEquals: (_host, args) => bool(text(args, 0) === text(args, 1)),
|
|
171
170
|
StringCompare: (_host, args) => int(BigInt(compareUtf8(text(args, 0), text(args, 1)))),
|
|
172
|
-
|
|
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),
|
|
173
174
|
StringTrim: (_host, args) => str(text(args, 0).replace(/^\p{White_Space}+|\p{White_Space}+$/gu, "")),
|
|
174
|
-
|
|
175
|
-
|
|
175
|
+
StringLen: (_host, args) => int(BigInt(indexed(args, 0).scalarLength)),
|
|
176
|
+
StringByteLen: (_host, args) => int(BigInt(indexed(args, 0).byteLength)),
|
|
176
177
|
StringIndex: (_host, args) => {
|
|
177
|
-
const
|
|
178
|
+
const value = indexed(args, 0);
|
|
178
179
|
const at = index(args, 1);
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
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) };
|
|
183
221
|
},
|
|
184
|
-
StringIsEmpty: (_host, args) => bool(text(args, 0).length === 0),
|
|
185
222
|
StringReplace: (_host, args) => str(text(args, 0).split(text(args, 1)).join(text(args, 2))),
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
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;
|
|
192
254
|
},
|
|
193
255
|
I64ToString: (_host, args) => str(renderStdout(arg(args, 0))),
|
|
194
256
|
U64ToString: (_host, args) => str(renderStdout(arg(args, 0))),
|
|
@@ -197,8 +259,41 @@ const UNIT_FAMILY_SHIMS = {
|
|
|
197
259
|
U8ToString: (_host, args) => str(renderStdout(arg(args, 0))),
|
|
198
260
|
BoolToString: (_host, args) => str(renderStdout(arg(args, 0))),
|
|
199
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,
|
|
200
293
|
CharToString: (_host, args) => str(renderStdout(arg(args, 0))),
|
|
201
|
-
BytesDecodeUtf8: (
|
|
294
|
+
BytesDecodeUtf8: () => {
|
|
295
|
+
throw new Error("UTF-8 decode requires its checked result carrier");
|
|
296
|
+
},
|
|
202
297
|
// TextDecoder replaces each maximal invalid subsequence with U+FFFD, as
|
|
203
298
|
// native lossy decoding does.
|
|
204
299
|
BytesDecodeUtf8Lossy: (_host, args) => str(decodeUtf8(arg(args, 0))),
|
|
@@ -326,6 +421,12 @@ const VECTOR_SHIMS = {
|
|
|
326
421
|
Index: (_host, args) => cloneValue(sequenceElement(args)),
|
|
327
422
|
IndexBorrow: (_host, args) => sequenceElement(args),
|
|
328
423
|
Len: (_host, args) => int(BigInt(vec(args, 0).items.length)),
|
|
424
|
+
Join: (_host, args) => {
|
|
425
|
+
const items = vec(args, 0).items;
|
|
426
|
+
return str(items
|
|
427
|
+
.map((_, at) => indexed(items, at).value)
|
|
428
|
+
.join(indexed(args, 1).value));
|
|
429
|
+
},
|
|
329
430
|
Slice: (_host, args) => {
|
|
330
431
|
const items = vec(args, 0).items;
|
|
331
432
|
const start = index(args, 1);
|
|
@@ -665,6 +766,24 @@ function bigMax(left, right) {
|
|
|
665
766
|
}
|
|
666
767
|
// ── externs ─────────────────────────────────────────────────────────────────
|
|
667
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
|
+
},
|
|
668
787
|
hew_msg_envelope_release: (host, args) => {
|
|
669
788
|
host.releaseValue(arg(args, 0));
|
|
670
789
|
return UNIT;
|
|
@@ -675,7 +794,7 @@ const EXTERN_SHIMS = {
|
|
|
675
794
|
host.pipes.freePair(arg(args, 0));
|
|
676
795
|
return UNIT;
|
|
677
796
|
},
|
|
678
|
-
hew_stream_last_error: () => (
|
|
797
|
+
hew_stream_last_error: () => str(""),
|
|
679
798
|
hew_regex_new: (host, args) => compileRegex(host, args),
|
|
680
799
|
hew_regex_clone: (_host, args) => cloneValue(arg(args, 0)),
|
|
681
800
|
// Releasing a handle the VM traces by reference is nothing to do.
|
|
@@ -721,6 +840,47 @@ const EXTERN_SHIMS = {
|
|
|
721
840
|
return int(high <= low ? low : low + host.prng.randbelow(high - low));
|
|
722
841
|
},
|
|
723
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
|
+
}
|
|
724
884
|
/// `bytes` is a vector of byte values, so decoding walks its items.
|
|
725
885
|
function decodeUtf8(value) {
|
|
726
886
|
if (value.kind === "string") {
|
|
@@ -750,6 +910,12 @@ function detailName(detail) {
|
|
|
750
910
|
function arg(args, at) {
|
|
751
911
|
return args[at] ?? UNIT;
|
|
752
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
|
+
}
|
|
753
919
|
function text(args, at) {
|
|
754
920
|
const value = arg(args, at);
|
|
755
921
|
return value.kind === "string" ? value.value : renderStdout(value);
|
|
@@ -779,7 +945,7 @@ function comparable(value) {
|
|
|
779
945
|
return `${value.kind}:${renderStdout(value)}`;
|
|
780
946
|
}
|
|
781
947
|
function str(value) {
|
|
782
|
-
return
|
|
948
|
+
return stringValue(value);
|
|
783
949
|
}
|
|
784
950
|
function bool(value) {
|
|
785
951
|
return { kind: "bool", value };
|
|
@@ -787,3 +953,29 @@ function bool(value) {
|
|
|
787
953
|
function int(value) {
|
|
788
954
|
return { kind: "i64", value };
|
|
789
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
|
+
}
|