Complete Example
const std = @import("std");
pub fn main() !void {
const StartingState = Witness(Example.Exit, Example.AOrB(Example.State1, Example.State2));
var gst: GlobalState = .init;
sw: switch (StartingState.transition(&gst)) {
.Current => |function| {
continue :sw function(&gst);
},
.Exit => {},
}
}
pub const GlobalState = struct {
counter: u64,
prng: std.Random.DefaultPrng,
pub const init: GlobalState = .{
.counter = 0,
.prng = .init(123),
};
};
pub fn Witness(End: type, Current: type) type {
if (Current == End) {
return struct {
pub const name = Current.name;
pub fn transition(gst: *GlobalState) TransitionResult {
_ = gst;
std.debug.print("end: {s} \n", .{Current.name});
return .Exit;
}
};
} else {
return struct {
pub const name = Current.name;
pub fn transition(gst: *GlobalState) TransitionResult {
switch (Current.transitionInt(gst)) {
inline else => |wit, tag| {
_ = tag;
std.debug.print("{s} -> {s}\n", .{ Current.name, @TypeOf(wit).name });
return .{ .Current = @TypeOf(wit).transition };
},
}
}
};
}
}
pub const Example = struct {
pub const Exit = union(enum) {
pub const name = getName(Example, Exit);
};
pub const State1 = union(enum) {
exit: Witness(Exit, Exit),
toAOrB: Witness(Exit, AOrB(State1, State2)),
pub const name = getName(Example, State1);
pub fn transitionInt(gst: *GlobalState) @This() {
gst.counter += 1;
if (gst.counter >= 10) {
return .exit;
}
return .toAOrB;
}
};
pub const State2 = union(enum) {
exit: Witness(Exit, Exit),
toAOrB: Witness(Exit, AOrB(State1, State2)),
pub const name = getName(Example, State2);
pub fn transitionInt(gst: *GlobalState) @This() {
gst.counter += 1;
if (gst.counter >= 10) {
return .exit;
}
return .toAOrB;
}
};
pub fn AOrB(comptime A: type, comptime B: type) type {
return union(enum) {
toA: Witness(Exit, A),
toB: Witness(Exit, B),
toSelf: Witness(Exit, AOrB(A, B)),
pub const name = getComposedName(Example, AOrB, .{ A, B });
const Self = @This();
pub fn transitionInt(gst: *GlobalState) @This() {
const random = gst.prng.random();
if (random.intRangeLessThan(usize, 0, 3) != 0) {
return .toSelf;
}
if (random.boolean()) {
return .toA;
}
return .toB;
}
};
}
};
// Utilities:
pub fn getName(comptime Container: type, comptime decl: anytype) []const u8 {
comptime {
for (std.meta.declarations(Container)) |possible_decl| {
const name = possible_decl.name;
const decl_value = @field(Container, name);
if (@TypeOf(decl_value) == @TypeOf(decl) and decl_value == decl) {
return name;
}
} else unreachable;
}
}
pub const TransitionResult = union(enum) {
Exit: void,
Current: *const fn (gst: *GlobalState) TransitionResult,
};
pub fn getComposedName(comptime Container: type, comptime decl: anytype, comptime args: anytype) []const u8 {
comptime {
var composed_name: []const u8 = "";
composed_name = composed_name ++ getName(Container, decl) ++ "(";
for (&args, 0..) |arg, i| {
if (i > 0) {
composed_name = composed_name ++ ", ";
}
composed_name = composed_name ++ arg.name;
}
composed_name = composed_name ++ ")";
return composed_name;
}
}
