Skip to documentation
SLOP

tiny.mprompt.effect

Reference tiny.mprompt effect

Defined in tiny.mprompt.

Code in a body makes a named request, and the nearest enclosing code registered for that request answers it and decides whether the body continues, how often, and with what value: algebraic effect handlers, built on the package's stack-switching runtime.

API (32)

Actions

Public operations.

Types and contracts

Public types and contracts.

Values and defaults

Public values and defaults.

No direct callersNo direct callstiny.mprompteffect
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Called byCallsNo direct callersprivate; no linktiny.mpromptfunprivate sourcelib.mprompt.src.effectpopFrameprivate sourcelib.mprompt.src.effectpushFrameeffectfinally
Static calls · unresolved targets: 1 · external targets: 0.
Called byCallsNo direct callerseffecthandleRaweffecthandle
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callseffectEffectDefinitioneffecthandleeffecthandleRaw
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callerseffectoperationTableeffecthandlerDef
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate; no linktiny.mpromptfunprivate sourcelib.mprompt.src.effectpopFrameprivate sourcelib.mprompt.src.effectpushFrameeffectmask
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callseffecthandlerDefeffectoperationTable
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsprivate sourcelib.mprompt.src.effectunhandledOperationeffectoptagName
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate sourcelib.mprompt.src.effectperformForwardeffectperformRaweffectperform
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallseffectEffectDefinitioneffectperformprivate sourcelib.mprompt.src.effectfindprivate sourcelib.mprompt.src.effectperformAtprivate sourcelib.mprompt.src.effectunhandledOperationeffectperformRaw
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallseffectEffectContinuationprivate sourcelib.mprompt.src.effectresumeInternaleffectresumeEffect
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallseffectEffectContinuationprivate sourcelib.mprompt.src.effectresumeInternaleffectresumeFinal
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallseffectEffectContinuationprivate sourcelib.mprompt.src.effectresumeUnwindeffectresumeRelease
Static calls · unresolved targets: 0 · external targets: 3.
Called byCallsNo direct callseffectEffectContinuationeffectresumeTail
Static calls · unresolved targets: 0 · external targets: 2.

Source: lib/mprompt/src/effect.zig

zig
//! Code in a body makes a named request, and the nearest enclosing code registered for that request//! answers it and decides whether the body continues, how often, and with what value: algebraic//! effect handlers, built on the package's stack-switching runtime. Two layers provide this//! control: C-convention functions over tables of raw pointers, and a typed layer that builds those//! tables at compile time from a Zig description of the requests.//!//! A body wants to ask for things such as a value to read, a state to update, a choice among//! branches, or an early exit, without knowing which enclosing code answers, so the same body can//! run under different answers. The answering code needs a range of powers: answer and let the body//! go on at once, keep the body waiting and continue it later, continue it several times to explore//! each branch of a choice, or end it early.//!//! Finding the answering code happens at run time, by walking outward from the request through the//! code that encloses it, because answering code nests and one body can run under different//! answering code. Keeping the body waiting means switching stacks and saving registers, and an//! answer that lets the body go on at once, such as reading a value or updating a counter, needs//! neither. Continuing a body more than once needs a copy of its suspended stack, and ending it//! early has to discard that stack without running the rest of the body.//!//! Daan Leijen's paper [Implementing Algebraic Effects in//! C](https://www.microsoft.com/en-us/research/publication/implementing-algebraic-effects-c/) and//! his [libmprompt](https://github.com/koka-lang/libmprompt) library implement algebraic effect//! handlers on top of multi-prompt delimited continuations. The package keeps libmprompt's way of//! moving control through handlers: each handler runs its body under a prompt of its own, and an//! operation that suspends the body moves control to its handler's prompt. The package's reference//! benchmark reruns libmprompt's effect workloads, among them a reader, a state counter, a choice//! search, n-queens and a triples search, at the sizes meant for optimized builds.//!//! The package identifies each effect by one table of strings: the effect's name, then one name per//! operation. The runtime finds an operation's handler by comparing that table's address. A handler//! wraps its body in a frame on a new stack, and the runtime walks the frames outward from the call//! that performs an operation to the innermost handler for its effect. A handler answers each of//! its operations with a function written for that operation (*clause*). Each clause carries a kind//! fixed when the handler is written, so a clause that lets the body go on at once runs in place on//! the body's stack as an ordinary call, and only the kinds that may keep the body waiting switch//! stacks. One of those kinds allows at most one resume, made while its clause runs, so it keeps//! its handle in the clause's own stack frame and allocates nothing. The others allocate their//! handle from the process allocator. The handler search keeps the last handler it found and reuses//! it for the same effect until a frame is pushed or popped. The typed layer declares an effect//! once with `EffectDefinition`, builds its tables at compile time, allows at most eight//! operations, and rejects a handler that leaves an operation without a clause at compile time.//!//! - *local state*: a pointer the handler keeps and receives back with each resume//! - *result function*: a handler's optional function that replaces the body's result//! - *operation tag*: the record naming one operation by its effect and index//! - *mask*: a frame that makes operations of one effect skip their innermost handler//! - *release*: end a suspended body without running the rest of it//! - *tail resume*: a resume made as a function's last act that never returns to it//! - *one-shot handle*: a handle resumable at most once, continuing in place with no copy//! - *multi-shot handle*: a reference-counted handle resumable more than onceconst std = @import("std");const pretty = @import("pretty");const mp = @import("root.zig");const effect_mod = @This();const assert = std.debug.assert;/// The function type for the body of `handle`, `handleRaw`, `mask` and `finally`. A caller passes/// one as the code to run under a handler or a frame. The function uses the C calling convention,/// takes one pointer argument and returns one pointer result.pub const ActionFn = *const fn (?*anyopaque) callconv(.c) ?*anyopaque;/// The function type of a handler's result function. A handler that turns the body's final result/// into something else supplies one, as a counting handler turns each finished branch into 1. The/// function receives the handler's local state and the body's result, and its return value becomes/// the result of `handle`.pub const ResultFn = *const fn (?*anyopaque, ?*anyopaque) callconv(.c) ?*anyopaque;/// The function type that `finally` calls after its body returns. The function receives the local/// pointer given to `finally`.pub const ReleaseFn = *const fn (?*anyopaque) callconv(.c) void;/// The function type of a clause. A handler author writes one per operation. The function receives/// the resumption, the handler's local state and the operation's argument, and it returns the/// clause's result. Resuming the resumption makes the body's `perform` call return the value passed/// in. The resumption is null for `abort` and `never` clauses. For `tail_noop` and `tail` clauses,/// the return value becomes the result of `perform`. For the other kinds, the return value becomes/// the result of the `handle` call, or of the resume that last continued the body.pub const OpFn = *const fn (?*Resume, ?*anyopaque, ?*anyopaque) callconv(.c) ?*anyopaque;/// A pointer to a null-terminated array of C strings: the effect's name, then one name per/// operation in index order. A raw caller defines one per set of operations and uses it in tags and/// handler tables. The runtime matches a handler to an operation's effect by this pointer's/// address, so each effect needs exactly one array, and two arrays that hold the same strings are/// two different effects. The typed layer builds the array from the effect's name and one/// `name/operation` string per operation.pub const Effect = [*:null]const ?[*:0]const u8;/// Names one operation by its effect and its index. A raw caller defines one constant per operation/// and performs with its address. The `perform` call takes a pointer to one, and the tag's index/// picks the handler's clause.pub const Optag = extern struct {    /// The effect the operation belongs to, which the handler search matches by address. The    /// `optagName` function prints `<null>` for a null effect.    effect: ?Effect,    /// The operation's zero-based index. The index picks the handler's clause and the name at    /// position index plus one in the effect's array. An index of 8 or more fails the bounds check    /// in safe builds, and nothing checks that the clause at the index answers this operation.    opidx: c_long,};/// Lists the kinds of clause a handler table holds, as C `int` values in declaration order. A/// handler author picks one per operation to say how its clause may continue the body, and the/// runtime takes the cheapest way to run the clause from it. The kind decides whether the clause/// runs on the body's stack or after a stack switch, and whether its resumption lives on a stack or/// is allocated.pub const OperationKind = enum(c_int) {    /// Marks an empty slot, and handler tables are padded with it. Performing an operation whose    /// slot holds it runs nothing and returns null. The `on` helper rejects it at compile time.    null_op,    /// The clause passes the operation to the next enclosing handler of the same effect: it    /// performs the operation again inside a mask, and the mask covers this handler's effect. The    /// slot holds no function. The typed layer builds this clause with `forward()`, and `on`    /// rejects this kind.    forward,    /// The clause ends the body: control goes back to the handler, the body's stacklets are dropped    /// without running the rest of the body, and the clause runs with a null resumption. The    /// clause's result becomes the result of `handle`, and the handler's result function does not    /// run. A `finally` call inside the dropped body never calls its `finally_fun` function. A    /// typed clause of this kind returns the result type of `handle`.    abort,    /// Runs the same code path as `abort`.    never,    /// The clause runs in place on the body's stack as an ordinary call, with no stack switch. The    /// resumption lives on that stack, and a raw clause continues the body by returning    /// `resumeTail(...)`. A typed clause of this kind returns the operation's result. An operation    /// the clause performs itself starts its search at the body's innermost frame, so it can reach    /// this same handler.    tail_noop,    /// The clause runs in place on the body's stack, as with `tail_noop`. While the clause runs, an    /// extra frame makes the operations it performs skip this handler and every frame inside it, so    /// they reach the handlers outside.    tail,    /// The body suspends to the handler's prompt, and the clause runs on the handler's side with a    /// resumption kept in its own stack frame, so nothing is allocated. The clause resumes the body    /// at most once, before it returns, and it cannot release the resumption. A clause that returns    /// without resuming leaves the body's stacklet allocated.    scoped_once,    /// Runs the same code path as `multi`: the clause gets an allocated resumption that can resume    /// more than once. The tests use it for clauses that resume the body several times before they    /// return.    scoped,    /// The body suspends, and the clause gets an allocated resumption that it may keep after it    /// returns. The clause resumes it at most once, with `resumeFinal` or `resumeTail`, and    /// `resumeEffect` on it fails an assertion in safe builds. A kept resumption can be resumed    /// later under a different handler.    once,    /// The body suspends, and the clause gets an allocated resumption that can resume more than    /// once. The `resumeEffect` call keeps the resumption for another resume, and `resumeFinal`,    /// `resumeTail` or `resumeRelease` ends it.    multi,};/// One slot of a handler table: the operation's tag, its clause, and the kind of that clause. A raw/// handler table holds one per operation.pub const Operation = extern struct {    /// The kind of the slot's clause, which decides how `perform` runs the clause.    opkind: OperationKind,    /// The tag of the operation this slot answers. A `forward` slot performs the operation again    /// with it. The `perform` call selects the slot by the performed tag's index and does not    /// compare this field with that tag.    optag: ?*const Optag,    /// The slot's clause, null for `forward` and `null_op` slots.    opfun: ?OpFn,};/// The number of slots in every handler table, and so the most operations one effect can have. A/// caller sizes handler tables with it, and `EffectDefinition` checks each effect against it.pub const max_operations = 8;/// Describes one handler: its effect, an optional result function, and a table of eight slots that/// hold its clauses. A raw caller builds one per handler, usually as a constant, and passes its/// address to `handle`. The `handle` function keeps a pointer to it while the body runs, so it must/// outlive the call.pub const HandlerDef = extern struct {    /// The effect this handler answers. The runtime picks this handler for an operation when this    /// pointer equals the operation's effect.    effect: ?Effect,    /// An optional function applied to the body's result, with the handler's current local state,    /// when the body returns. A null value leaves the result unchanged. The function does not run    /// when an `abort` or `never` clause ends the body.    resultfun: ?ResultFn,    /// The eight slots that hold the handler's clauses: slot i answers the operation whose index is    /// i. Unused slots hold `null_op`.    operations: [max_operations]Operation,};/// Records an operation's argument type and result type. A caller writes one with `operation` for/// each entry of an `EffectDefinition` spec.pub const OperationSignature = struct {    /// The type `perform` passes to the clause, and `void` means the operation takes no argument.    Arg: type,    /// The type the clause gives back to `perform`.    Result: type,};/// Returns the signature of one operation with argument type `Arg` and result type `Result`, so a/// caller writes one per field of an `EffectDefinition` spec's `.operations`.pub fn operation(comptime Arg: type, comptime Result: type) OperationSignature {    return .{ .Arg = Arg, .Result = Result };}/// Returns a clause that answers an operation with `handler` under clause kind `kind`, so a caller/// writes one per operation in the clauses passed to a typed `handle`. A `kind` of `.forward` is a/// compile error that points to `forward()`, and `.null_op` is a compile error. For `tail_noop`,/// `tail`, `abort` and `never`, the handler takes `()`, `(arg)` or `(context, arg)`, and the empty/// form needs a `void` argument. For `scoped_once`, `scoped`, `once` and `multi`, the handler takes/// `(continuation)`, `(continuation, arg)` or `(continuation, context, arg)`, and the one-parameter/// form needs a `void` argument. The `tail_noop` and `tail` handlers return the operation's result,/// and the other kinds return the result type of `handle`. The handler must be a function with a/// declared return type, and a generic handler is a compile error.pub fn on(comptime kind: OperationKind, comptime handler: anytype) HandlerClause(@TypeOf(handler)) {    if (kind == .forward) {        @compileError("use forward() for forwarded operations");    }    if (kind == .null_op) {        @compileError("null operations are only used to terminate raw handler tables");    }    return .{ .kind = kind, .handler = handler };}/// Returns a clause that passes the operation to the next enclosing handler of the same effect, so/// an inner handler that answers some operations of an effect lets an outer handler answer the/// rest.pub fn forward() ForwardClause {    return .{};}fn HandlerClause(comptime Handler: type) type {    return struct {        kind: OperationKind,        handler: Handler,    };}const ForwardClause = struct {    kind: OperationKind = .forward,};const ContinueKind = enum {    regular,    final,    tail,};/// Returns a type for one effect so a caller declares an effect once in Zig, then performs and/// handles its operations with checked types and no pointer casts. The type is built from a spec/// with a `.name` string and an `.operations` struct literal whose fields are/// `operation(Arg, Result)` values. A spec with more than eight operations is a compile error, and/// so is an `.operations` value of any type other than a struct literal. The type builds the/// effect's name table and one operation tag per operation at compile time, with operation names of/// the form `name/operation`.pub fn EffectDefinition(comptime spec: anytype) type {    const Operations = @TypeOf(spec.operations);    const operations_info = switch (@typeInfo(Operations)) {        .@"struct" => |info| info,        else => @compileError("effect operations must be a struct literal"),    };    const operation_names = operations_info.field_names;    if (operation_names.len > max_operations) {        @compileError("effect definitions support at most eight operations");    }    return struct {        /// An enum with one tag per field of `.operations`, in declaration order. `optag`,        /// `Signature`, `Continuation`, `perform` and `performWithoutValue` take one to pick the        /// operation.        pub const Op: type = std.meta.FieldEnum(Operations);        /// The number of operations in the spec.        pub const operation_count = operation_names.len;        const Self = @This();        const names = blk: {            var result: [operation_count + 2:null]?[*:0]const u8 = undefined;            result[0] = spec.name;            for (operation_names, 0..) |operation_name, index| {                result[index + 1] = std.fmt.comptimePrint("{s}/{s}", .{ spec.name, operation_name });            }            result[operation_count + 1] = null;            break :blk result;        };        const tags = blk: {            var result: [operation_count]Optag = undefined;            for (operation_names, 0..) |_, index| {                result[index] = .{ .effect = rawEffect(), .opidx = index };            }            break :blk result;        };        /// Returns the effect's name table so raw calls that name this effect take it.        pub fn rawEffect() Effect {            return @ptrCast(&names);        }        /// Returns a pointer to `op_id`'s operation tag so raw calls that perform or handle one        /// operation take its tag. The tag's index is the operation's position in `.operations`.        pub fn optag(comptime op_id: Op) *const Optag {            return &tags[operationIndex(op_id)];        }        /// Returns the argument type and result type of `op_id` as the spec declares them.        pub fn Signature(comptime op_id: Op) OperationSignature {            return @field(spec.operations, @tagName(op_id));        }        /// Returns the continuation type that a clause of kind `kind` for `op_id` receives, so a        /// continuation clause names its parameter type with it. A resume of that continuation        /// passes a value of `op_id`'s result type into the body and returns `HandlerResult`.        /// `HandlerResult` must be the `Result` of the `handle` call, and the compiler rejects a        /// mismatch at the clause call site.        pub fn Continuation(comptime op_id: Op, comptime kind: OperationKind, comptime HandlerResult: type) type {            const sig = Signature(op_id);            return EffectContinuation(sig.Result, HandlerResult, kind);        }        /// Performs `op_id` with `arg` and returns the result the clause gives, so code under a        /// handler asks for an operation with a typed argument and gets a typed result. The        /// argument and the result travel in a small record on the performer's stack, and the        /// clause reaches that record by address. Whether the call suspends the body depends on the        /// clause's kind. With no enclosing handler for the effect, the runtime prints        /// `lib/mpeff: unhandled operation:` and the operation's name to standard error. An        /// operation with a non-`void` result then unwraps a null pointer, which panics in safe        /// builds, and an operation with a `void` result returns normally.        pub fn perform(comptime op_id: Op, arg: Signature(op_id).Arg) Signature(op_id).Result {            const sig = Signature(op_id);            const OpFrame = PerformFrame(sig.Arg, sig.Result);            var frame: OpFrame = .{};            writeSlot(sig.Arg, &frame.arg, arg);            return readSlot(sig.Result, performRaw(optag(op_id), &frame));        }        /// Performs `op_id` with no argument, for an operation whose `Arg` is `void`. Any other        /// operation is a compile error.        pub fn performWithoutValue(comptime op_id: Op) Signature(op_id).Result {            const sig = Signature(op_id);            if (sig.Arg != void) {                @compileError("performWithoutValue requires an operation with a void argument");            }            return Self.perform(op_id, {});        }        /// Runs `body(context)` on a new stacklet under a handler for this effect and returns a        /// `Result`, so a caller runs a body under this effect's typed clauses and gets back the        /// body's result, or the result a clause chose. The `context` parameter must be a pointer,        /// and every clause that asks for it receives it. The `clauses` argument is a struct        /// literal with one field per operation, each made by `on` or `forward()`, and a missing        /// field is a compile error. The handler table is built at compile time. The result is the        /// body's return value, the value an `abort` or `never` clause returns, or the value a        /// continuation clause returns. An error union passes through as `Result`.        pub fn handle(            comptime Result: type,            context: anytype,            comptime body: *const fn (@TypeOf(context)) Result,            comptime clauses: anytype,        ) Result {            const Context = @TypeOf(context);            requirePointer(Context, "effect handler context");            const Clauses = @TypeOf(clauses);            const Runner = struct {                const Env = struct {                    context: Context,                    result: Slot(Result) = .{},                };                fn start(arg: ?*anyopaque) callconv(.c) ?*anyopaque {                    const env: *Env = @ptrCast(@alignCast(arg.?));                    writeSlot(Result, &env.result, body(env.context));                    return slotPtr(Result, &env.result);                }                fn table() [max_operations]Operation {                    var entries = @as([max_operations]Operation, @splat(.{ .opkind = .null_op, .optag = null, .opfun = null }));                    inline for (operation_names) |operation_name| {                        if (!hasStructField(Clauses, operation_name)) {                            @compileError("missing handler clause for operation '" ++ operation_name ++ "'");                        }                        const op_id = std.meta.stringToEnum(Op, operation_name).?;                        entries[operationIndex(op_id)] = operationEntry(op_id);                    }                    return entries;                }                fn operationEntry(comptime op_id: Op) Operation {                    const clause = @field(clauses, @tagName(op_id));                    return .{                        .opkind = clause.kind,                        .optag = optag(op_id),                        .opfun = switch (clause.kind) {                            .forward => null,                            .null_op => null,                            else => thunk(op_id),                        },                    };                }                fn thunk(comptime op_id: Op) OpFn {                    return struct {                        fn call(raw_resume: ?*Resume, local: ?*anyopaque, raw_arg: ?*anyopaque) callconv(.c) ?*anyopaque {                            const env: *Env = @ptrCast(@alignCast(local.?));                            const sig = Signature(op_id);                            const OpFrame = PerformFrame(sig.Arg, sig.Result);                            const frame: *OpFrame = @ptrCast(@alignCast(raw_arg.?));                            const op_arg = readSlotValue(sig.Arg, &frame.arg);                            const clause = @field(clauses, @tagName(op_id));                            switch (clause.kind) {                                .tail_noop, .tail => {                                    const op_result = callTailHandler(clause.handler, env.context, op_arg);                                    writeSlot(sig.Result, &frame.result, op_result);                                    return slotPtr(sig.Result, &frame.result);                                },                                .abort, .never => {                                    const result = callTailHandler(clause.handler, env.context, op_arg);                                    writeSlot(Result, &env.result, result);                                    return slotPtr(Result, &env.result);                                },                                .scoped_once, .scoped, .once, .multi => {                                    const continuation: EffectContinuation(sig.Result, Result, clause.kind) = .{                                        .raw_resume = raw_resume.?,                                        .local = local,                                    };                                    const result = callContinuationHandler(clause.handler, continuation, env.context, op_arg);                                    writeSlot(Result, &env.result, result);                                    return slotPtr(Result, &env.result);                                },                                .forward, .null_op => unreachable,                            }                        }                    }.call;                }                const hdef = HandlerDef{                    .effect = rawEffect(),                    .resultfun = null,                    .operations = table(),                };            };            var env: Runner.Env = .{ .context = context };            return readSlot(Result, handleRaw(&Runner.hdef, &env, Runner.start, &env));        }        /// Runs a body that takes no arguments, the way `handle` does, so a caller whose body and        /// clauses need no context skips the pointer. A clause of the `(context, arg)` form        /// receives a pointer to an empty struct.        pub fn handleWithoutContext(            comptime Result: type,            comptime body: *const fn () Result,            comptime clauses: anytype,        ) Result {            const Context = struct {};            const Runner = struct {                fn start(_: *Context) Result {                    return body();                }            };            var context: Context = .{};            return Self.handle(Result, &context, Runner.start, clauses);        }        fn operationIndex(comptime op_id: Op) usize {            inline for (operation_names, 0..) |operation_name, index| {                if (std.mem.eql(u8, operation_name, @tagName(op_id))) return index;            }            unreachable;        }    };}/// Returns the continuation type for clause kinds `scoped_once`, `scoped`, `once` and `multi`, so a/// typed clause that may keep the body waiting receives one and resumes the body with the/// operation's result. Any other kind is a compile error. `ResumeValue` is the operation's result/// type, which `perform` returns in the body. `HandlerResult` is the result type of the `handle`/// call. A resume returns when the resumed body finishes, with the body's result, or when a later/// clause suspends the body and returns, with that clause's result. A `scoped_once` or `once`/// continuation is resumed at most once, and a `scoped` or `multi` continuation is resumed any/// number of times, with its last resume final. A `scoped`, `once` or `multi` continuation that/// gets no final resume and no release stays allocated.pub fn EffectContinuation(comptime ResumeValue: type, comptime HandlerResult: type, comptime kind: OperationKind) type {    comptime {        switch (kind) {            .scoped_once, .scoped, .once, .multi => {},            else => @compileError("effect continuations are only available for scoped_once, scoped, once, and multi operations"),        }    }    return struct {        /// The raw resumption for the suspended body.        raw_resume: *Resume,        /// The handler's local state, passed back with each resume. The typed `handle` sets it to        /// the address of its own environment, which holds the context and the result.        local: ?*anyopaque,        const Self = @This();        /// Resumes the body with `value` as the result of its `perform` and returns the handler's        /// result, for every resume but the last in a clause that resumes the body more than once,        /// as a search over both branches of a choice does. A `scoped` or `multi` continuation        /// stays usable for another resume. This call is the one resume for a `scoped_once`        /// continuation. A `once` continuation is a compile error here, and it uses        /// `continueFinalWith` or `continueTailWith`.        pub fn continueWith(self: Self, value: ResumeValue) HandlerResult {            if (kind == .once) {                @compileError("once continuations must use continueFinalWith or continueTailWith");            }            return self.continueInternal(.regular, value);        }        /// Resumes the body with `value` as the result of its `perform` and returns the handler's        /// result, so a clause makes its last resume of the body. This call is the continuation's        /// last resume: it frees an allocated continuation, which no caller uses again.        pub fn continueFinalWith(self: Self, value: ResumeValue) HandlerResult {            return self.continueInternal(.final, value);        }        /// Resumes the body with `value` as a tail resume and frees an allocated continuation, for        /// a clause whose last act is to resume the body, so no clause frame stays under the        /// resumed body. For every kind this type allows, control does not come back to the clause,        /// and the result goes to the call that entered or last resumed the handled body. The call        /// must be the clause's last action.        pub fn continueTailWith(self: Self, value: ResumeValue) HandlerResult {            return self.continueInternal(.tail, value);        }        /// Resumes the body with no value, for a `ResumeValue` of `void`. Any other `ResumeValue`        /// is a compile error.        pub fn continueWithoutValue(self: Self) HandlerResult {            if (ResumeValue != void) {                @compileError("continueWithoutValue requires ResumeValue to be void");            }            return self.continueWith({});        }        /// Makes the final resume with no value, for a `ResumeValue` of `void`. Any other        /// `ResumeValue` is a compile error.        pub fn continueFinalWithoutValue(self: Self) HandlerResult {            if (ResumeValue != void) {                @compileError("continueFinalWithoutValue requires ResumeValue to be void");            }            return self.continueFinalWith({});        }        /// Makes a tail resume with no value, for a `ResumeValue` of `void`. Any other        /// `ResumeValue` is a compile error.        pub fn continueTailWithoutValue(self: Self) HandlerResult {            if (ResumeValue != void) {                @compileError("continueTailWithoutValue requires ResumeValue to be void");            }            return self.continueTailWith({});        }        /// Ends the suspended body without running its rest, for a clause that will not continue        /// the body, such as a failing branch of a search. The body is resumed only to jump back to        /// its handler, and its stacklet is freed. For a `multi` or `scoped` continuation that        /// other references share, or that has resumed before, the call gives up one reference and        /// resumes nothing. The call is valid for `scoped`, `once` and `multi` continuations, and        /// any other kind is a compile error. A `finally` call inside the ended body never calls        /// its `finally_fun` function.        pub fn release(self: Self) void {            switch (kind) {                .scoped, .once, .multi => {},                else => @compileError("release is only valid for scoped, once, and multi continuations"),            }            resumeRelease(self.raw_resume);        }        fn continueInternal(self: Self, comptime continue_kind: ContinueKind, value: ResumeValue) HandlerResult {            var slot: Slot(ResumeValue) = .{};            writeSlot(ResumeValue, &slot, value);            const result = switch (continue_kind) {                .regular => resumeEffect(self.raw_resume, self.local, slotPtr(ResumeValue, &slot)),                .final => resumeFinal(self.raw_resume, self.local, slotPtr(ResumeValue, &slot)),                .tail => resumeTail(self.raw_resume, self.local, slotPtr(ResumeValue, &slot)),            };            return readSlot(HandlerResult, result);        }    };}/// Builds a handler description from an effect, an optional result function, and up to eight/// operations, so a raw caller builds its handler constants. The operations are copied in slice/// order, and the remaining slots hold `null_op`. Each operation's position in the slice must equal/// its tag's index, because `perform` picks the clause by that index. The function runs at compile/// time when its arguments are constants.pub fn handlerDef(effect: ?Effect, resultfun: ?ResultFn, operations: []const Operation) HandlerDef {    return .{        .effect = effect,        .resultfun = resultfun,        .operations = operationTable(operations),    };}/// Returns an eight-slot table with `entries` in order and `null_op` in the remaining slots./// `handlerDef` calls it to fill the table of each handler description it builds. More than eight/// entries fails an assertion in safe builds.pub fn operationTable(entries: []const Operation) [max_operations]Operation {    assert(entries.len <= max_operations);    var table = @as([max_operations]Operation, @splat(.{ .opkind = .null_op, .optag = null, .opfun = null }));    for (entries, 0..) |entry, index| table[index] = entry;    return table;}fn PerformFrame(comptime Arg: type, comptime Result: type) type {    return struct {        arg: Slot(Arg) = .{},        result: Slot(Result) = .{},    };}fn Slot(comptime T: type) type {    return if (T == void) struct {} else struct {        value: T = undefined,    };}fn writeSlot(comptime T: type, slot: *Slot(T), value: T) void {    if (comptime T != void) {        slot.value = value;    }}fn readSlotValue(comptime T: type, slot: *Slot(T)) T {    if (comptime T != void) {        return slot.value;    }    return {};}fn slotPtr(comptime T: type, slot: *Slot(T)) ?*anyopaque {    if (comptime T != void) {        return @ptrCast(slot);    }    return null;}fn readSlot(comptime T: type, ptr: ?*anyopaque) T {    if (comptime T != void) {        const slot: *Slot(T) = @ptrCast(@alignCast(ptr.?));        return slot.value;    }    return {};}fn callTailHandler(handler: anytype, context: anytype, arg: anytype) handlerReturnType(@TypeOf(handler)) {    const Arg = @TypeOf(arg);    const count = comptime handlerParamCount(@TypeOf(handler));    if (count == 0) {        if (Arg != void) @compileError("handler without parameters requires a void operation argument");        return handler();    }    if (count == 1) return handler(arg);    if (count == 2) return handler(context, arg);    @compileError("tail handlers must accept (), (arg), or (context, arg)");}fn callContinuationHandler(    handler: anytype,    continuation: anytype,    context: anytype,    arg: anytype,) handlerReturnType(@TypeOf(handler)) {    const Arg = @TypeOf(arg);    const count = comptime handlerParamCount(@TypeOf(handler));    if (count == 1) {        if (Arg != void) @compileError("continuation-only handlers require a void operation argument");        return handler(continuation);    }    if (count == 2) return handler(continuation, arg);    if (count == 3) return handler(continuation, context, arg);    @compileError("continuation handlers must accept (continuation), (continuation, arg), or (continuation, context, arg)");}fn handlerReturnType(comptime Handler: type) type {    return handlerFnInfo(Handler).return_type orelse @compileError("generic effect handlers are not supported");}fn handlerParamCount(comptime Handler: type) usize {    return handlerFnInfo(Handler).param_types.len;}fn handlerFnInfo(comptime Handler: type) std.builtin.Type.Fn {    const Fn = switch (@typeInfo(Handler)) {        .pointer => |ptr| ptr.child,        .@"fn" => Handler,        else => @compileError("effect handlers must be functions"),    };    return switch (@typeInfo(Fn)) {        .@"fn" => |info| info,        else => @compileError("effect handlers must be functions"),    };}fn requirePointer(comptime T: type, comptime name: []const u8) void {    switch (@typeInfo(T)) {        .pointer => {},        else => @compileError(name ++ " must be a pointer"),    }}fn hasStructField(comptime T: type, comptime name: []const u8) bool {    const info = switch (@typeInfo(T)) {        .@"struct" => |struct_info| struct_info,        else => return false,    };    inline for (info.field_names) |field_name| {        if (std.mem.eql(u8, field_name, name)) return true;    }    return false;}const Frame = extern struct {    effect: ?Effect,    parent: ?*Frame,};const HandleFrame = struct {    frame: Frame,    prompt: *mp.Prompt,    hdef: *const HandlerDef,    local: ?*anyopaque,};const UnderFrame = struct {    frame: Frame,    under: ?Effect,};const MaskFrame = struct {    frame: Frame,    mask: ?Effect,    from: usize,};const FinallyFrame = struct {    frame: Frame,    fun: ReleaseFn,    local: ?*anyopaque,};/// Lists the four kinds of effect resumption as C `int` values. The runtime records in each/// resumption where it lives and how it resumes, so one set of resume functions serves every kind/// of clause.pub const ResumptionKind = enum(c_int) {    /// The clause runs on the body's stack, and the resumption points at the handler's local state.    /// Resuming it stores the new local state and returns the value. `tail_noop` and `tail` clauses    /// get this kind.    inplace,    /// The resumption lives in a stack frame of the clause and wraps the suspended prompt's    /// one-shot handle.    scoped_once,    /// The resumption is allocated and wraps the suspended prompt's one-shot handle. Its final    /// resume frees it.    once,    /// The resumption is allocated and wraps the suspended prompt's multi-shot handle. Each resume    /// before the final resume adds a reference first.    multi,};const ResumePayload = extern union {    plocal: *?*anyopaque,    continuation: *mp.Resume,};/// Holds an effect resumption as a kind and a payload. A raw clause receives a pointer to one and/// passes it to `resumeEffect`, `resumeFinal`, `resumeTail` or `resumeRelease`.pub const Resume = extern struct {    /// Records which of the four kinds this resumption is.    kind: ResumptionKind,    /// Holds a pointer to the handler's local state for an `inplace` resumption. For the other    /// kinds, the payload holds the prompt resumption for the suspended body.    payload: ResumePayload,};const PerformEnv = extern struct {    rkind: ResumptionKind,    opfun: OpFn,    local: ?*anyopaque,    oparg: ?*anyopaque,};const ResumeEnv = extern struct {    local: ?*anyopaque,    result: ?*anyopaque,    unwind: bool,};const HandleStartEnv = extern struct {    hdef: *const HandlerDef,    local: ?*anyopaque,    body: ActionFn,    arg: ?*anyopaque,};const under_names = [_:null]?[*:0]const u8{ "mpe_frame_under", null };const mask_names = [_:null]?[*:0]const u8{ "mpe_frame_mask", null };const finally_names = [_:null]?[*:0]const u8{ "mpe_frame_finally", null };const unwind_names = [_:null]?[*:0]const u8{ "mpe_unwind", "mpe_unwind/mpe_unwind", null };const under_effect: Effect = @ptrCast(&under_names);const mask_effect: Effect = @ptrCast(&mask_names);const finally_effect: Effect = @ptrCast(&finally_names);const unwind_effect: Effect = @ptrCast(&unwind_names);const unwind_optag: Optag = .{ .effect = unwind_effect, .opidx = 0 };threadlocal var frame_top: ?*Frame = null;threadlocal var find_cache: ?*HandleFrame = null;/// Returns the effect's name, the first string of its name table, for a diagnostic to print the/// name of an effect. The call returns `<null>` for a null effect or a null first entry.pub fn effectName(effect: ?Effect) [*:0]const u8 {    const names = effect orelse return "<null>";    return names[0] orelse "<null>";}/// Returns the string at position index plus one in the operation's effect table, for a diagnostic/// to print the name of an operation as the unhandled-operation message does. The call returns/// `<null>` for a null tag, a null effect or a null entry.pub fn optagName(optag: ?*const Optag) [*:0]const u8 {    const tag = optag orelse return "<null>";    const names = tag.effect orelse return "<null>";    const index: usize = @intCast(tag.opidx + 1);    return names[index] orelse "<null>";}/// The typed `handle` and raw callers run a body under a handler table with this function. The call/// creates a prompt with a new stacklet, pushes a handler frame for `hdef` with local state/// `local`, and runs `body(arg)` there. The call returns the body's result, passed through the/// handler's result function when it has one. When an `abort` or `never` clause ends the body, the/// call returns that clause's result. `hdef` must stay valid until the call returns. Handler frames/// live in a per-thread list, so a handler answers operations performed on its own thread.pub fn handleRaw(hdef: *const HandlerDef, local: ?*anyopaque, body: ActionFn, arg: ?*anyopaque) ?*anyopaque {    var env: HandleStartEnv = .{        .hdef = hdef,        .local = local,        .body = body,        .arg = arg,    };    return mp.prompt(handleStart, &env);}/// Raw callers use this name. The function does exactly what `handleRaw` does.pub fn handle(hdef: *const HandlerDef, local: ?*anyopaque, body: ActionFn, arg: ?*anyopaque) ?*anyopaque {    return handleRaw(hdef, local, body, arg);}/// The typed `perform` and raw code call this function to perform an operation. The call finds the/// innermost handler for the tag's effect and runs its clause at the tag's index, in the way the/// clause's kind requires. The search walks the thread's frames outward and passes over one more/// handler of the operation's effect for each mask of that effect that applies. Inside a `tail`/// clause, the search jumps from the clause's frame to the frames outside the clause's own handler./// The last handler found is kept and reused for the same effect until a frame is pushed or popped./// The call returns the clause's result for kinds that run in place, and the value a resume passed/// for kinds that suspend the body. With no handler for the effect, the call prints/// `lib/mpeff: unhandled operation:` and the operation's name to standard error and returns null.pub fn performRaw(optag: *const Optag, arg: ?*anyopaque) ?*anyopaque {    const h = find(optag) orelse {        @branchHint(.unlikely);        return unhandledOperation(optag);    };    const op_index: usize = @intCast(optag.opidx);    const op = &h.hdef.operations[op_index];    return performAt(h, op, arg);}/// Raw callers use this name. The function does exactly what `performRaw` does.pub fn perform(optag: *const Optag, arg: ?*anyopaque) ?*anyopaque {    return performRaw(optag, arg);}/// Resumes the body: its `perform` returns `arg`, and `local` becomes the handler's new local/// state. A clause that resumes the body more than once, as one that tries each branch of a choice/// does, calls it for every resume but the last. When the body finishes and the handler has no/// result function, the call returns the body's result. When the body finishes and the handler has/// a result function, the call returns the value that the function puts in place of the body's/// result. When a later clause suspends the body and returns, the call returns that clause's/// result. For a `multi` resumption, the call adds a reference first, so the resumption stays/// usable. For a `scoped_once` resumption, this call is the one resume. An `inplace` or `once`/// resumption fails an assertion in safe builds.pub fn resumeEffect(resume_ptr: *Resume, local: ?*anyopaque, arg: ?*anyopaque) ?*anyopaque {    return resumeInternal(false, resume_ptr, local, arg, false);}/// Resumes the body the way `resumeEffect` does, as the resumption's last resume. A clause ends/// every allocated resumption with this function, `resumeTail` or `resumeRelease`, and makes its/// last resume of a `multi` resumption with it. For `once` and `multi` resumptions, the call frees/// the allocated record first, and the caller never uses the resumption again.pub fn resumeFinal(resume_ptr: *Resume, local: ?*anyopaque, arg: ?*anyopaque) ?*anyopaque {    return resumeInternal(true, resume_ptr, local, arg, false);}/// Resumes the body for a clause whose last act is to resume, such as a state or reader clause, so/// no clause frame stays under the resumed body. For an `inplace` resumption, the call stores/// `local` as the handler's local state and returns `arg`, which the clause returns to `perform`./// For the other kinds, the call resumes the body as a tail resume, so control does not come back/// to the clause. The call frees an allocated resumption first. The call must be the clause's last/// action, with its value returned.pub fn resumeTail(resume_ptr: *Resume, local: ?*anyopaque, arg: ?*anyopaque) ?*anyopaque {    if (resume_ptr.kind == .inplace) {        @branchHint(.likely);        resume_ptr.payload.plocal.* = local;        return arg;    }    var renv: ResumeEnv = .{        .local = local,        .result = arg,        .unwind = false,    };    if (resume_ptr.kind == .scoped_once) {        @branchHint(.likely);        return mp.resumeTailPrompt(resume_ptr.payload.continuation, &renv);    }    const mpr = resume_ptr.payload.continuation;    mp.processAllocator().destroy(resume_ptr);    return mp.resumeTailPrompt(mpr, &renv);}/// Ends an effect resumption for a clause that will not continue the body, such as a failing branch/// of a choice. The call does nothing for null, which an `abort` clause receives. For a `once`/// resumption, and for a `multi` resumption that holds the last reference and never resumed, the/// call resumes the body with an unwind flag. The unwind makes the body's `perform` jump back to/// the handler, and the runtime drops the body's stacklet without running the rest of the body. For/// a `multi` resumption with other references or earlier resumes, the call frees the record and/// gives up one reference. A `scoped_once` or `inplace` resumption fails an assertion in safe/// builds. A `finally` call inside the dropped body never calls its `finally_fun` function.pub fn resumeRelease(resume_ptr: ?*Resume) void {    const r = resume_ptr orelse return;    if (r.kind == .once) {        resumeUnwind(r);        return;    }    assert(r.kind == .multi);    const mpr = r.payload.continuation;    if (mp.resumeShouldUnwind(mpr) != 0) {        resumeUnwind(r);    } else {        mp.processAllocator().destroy(r);        mp.resumeDrop(mpr);    }}/// Runs `fun(arg)` under a frame for `effect`, for code that must reach an outer handler of an/// effect past the innermost one. While `fun` runs, a search for a handler of `effect` passes over/// one more handler of that effect. The frame applies only when the search has at least `from`/// handlers of that effect still to pass over when it reaches the frame, so a `from` of 0 always/// applies. The call returns what `fun` returns, and pops the frame on return.pub fn mask(effect: ?Effect, from: usize, fun: ActionFn, arg: ?*anyopaque) ?*anyopaque {    var f: MaskFrame = .{        .frame = .{ .effect = mask_effect, .parent = null },        .mask = effect,        .from = from,    };    pushFrame(&f.frame);    defer popFrame(&f.frame);    return fun(arg);}/// Runs `fun(arg)`, then calls `finally_fun(local)`, and returns `fun`'s result, so code can run a/// second function after its body returns. `finally_fun` runs only when `fun` returns normally, so/// a body ended by an `abort` or `never` clause, or by `resumeRelease`, skips it.pub fn finally(local: ?*anyopaque, finally_fun: ReleaseFn, fun: ActionFn, arg: ?*anyopaque) ?*anyopaque {    var f: FinallyFrame = .{        .frame = .{ .effect = finally_effect, .parent = null },        .fun = finally_fun,        .local = local,    };    pushFrame(&f.frame);    const result = fun(arg);    popFrame(&f.frame);    f.fun(f.local);    return result;}fn pushFrame(f: *Frame) void {    f.parent = frame_top;    assert(f.parent != f);    frame_top = f;    clearFindCache();}fn popFrame(f: *Frame) void {    assert(frame_top == f);    frame_top = f.parent;    clearFindCache();}fn clearFindCache() void {    find_cache = null;}fn handleStart(prompt: *mp.Prompt, earg: ?*anyopaque) callconv(.c) ?*anyopaque {    const env: *HandleStartEnv = @ptrCast(@alignCast(earg.?));    var h: HandleFrame = .{        .frame = .{ .effect = env.hdef.effect, .parent = null },        .prompt = prompt,        .hdef = env.hdef,        .local = env.local,    };    pushFrame(&h.frame);    var result = env.body(env.arg);    popFrame(&h.frame);    if (h.hdef.resultfun) |resultfun| {        result = resultfun(h.local, result);    }    return result;}fn find(optag: *const Optag) ?*HandleFrame {    var f = frame_top;    const operation_effect = optag.effect;    var mask_level: usize = 0;    if (find_cache) |cached| {        if (cached.frame.effect == operation_effect) {            @branchHint(.likely);            return cached;        }    }    while (f) |frame| {        @branchHint(.likely);        const eff = frame.effect;        if (eff == operation_effect) {            @branchHint(.likely);            if (mask_level == 0) return cacheFind(@fieldParentPtr("frame", frame));            mask_level -= 1;        } else if (eff == under_effect) {            @branchHint(.unlikely);            const under: *UnderFrame = @fieldParentPtr("frame", frame);            var cursor = frame.parent;            while (cursor) |candidate| {                if (candidate.effect == under.under) break;                cursor = candidate.parent;            }            f = cursor orelse return null;        } else if (eff == mask_effect) {            @branchHint(.unlikely);            const masked: *MaskFrame = @fieldParentPtr("frame", frame);            if (masked.mask == operation_effect and masked.from <= mask_level) {                mask_level += 1;            }        }        f = f.?.parent;    }    return null;}fn cacheFind(h: *HandleFrame) *HandleFrame {    find_cache = h;    return h;}fn performAt(h: *HandleFrame, op: *const Operation, arg: ?*anyopaque) ?*anyopaque {    if (op.opkind == .tail_noop) {        @branchHint(.likely);        var r: Resume = .{            .kind = .inplace,            .payload = .{ .plocal = &h.local },        };        return op.opfun.?(&r, h.local, arg);    }    if (op.opkind == .tail) {        @branchHint(.likely);        return performUnder(h, op, arg);    }    if (op.opkind == .scoped_once) return performYieldTo(.scoped_once, h, op, arg);    if (op.opkind == .once) return performYieldTo(.once, h, op, arg);    if (op.opkind == .never) return unwindTo(h, op, arg);    if (op.opkind == .abort) return performYieldToAbort(h, op, arg);    if (op.opkind == .forward) return performForward(h, op, arg);    if (op.opkind == .null_op) return null;    return performYieldTo(.multi, h, op, arg);}fn performForward(h: *HandleFrame, op: *const Operation, arg: ?*anyopaque) ?*anyopaque {    const optag = op.optag orelse return null;    var f: MaskFrame = .{        .frame = .{ .effect = mask_effect, .parent = null },        .mask = h.frame.effect,        .from = 0,    };    pushFrame(&f.frame);    defer popFrame(&f.frame);    return perform(optag, arg);}fn performUnder(h: *HandleFrame, op: *const Operation, arg: ?*anyopaque) ?*anyopaque {    const saved_find_cache = find_cache;    var f: UnderFrame = .{        .frame = .{ .effect = under_effect, .parent = null },        .under = h.frame.effect,    };    f.frame.parent = frame_top;    assert(f.frame.parent != &f.frame);    frame_top = &f.frame;    clearFindCache();    defer {        assert(frame_top == &f.frame);        frame_top = f.frame.parent;        find_cache = saved_find_cache;    }    var r: Resume = .{        .kind = .inplace,        .payload = .{ .plocal = &h.local },    };    return op.opfun.?(&r, h.local, arg);}fn performYieldTo(rkind: ResumptionKind, h: *HandleFrame, op: *const Operation, arg: ?*anyopaque) ?*anyopaque {    const resume_top = frame_top;    frame_top = h.frame.parent;    clearFindCache();    var penv: PerformEnv = .{        .rkind = rkind,        .opfun = op.opfun.?,        .local = h.local,        .oparg = arg,    };    const result = mp.yieldPrompt(h.prompt, performOpClause, &penv);    const renv: *ResumeEnv = @ptrCast(@alignCast(result.?));    h.local = renv.local;    assert(frame_top != &h.frame);    h.frame.parent = frame_top;    frame_top = resume_top;    clearFindCache();    if (renv.unwind) return unwindTo(h, &unwind_operation, renv.result);    return renv.result;}fn performOpClause(mpr: *mp.Resume, earg: ?*anyopaque) callconv(.c) ?*anyopaque {    const env: *PerformEnv = @ptrCast(@alignCast(earg.?));    var stack_resume: Resume = undefined;    const r = if (env.rkind == .scoped_once) blk: {        @branchHint(.likely);        break :blk &stack_resume;    } else allocateResume();    r.kind = env.rkind;    r.payload.continuation = if (env.rkind == .multi) mp.resumeMulti(mpr) else mpr;    return env.opfun(r, env.local, env.oparg);}fn performYieldToAbort(h: *HandleFrame, op: *const Operation, arg: ?*anyopaque) ?*anyopaque {    frame_top = h.frame.parent;    clearFindCache();    var env: PerformEnv = .{        .rkind = .scoped_once,        .opfun = op.opfun.?,        .local = h.local,        .oparg = arg,    };    return mp.yieldPrompt(h.prompt, performOpClauseAbort, &env);}fn performOpClauseAbort(mpr: *mp.Resume, earg: ?*anyopaque) callconv(.c) ?*anyopaque {    const env: PerformEnv = (@as(*PerformEnv, @ptrCast(@alignCast(earg.?)))).*;    mp.resumeDrop(mpr);    return env.opfun(null, env.local, env.oparg);}fn unwindTo(h: *HandleFrame, op: *const Operation, arg: ?*anyopaque) ?*anyopaque {    return performYieldToAbort(h, op, arg);}fn handleOpUnwind(_: ?*Resume, _: ?*anyopaque, arg: ?*anyopaque) callconv(.c) ?*anyopaque {    return arg;}const unwind_operation: Operation = .{    .opkind = .abort,    .optag = &unwind_optag,    .opfun = handleOpUnwind,};fn resumeInternal(final: bool, resume_ptr: *Resume, local: ?*anyopaque, arg: ?*anyopaque, unwind: bool) ?*anyopaque {    assert(@backingInt(resume_ptr.kind) >= @backingInt(ResumptionKind.scoped_once));    var renv: ResumeEnv = .{        .local = local,        .result = arg,        .unwind = unwind,    };    switch (resume_ptr.kind) {        .scoped_once => {            @branchHint(.likely);            return mp.resumePrompt(resume_ptr.payload.continuation, &renv);        },        .once => {            assert(final);            const mpr = resume_ptr.payload.continuation;            mp.processAllocator().destroy(resume_ptr);            return mp.resumePrompt(mpr, &renv);        },        .multi => {            const mpr = resume_ptr.payload.continuation;            if (final) {                mp.processAllocator().destroy(resume_ptr);            } else {                _ = mp.resumeDup(mpr);            }            return mp.resumePrompt(mpr, &renv);        },        .inplace => unreachable,    }}fn resumeUnwind(resume_ptr: *Resume) void {    _ = resumeInternal(true, resume_ptr, null, null, true);}fn allocateResume() *Resume {    return mp.processAllocator().create(Resume) catch std.debug.panic("lib/mpeff: out of memory", .{});}fn unhandledOperation(optag: *const Optag) ?*anyopaque {    @branchHint(.cold);    pretty.diagnostic.writeStderrText("lib/mpeff: unhandled operation: {s}\n", .{optagName(optag)});    return null;}fn intToPtr(value: isize) ?*anyopaque {    if (value == 0) return null;    return @ptrFromInt(@as(usize, @intCast(value)));}fn ptrToInt(value: ?*anyopaque) isize {    return if (value) |ptr| @intCast(@intFromPtr(ptr)) else 0;}const reader_names = [_:null]?[*:0]const u8{ "reader", "reader/ask", null };const reader_effect: effect_mod.Effect = @ptrCast(&reader_names);const reader_ask_tag: effect_mod.Optag = .{ .effect = reader_effect, .opidx = 0 };fn readerAsk() isize {    return ptrToInt(effect_mod.perform(&reader_ask_tag, null));}fn stackUse(kb: usize) isize {    if (kb <= 4) return readerAsk();    var page: [4096]u8 = undefined;    page[4095] = @truncate(kb);    std.mem.doNotOptimizeAway(&page);    return stackUse(kb - 4);}fn handleReaderAsk(continuation: ?*effect_mod.Resume, local: ?*anyopaque, _: ?*anyopaque) callconv(.c) ?*anyopaque {    return effect_mod.resumeTail(continuation.?, local, local);}fn handleGeneralReaderAsk(continuation: ?*effect_mod.Resume, local: ?*anyopaque, _: ?*anyopaque) callconv(.c) ?*anyopaque {    _ = local;    return effect_mod.resumeTail(continuation.?, intToPtr(42), intToPtr(42));}const reader_def = effect_mod.handlerDef(reader_effect, null, &.{    .{ .opkind = .tail_noop, .optag = &reader_ask_tag, .opfun = handleReaderAsk },});const general_reader_def = effect_mod.handlerDef(reader_effect, null, &.{    .{ .opkind = .scoped_once, .optag = &reader_ask_tag, .opfun = handleGeneralReaderAsk },});const forward_reader_def = effect_mod.handlerDef(reader_effect, null, &.{    .{ .opkind = .forward, .optag = &reader_ask_tag, .opfun = null },});fn handleUnderReaderAsk(continuation: ?*effect_mod.Resume, local: ?*anyopaque, _: ?*anyopaque) callconv(.c) ?*anyopaque {    return effect_mod.resumeTail(continuation.?, local, intToPtr(readerAsk() + 1));}const under_reader_def = effect_mod.handlerDef(reader_effect, null, &.{    .{ .opkind = .tail, .optag = &reader_ask_tag, .opfun = handleUnderReaderAsk },});fn readerHandle(action: effect_mod.ActionFn, init: isize, arg: ?*anyopaque) ?*anyopaque {    return effect_mod.handle(&reader_def, intToPtr(init), action, arg);}fn generalReaderHandle(action: effect_mod.ActionFn, init: isize, arg: ?*anyopaque) ?*anyopaque {    return effect_mod.handle(&general_reader_def, intToPtr(init), action, arg);}fn forwardingReaderHandle(action: effect_mod.ActionFn, init: isize, arg: ?*anyopaque) ?*anyopaque {    return effect_mod.handle(&forward_reader_def, intToPtr(init), action, arg);}fn underReaderHandle(action: effect_mod.ActionFn, init: isize, arg: ?*anyopaque) ?*anyopaque {    return effect_mod.handle(&under_reader_def, intToPtr(init), action, arg);}fn readerAction(_: ?*anyopaque) callconv(.c) ?*anyopaque {    return intToPtr(stackUse(64) + readerAsk());}test "reader handles tail and scoped-once ask operations" {    try std.testing.expectEqual(@as(isize, 84), ptrToInt(readerHandle(readerAction, 42, null)));    try std.testing.expectEqual(@as(isize, 84), ptrToInt(generalReaderHandle(readerAction, 99, null)));}fn askOnce(_: ?*anyopaque) callconv(.c) ?*anyopaque {    return intToPtr(readerAsk());}fn innerForwardingReader(arg: ?*anyopaque) callconv(.c) ?*anyopaque {    return forwardingReaderHandle(askOnce, 99, arg);}test "forward operation skips the current handler and reaches the enclosing handler" {    try std.testing.expectEqual(@as(isize, 7), ptrToInt(readerHandle(innerForwardingReader, 7, null)));}fn innerUnderReader(arg: ?*anyopaque) callconv(.c) ?*anyopaque {    return underReaderHandle(askOnce, 99, arg);}test "tail operation runs under-frame effects below the current handler" {    try std.testing.expectEqual(@as(isize, 8), ptrToInt(readerHandle(innerUnderReader, 7, null)));}const state_names = [_:null]?[*:0]const u8{ "state", "state/get", "state/set", null };const state_effect: effect_mod.Effect = @ptrCast(&state_names);const state_get_tag: effect_mod.Optag = .{ .effect = state_effect, .opidx = 0 };const state_set_tag: effect_mod.Optag = .{ .effect = state_effect, .opidx = 1 };fn rawStateGet() isize {    return ptrToInt(effect_mod.perform(&state_get_tag, null));}fn rawStateSet(value: isize) void {    _ = effect_mod.perform(&state_set_tag, intToPtr(value));}fn handleStateGet(continuation: ?*effect_mod.Resume, local: ?*anyopaque, _: ?*anyopaque) callconv(.c) ?*anyopaque {    return effect_mod.resumeTail(continuation.?, local, local);}fn handleStateSet(continuation: ?*effect_mod.Resume, _: ?*anyopaque, arg: ?*anyopaque) callconv(.c) ?*anyopaque {    return effect_mod.resumeTail(continuation.?, arg, null);}fn stateDef(comptime kind: effect_mod.OperationKind) effect_mod.HandlerDef {    return effect_mod.handlerDef(state_effect, null, &.{        .{ .opkind = kind, .optag = &state_get_tag, .opfun = handleStateGet },        .{ .opkind = kind, .optag = &state_set_tag, .opfun = handleStateSet },    });}const tail_state_def = stateDef(.tail_noop);const under_state_def = stateDef(.tail);const once_state_def = stateDef(.scoped_once);const allocated_once_state_def = stateDef(.once);const multi_state_def = stateDef(.multi);fn stateHandle(def: *const effect_mod.HandlerDef, action: effect_mod.ActionFn, init: isize) ?*anyopaque {    return effect_mod.handle(def, intToPtr(init), action, null);}fn counterAction(_: ?*anyopaque) callconv(.c) ?*anyopaque {    var count: isize = 0;    while (true) {        const current = rawStateGet();        if (current <= 0) break;        rawStateSet(current - 1);        count += 1;    }    return intToPtr(count);}test "state counter works across tail, under, scoped-once, and multi handlers" {    try std.testing.expectEqual(@as(isize, 1000), ptrToInt(stateHandle(&tail_state_def, counterAction, 1000)));    try std.testing.expectEqual(@as(isize, 1000), ptrToInt(stateHandle(&under_state_def, counterAction, 1000)));    try std.testing.expectEqual(@as(isize, 1000), ptrToInt(stateHandle(&once_state_def, counterAction, 1000)));    try std.testing.expectEqual(@as(isize, 1000), ptrToInt(stateHandle(&allocated_once_state_def, counterAction, 1000)));    try std.testing.expectEqual(@as(isize, 100), ptrToInt(stateHandle(&multi_state_def, counterAction, 100)));}test "state counter covers upstream debug workload sizes" {    const count: isize = 100_100;    try std.testing.expectEqual(count, ptrToInt(stateHandle(&tail_state_def, counterAction, count)));    try std.testing.expectEqual(count, ptrToInt(stateHandle(&under_state_def, counterAction, count)));    try std.testing.expectEqual(count, ptrToInt(stateHandle(&once_state_def, counterAction, count)));    try std.testing.expectEqual(@divTrunc(count, 10), ptrToInt(stateHandle(&multi_state_def, counterAction, @divTrunc(count, 10))));}fn reader1(arg: ?*anyopaque) callconv(.c) ?*anyopaque {    return readerHandle(counterAction, 1, arg);}fn reader2(arg: ?*anyopaque) callconv(.c) ?*anyopaque {    return readerHandle(reader1, 2, arg);}fn reader3(arg: ?*anyopaque) callconv(.c) ?*anyopaque {    return readerHandle(reader2, 3, arg);}fn reader4(arg: ?*anyopaque) callconv(.c) ?*anyopaque {    return readerHandle(reader3, 4, arg);}fn reader5(arg: ?*anyopaque) callconv(.c) ?*anyopaque {    return readerHandle(reader4, 5, arg);}fn reader6(arg: ?*anyopaque) callconv(.c) ?*anyopaque {    return readerHandle(reader5, 6, arg);}fn reader7(arg: ?*anyopaque) callconv(.c) ?*anyopaque {    return readerHandle(reader6, 7, arg);}fn reader8(arg: ?*anyopaque) callconv(.c) ?*anyopaque {    return readerHandle(reader7, 8, arg);}fn reader9(arg: ?*anyopaque) callconv(.c) ?*anyopaque {    return readerHandle(reader8, 9, arg);}fn reader10(arg: ?*anyopaque) callconv(.c) ?*anyopaque {    return readerHandle(reader9, 10, arg);}test "state handler composes under nested reader handlers" {    try std.testing.expectEqual(@as(isize, 250), ptrToInt(stateHandle(&tail_state_def, reader3, 250)));    try std.testing.expectEqual(@as(isize, 250), ptrToInt(stateHandle(&once_state_def, reader3, 250)));}test "state handler composes under ten nested reader handlers" {    try std.testing.expectEqual(@as(isize, 100), ptrToInt(stateHandle(&tail_state_def, reader10, 100)));    try std.testing.expectEqual(@as(isize, 100), ptrToInt(stateHandle(&once_state_def, reader10, 100)));}fn finallyBody(arg: ?*anyopaque) callconv(.c) ?*anyopaque {    return arg;}fn markReleased(local: ?*anyopaque) callconv(.c) void {    const released: *bool = @ptrCast(@alignCast(local.?));    released.* = true;}test "finally frame runs release function after normal return" {    var released = false;    try std.testing.expectEqual(@as(isize, 42), ptrToInt(effect_mod.finally(&released, markReleased, finallyBody, intToPtr(42))));    try std.testing.expect(released);}const StateFn = struct {    env: ?*anyopaque,    applyFn: *const fn (?*anyopaque, isize) isize,    fn apply(self: StateFn, state: isize) isize {        return self.applyFn(self.env, state);    }};fn stateFnToPtr(function: StateFn) ?*anyopaque {    const box = std.testing.allocator.create(StateFn) catch @panic("unable to allocate state function");    box.* = function;    return @ptrCast(box);}fn stateFnFromPtr(value: ?*anyopaque) StateFn {    const box: *StateFn = @ptrCast(@alignCast(value.?));    const function = box.*;    std.testing.allocator.destroy(box);    return function;}fn mstateResultValue(env: ?*anyopaque, _: isize) isize {    return ptrToInt(env);}fn handleMstateResult(_: ?*anyopaque, arg: ?*anyopaque) callconv(.c) ?*anyopaque {    return stateFnToPtr(.{ .env = arg, .applyFn = mstateResultValue });}fn mstateGetValue(env: ?*anyopaque, state: isize) isize {    const continuation: *effect_mod.Resume = @ptrCast(@alignCast(env.?));    const function = stateFnFromPtr(effect_mod.resumeFinal(continuation, null, intToPtr(state)));    return function.apply(state);}fn handleMstateGet(continuation: ?*effect_mod.Resume, _: ?*anyopaque, _: ?*anyopaque) callconv(.c) ?*anyopaque {    return stateFnToPtr(.{        .env = @ptrCast(continuation.?),        .applyFn = mstateGetValue,    });}const PutEnv = struct {    new_state: isize,    continuation: *effect_mod.Resume,};fn mstatePutValue(env_ptr: ?*anyopaque, _: isize) isize {    const env: *PutEnv = @ptrCast(@alignCast(env_ptr.?));    const new_state = env.new_state;    const continuation = env.continuation;    std.testing.allocator.destroy(env);    const function = stateFnFromPtr(effect_mod.resumeFinal(continuation, null, null));    return function.apply(new_state);}fn handleMstateSet(continuation: ?*effect_mod.Resume, _: ?*anyopaque, arg: ?*anyopaque) callconv(.c) ?*anyopaque {    const env = std.testing.allocator.create(PutEnv) catch @panic("unable to allocate monadic state closure");    env.* = .{        .new_state = ptrToInt(arg),        .continuation = continuation.?,    };    return stateFnToPtr(.{        .env = @ptrCast(env),        .applyFn = mstatePutValue,    });}const mstate_def = effect_mod.handlerDef(state_effect, handleMstateResult, &.{    .{ .opkind = .once, .optag = &state_get_tag, .opfun = handleMstateGet },    .{ .opkind = .once, .optag = &state_set_tag, .opfun = handleMstateSet },});fn mstateHandle(action: effect_mod.ActionFn, init: isize, arg: ?*anyopaque) ?*anyopaque {    const function = stateFnFromPtr(effect_mod.handle(&mstate_def, null, action, arg));    return intToPtr(function.apply(init));}test "monadic state handler defers state threading through once resumptions" {    try std.testing.expectEqual(@as(isize, 200), ptrToInt(mstateHandle(counterAction, 200, null)));}const exit_names = [_:null]?[*:0]const u8{ "exit", "exit/capture", null };const exit_effect: effect_mod.Effect = @ptrCast(&exit_names);const exit_capture_tag: effect_mod.Optag = .{ .effect = exit_effect, .opidx = 0 };fn exitCapture() ?*anyopaque {    return effect_mod.perform(&exit_capture_tag, null);}fn handleExitCapture(continuation: ?*effect_mod.Resume, _: ?*anyopaque, _: ?*anyopaque) callconv(.c) ?*anyopaque {    return @ptrCast(continuation.?);}const exit_def = effect_mod.handlerDef(exit_effect, null, &.{    .{ .opkind = .once, .optag = &exit_capture_tag, .opfun = handleExitCapture },});fn exitHandle(action: effect_mod.ActionFn, arg: ?*anyopaque) ?*anyopaque {    return effect_mod.handle(&exit_def, null, action, arg);}fn rehandleBody(_: ?*anyopaque) callconv(.c) ?*anyopaque {    const first = readerAsk();    _ = exitCapture();    const second = readerAsk();    return intToPtr(first + second);}fn withExitHandle(arg: ?*anyopaque) callconv(.c) ?*anyopaque {    return exitHandle(rehandleBody, arg);}fn withResume(arg: ?*anyopaque) callconv(.c) ?*anyopaque {    const continuation: *effect_mod.Resume = @ptrCast(@alignCast(arg.?));    return effect_mod.resumeFinal(continuation, null, null);}test "captured effect continuation can be resumed under a different handler" {    const captured = readerHandle(withExitHandle, 1, null);    try std.testing.expectEqual(@as(isize, 3), ptrToInt(readerHandle(withResume, 2, captured)));}const amb_names = [_:null]?[*:0]const u8{ "amb", "amb/flip", null };const amb_effect: effect_mod.Effect = @ptrCast(&amb_names);const amb_flip_tag: effect_mod.Optag = .{ .effect = amb_effect, .opidx = 0 };fn ambFlip() bool {    return ptrToInt(effect_mod.perform(&amb_flip_tag, null)) != 0;}fn rawAmbBody(_: ?*anyopaque) callconv(.c) ?*anyopaque {    return intToPtr(if (ambFlip()) 10 else 1);}fn handleAmbFlip(continuation: ?*effect_mod.Resume, local: ?*anyopaque, _: ?*anyopaque) callconv(.c) ?*anyopaque {    const false_branch = ptrToInt(effect_mod.resumeEffect(continuation.?, local, intToPtr(0)));    const true_branch = ptrToInt(effect_mod.resumeFinal(continuation.?, local, intToPtr(1)));    return intToPtr(false_branch + true_branch);}const amb_def = effect_mod.handlerDef(amb_effect, null, &.{    .{ .opkind = .scoped, .optag = &amb_flip_tag, .opfun = handleAmbFlip },});test "scoped multi-shot handler can resume both branches" {    try std.testing.expectEqual(@as(isize, 11), ptrToInt(effect_mod.handle(&amb_def, null, rawAmbBody, null)));}fn handleAmbCountResult(_: ?*anyopaque, _: ?*anyopaque) callconv(.c) ?*anyopaque {    return intToPtr(1);}fn handleAmbCountFlip(continuation: ?*effect_mod.Resume, local: ?*anyopaque, _: ?*anyopaque) callconv(.c) ?*anyopaque {    const false_branch = ptrToInt(effect_mod.resumeEffect(continuation.?, local, intToPtr(0)));    const true_branch = ptrToInt(effect_mod.resumeFinal(continuation.?, local, intToPtr(1)));    return intToPtr(false_branch + true_branch);}const amb_count_def = effect_mod.handlerDef(amb_effect, handleAmbCountResult, &.{    .{ .opkind = .scoped, .optag = &amb_flip_tag, .opfun = handleAmbCountFlip },});fn ambCountHandle(action: effect_mod.ActionFn, arg: ?*anyopaque) isize {    return ptrToInt(effect_mod.handle(&amb_count_def, null, action, arg));}fn xorAction(_: ?*anyopaque) callconv(.c) ?*anyopaque {    const x = ambFlip();    const y = ambFlip();    return intToPtr(if (x != y) 1 else 0);}test "ambiguity handler enumerates both boolean branches" {    try std.testing.expectEqual(@as(isize, 4), ambCountHandle(xorAction, null));}const choice_names = [_:null]?[*:0]const u8{ "choice", "choice/choose", "choice/fail", null };const choice_effect: effect_mod.Effect = @ptrCast(&choice_names);const choice_choose_tag: effect_mod.Optag = .{ .effect = choice_effect, .opidx = 0 };const choice_fail_tag: effect_mod.Optag = .{ .effect = choice_effect, .opidx = 1 };fn choiceChoose(max: isize) isize {    return ptrToInt(effect_mod.perform(&choice_choose_tag, intToPtr(max)));}fn choiceFail() void {    _ = effect_mod.perform(&choice_fail_tag, null);}fn choiceBody(_: ?*anyopaque) callconv(.c) ?*anyopaque {    const chosen = choiceChoose(4);    if (@rem(chosen, 2) == 0) return intToPtr(chosen);    choiceFail();    return intToPtr(99);}fn handleChoiceChoose(continuation: ?*effect_mod.Resume, local: ?*anyopaque, arg: ?*anyopaque) callconv(.c) ?*anyopaque {    const max = ptrToInt(arg);    var total: isize = 0;    var i: isize = 1;    while (i <= max) : (i += 1) {        const result = if (i == max)            effect_mod.resumeFinal(continuation.?, local, intToPtr(i))        else            effect_mod.resumeEffect(continuation.?, local, intToPtr(i));        total += ptrToInt(result);    }    return intToPtr(total);}fn handleChoiceFail(continuation: ?*effect_mod.Resume, _: ?*anyopaque, _: ?*anyopaque) callconv(.c) ?*anyopaque {    effect_mod.resumeRelease(continuation);    return intToPtr(0);}const choice_def = effect_mod.handlerDef(choice_effect, null, &.{    .{ .opkind = .scoped, .optag = &choice_choose_tag, .opfun = handleChoiceChoose },    .{ .opkind = .abort, .optag = &choice_fail_tag, .opfun = handleChoiceFail },});test "choice handler combines resumed branches and aborts failed branches" {    try std.testing.expectEqual(@as(isize, 6), ptrToInt(effect_mod.handle(&choice_def, null, choiceBody, null)));}fn ambStateXor() bool {    const x = ambFlip();    const y = ambFlip();    return x != y;}fn ambStateFoo(_: ?*anyopaque) callconv(.c) ?*anyopaque {    const p = ambFlip();    const current = rawStateGet();    rawStateSet(current + 1);    const result = if (current > 0 and p) ambStateXor() else false;    return intToPtr(if (result) 1 else 0);}fn stateInsideAmb(arg: ?*anyopaque) callconv(.c) ?*anyopaque {    return stateHandle(&tail_state_def, ambStateFoo, ptrToInt(arg));}fn ambInsideState(arg: ?*anyopaque) callconv(.c) ?*anyopaque {    return intToPtr(ambCountHandle(ambStateFoo, arg));}test "state and ambiguity handlers compose in both nesting orders" {    try std.testing.expectEqual(@as(isize, 2), ambCountHandle(stateInsideAmb, intToPtr(0)));    try std.testing.expectEqual(@as(isize, 5), ptrToInt(stateHandle(&tail_state_def, ambInsideState, 0)));}fn choiceCountBody(_: ?*anyopaque) callconv(.c) ?*anyopaque {    return intToPtr(1);}fn queenSafe(queen: isize, queens: []const isize) bool {    var diag: isize = 1;    var i = queens.len;    while (i > 0) {        i -= 1;        const previous = queens[i];        if (queen == previous or queen == previous + diag or queen == previous - diag) return false;        diag += 1;    }    return true;}fn findQueens(n: isize, col: usize, queens: *[12]isize) bool {    if (col == 0) return true;    if (!findQueens(n, col - 1, queens)) return false;    const queen = choiceChoose(n);    const placed = queens[0 .. col - 1];    if (!queenSafe(queen, placed)) {        choiceFail();        return false;    }    queens[col - 1] = queen;    return true;}fn nqueensBody(arg: ?*anyopaque) callconv(.c) ?*anyopaque {    const n: usize = @intCast(ptrToInt(arg));    var queens = @as([12]isize, @splat(0));    return intToPtr(if (findQueens(@intCast(n), n, &queens)) 1 else 0);}test "choice handler counts n-queens solutions" {    try std.testing.expectEqual(@as(isize, 92), ptrToInt(effect_mod.handle(&choice_def, null, nqueensBody, intToPtr(8))));}const yield_names = [_:null]?[*:0]const u8{ "yield", "yield/yield", null };const yield_effect: effect_mod.Effect = @ptrCast(&yield_names);const yield_yield_tag: effect_mod.Optag = .{ .effect = yield_effect, .opidx = 0 };fn yieldValue(value: isize) void {    _ = effect_mod.perform(&yield_yield_tag, intToPtr(value));}fn handleYieldResult(local: ?*anyopaque, _: ?*anyopaque) callconv(.c) ?*anyopaque {    return local;}fn handleYieldYield(continuation: ?*effect_mod.Resume, local: ?*anyopaque, _: ?*anyopaque) callconv(.c) ?*anyopaque {    return effect_mod.resumeTail(continuation.?, intToPtr(ptrToInt(local) + 1), local);}const yield_def = effect_mod.handlerDef(yield_effect, handleYieldResult, &.{    .{ .opkind = .tail_noop, .optag = &yield_yield_tag, .opfun = handleYieldYield },});fn yieldHandle(action: effect_mod.ActionFn, init: isize, arg: ?*anyopaque) ?*anyopaque {    return effect_mod.handle(&yield_def, intToPtr(init), action, arg);}fn handleChoiceIgnoreResult(_: ?*anyopaque, arg: ?*anyopaque) callconv(.c) ?*anyopaque {    return arg;}fn handleChoiceChooseIgnore(continuation: ?*effect_mod.Resume, local: ?*anyopaque, arg: ?*anyopaque) callconv(.c) ?*anyopaque {    const max = ptrToInt(arg);    if (max <= 0) return intToPtr(0);    var i: isize = 1;    while (i <= max) : (i += 1) {        _ = if (i == max)            effect_mod.resumeFinal(continuation.?, local, intToPtr(i))        else            effect_mod.resumeEffect(continuation.?, local, intToPtr(i));    }    return intToPtr(0);}const choice_ignore_def = effect_mod.handlerDef(choice_effect, handleChoiceIgnoreResult, &.{    .{ .opkind = .scoped, .optag = &choice_choose_tag, .opfun = handleChoiceChooseIgnore },    .{ .opkind = .abort, .optag = &choice_fail_tag, .opfun = handleChoiceFail },});fn choiceIgnoreHandle(action: effect_mod.ActionFn, arg: ?*anyopaque) ?*anyopaque {    return effect_mod.handle(&choice_ignore_def, null, action, arg);}fn triples(n: isize, sum: isize) void {    const x = choiceChoose(n);    const y = choiceChoose(x - 1);    const z = choiceChoose(y - 1);    if (x + y + z == sum) {        yieldValue(x);    } else {        choiceFail();    }}fn triplesBody(arg: ?*anyopaque) callconv(.c) ?*anyopaque {    const payload = ptrToInt(arg);    const n = @divTrunc(payload, 1 << 16);    const sum = @mod(payload, 1 << 16);    triples(n, sum);    return intToPtr(0);}fn chooseTriples(arg: ?*anyopaque) callconv(.c) ?*anyopaque {    return choiceIgnoreHandle(triplesBody, arg);}test "choice and yield handlers count matching triples" {    const payload = 100 * (1 << 16) + 27;    try std.testing.expectEqual(@as(isize, 48), ptrToInt(yieldHandle(chooseTriples, 0, intToPtr(payload))));}const Reader = mp.EffectDefinition(.{    .name = "typed-reader",    .operations = .{        .ask = mp.operation(void, isize),    },});const ReaderContext = struct {    value: isize,};fn askReader(context: *ReaderContext, _: void) isize {    return context.value;}fn readerBody(_: *ReaderContext) isize {    return Reader.performWithoutValue(.ask) + Reader.performWithoutValue(.ask);}test "typed effect handler answers tail operations without pointer casts" {    var context: ReaderContext = .{ .value = 21 };    try std.testing.expectEqual(@as(isize, 42), Reader.handle(isize, &context, readerBody, .{        .ask = mp.on(.tail_noop, askReader),    }));}test "typed effect lookup follows handler stack changes" {    var first: ReaderContext = .{ .value = 3 };    var second: ReaderContext = .{ .value = 11 };    try std.testing.expectEqual(@as(isize, 6), Reader.handle(isize, &first, readerBody, .{        .ask = mp.on(.tail_noop, askReader),    }));    try std.testing.expectEqual(@as(isize, 22), Reader.handle(isize, &second, readerBody, .{        .ask = mp.on(.tail_noop, askReader),    }));}fn typedAskOnce(_: *ReaderContext) isize {    return Reader.performWithoutValue(.ask);}fn typedInnerForward(_: *ReaderContext) isize {    var inner: ReaderContext = .{ .value = 99 };    return Reader.handle(isize, &inner, typedAskOnce, .{        .ask = mp.forward(),    });}test "typed effect handler can forward an operation to an enclosing handler" {    var outer: ReaderContext = .{ .value = 7 };    try std.testing.expectEqual(@as(isize, 7), Reader.handle(isize, &outer, typedInnerForward, .{        .ask = mp.on(.tail_noop, askReader),    }));}const State = mp.EffectDefinition(.{    .name = "typed-state",    .operations = .{        .get = mp.operation(void, isize),        .set = mp.operation(isize, void),    },});const StateContext = struct {    current: isize,};fn stateGet(context: *StateContext, _: void) isize {    return context.current;}fn stateSet(context: *StateContext, value: isize) void {    context.current = value;}fn stateCounter(_: *StateContext) isize {    var count: isize = 0;    while (true) {        const current = State.performWithoutValue(.get);        if (current <= 0) break;        State.perform(.set, current - 1);        count += 1;    }    return count;}test "typed effect handler supports mutable Zig context" {    var context: StateContext = .{ .current = 100 };    const count = State.handle(isize, &context, stateCounter, .{        .get = mp.on(.tail_noop, stateGet),        .set = mp.on(.tail_noop, stateSet),    });    try std.testing.expectEqual(@as(isize, 100), count);    try std.testing.expectEqual(@as(isize, 0), context.current);}const Amb = mp.EffectDefinition(.{    .name = "typed-amb",    .operations = .{        .flip = mp.operation(void, bool),    },});const AmbContext = struct {};fn ambBody(_: *AmbContext) isize {    return if (Amb.performWithoutValue(.flip)) 10 else 1;}fn handleFlip(    continuation: Amb.Continuation(.flip, .scoped, isize),    _: *AmbContext,    _: void,) isize {    const false_branch = continuation.continueWith(false);    const true_branch = continuation.continueFinalWith(true);    return false_branch + true_branch;}test "typed effect handler exposes scoped multi-shot continuations" {    var context: AmbContext = .{};    try std.testing.expectEqual(@as(isize, 11), Amb.handle(isize, &context, ambBody, .{        .flip = mp.on(.scoped, handleFlip),    }));}const Once = mp.EffectDefinition(.{    .name = "typed-once",    .operations = .{        .bump = mp.operation(isize, isize),    },});const OnceContext = struct {    seen: isize = 0,};fn onceBody(_: *OnceContext) isize {    return Once.perform(.bump, 41) + 1;}fn handleBump(    continuation: Once.Continuation(.bump, .once, isize),    context: *OnceContext,    value: isize,) isize {    context.seen = value;    return continuation.continueFinalWith(value + 1);}test "typed once handlers use final continuations" {    var context: OnceContext = .{};    try std.testing.expectEqual(@as(isize, 43), Once.handle(isize, &context, onceBody, .{        .bump = mp.on(.once, handleBump),    }));    try std.testing.expectEqual(@as(isize, 41), context.seen);}const Pure = mp.EffectDefinition(.{    .name = "typed-pure",    .operations = .{        .answer = mp.operation(void, isize),        .double = mp.operation(isize, isize),    },});fn pureAnswer() isize {    return 21;}fn pureDouble(value: isize) isize {    return value * 2;}fn pureBody() isize {    return Pure.performWithoutValue(.answer) + Pure.perform(.double, 10);}test "typed effect handler supports no-context bodies and clauses" {    try std.testing.expectEqual(@as(isize, 41), Pure.handleWithoutContext(isize, pureBody, .{        .answer = mp.on(.tail_noop, pureAnswer),        .double = mp.on(.tail_noop, pureDouble),    }));}const FallibleReader = mp.EffectDefinition(.{    .name = "typed-fallible-reader",    .operations = .{        .ask = mp.operation(void, error{Unavailable}!isize),    },});const FallibleReaderContext = struct {    available: bool,    value: isize,};fn fallibleAsk(context: *FallibleReaderContext, _: void) error{Unavailable}!isize {    if (!context.available) return error.Unavailable;    return context.value;}fn fallibleBody(_: *FallibleReaderContext) error{Unavailable}!isize {    const value = try FallibleReader.performWithoutValue(.ask);    return value + 1;}test "typed effect handler preserves Zig error unions" {    var unavailable: FallibleReaderContext = .{ .available = false, .value = 0 };    try std.testing.expectError(error.Unavailable, FallibleReader.handle(error{Unavailable}!isize, &unavailable, fallibleBody, .{        .ask = mp.on(.tail_noop, fallibleAsk),    }));    var available: FallibleReaderContext = .{ .available = true, .value = 41 };    try std.testing.expectEqual(@as(isize, 42), try FallibleReader.handle(error{Unavailable}!isize, &available, fallibleBody, .{        .ask = mp.on(.tail_noop, fallibleAsk),    }));}const Choice = mp.EffectDefinition(.{    .name = "typed-choice",    .operations = .{        .flip = mp.operation(void, bool),    },});fn chooseBody() isize {    return if (Choice.performWithoutValue(.flip)) 30 else 4;}fn chooseBoth(continuation: Choice.Continuation(.flip, .scoped, isize)) isize {    const false_branch = continuation.continueWith(false);    const true_branch = continuation.continueFinalWith(true);    return false_branch + true_branch;}test "typed scoped continuations can omit unused context and argument" {    try std.testing.expectEqual(@as(isize, 34), Choice.handleWithoutContext(isize, chooseBody, .{        .flip = mp.on(.scoped, chooseBoth),    }));}

Source: lib/mprompt/src/root.zig:55

zig
pub const effect = @import("effect.zig");

Audit

Definitions28
Public names28
Members26
Version26.7.0
Revisiondaab053ee433