tiny.mprompt.EffectDefinition
Defined in effect.
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.
Source
Source: lib/mprompt/src/effect.zig:249
zig
/// 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; } };}Source: lib/mprompt/src/root.zig:63
zig
pub const EffectDefinition = effect.EffectDefinition;Complete call list
14 direct calls.
tiny.mprompt.effect.EffectContinuation[function] atlib/mprompt/src/effect.zig:479lib.mprompt.src.effect.PerformFrame[function] — private source atlib/mprompt/src/effect.zig:603in nearest public ownertiny.mprompt.effectlib.mprompt.src.effect.Slot[function] — private source atlib/mprompt/src/effect.zig:610in nearest public ownertiny.mprompt.effectlib.mprompt.src.effect.callContinuationHandler[function] — private source atlib/mprompt/src/effect.zig:656in nearest public ownertiny.mprompt.effectlib.mprompt.src.effect.callTailHandler[function] — private source atlib/mprompt/src/effect.zig:644in nearest public ownertiny.mprompt.effecttiny.mprompt.effect.forward[function] atlib/mprompt/src/effect.zig:221tiny.mprompt.effect.handleRaw[function] atlib/mprompt/src/effect.zig:831lib.mprompt.src.effect.hasStructField[function] — private source atlib/mprompt/src/effect.zig:700in nearest public ownertiny.mprompt.effecttiny.mprompt.effect.performRaw[function] atlib/mprompt/src/effect.zig:855lib.mprompt.src.effect.readSlot[function] — private source atlib/mprompt/src/effect.zig:636in nearest public ownertiny.mprompt.effectlib.mprompt.src.effect.readSlotValue[function] — private source atlib/mprompt/src/effect.zig:622in nearest public ownertiny.mprompt.effectlib.mprompt.src.effect.requirePointer[function] — private source atlib/mprompt/src/effect.zig:693in nearest public ownertiny.mprompt.effectlib.mprompt.src.effect.slotPtr[function] — private source atlib/mprompt/src/effect.zig:629in nearest public ownertiny.mprompt.effectlib.mprompt.src.effect.writeSlot[function] — private source atlib/mprompt/src/effect.zig:616in nearest public ownertiny.mprompt.effect
Audit
| Definitions | 1 |
|---|---|
| Public names | 2 |
| Members | 0 |
| Version | 26.7.0 |
| Revision | daab053ee433 |