Last active
June 21, 2020 23:12
-
-
Save adrusi/f16edf3930a2973ce2d546f2acf23e9b to your computer and use it in GitHub Desktop.
Async generators in Zig 0.6.0
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const std = @import("std"); | |
const expectEqual = std.testing.expectEqual; | |
const expect = std.testing.expect; | |
const tuples = @import("tuples.zig"); | |
const Tuple = tuples.Tuple; | |
const tuple = tuples.tuple; | |
const comptime_utils = @import("comptime_utils.zig"); | |
pub fn Generator(comptime Ctx: type) type { | |
comptime { | |
const ctx_tinfo = @typeInfo(Ctx); | |
if (ctx_tinfo != .Struct) { | |
@compileError("Generator specification must be a struct"); | |
} | |
if (!@hasDecl(Ctx, "generate")) { | |
@compileError("Generator specification must have a 'generate' declaration"); | |
} | |
const gen_fn = Ctx.generate; | |
const fn_tinfo = @typeInfo(@TypeOf(gen_fn)); | |
if (fn_tinfo != .Fn) { | |
@compileError("Generator specification: 'generate' must be a function"); | |
} | |
if (fn_tinfo.Fn.args.len != 2) { | |
@compileError("Generator specification: 'generate' must accept exactly two parameters"); | |
} | |
const arg1_tinfo = @typeInfo(fn_tinfo.Fn.args[0].arg_type.?); | |
const arg2_tinfo = @typeInfo(fn_tinfo.Fn.args[1].arg_type.?); | |
const ret_tinfo = @typeInfo(fn_tinfo.Fn.return_type.?); | |
if (arg1_tinfo != .Pointer) { | |
@compileError("Generator specification: first parameter of 'generate' must be a pointer"); | |
} | |
if (arg1_tinfo.Pointer.child != Ctx) { | |
@compileError("Generator specification: first parameter of 'generate' must be a pointer to the generator specification struct"); | |
} | |
if (ret_tinfo != .ErrorUnion) { | |
@compileError("Generator specification: 'generate' must return an error union with void payload"); | |
} | |
if (ret_tinfo.ErrorUnion.payload != void) { | |
@compileError("Generator specification: 'generate' must return an error union with void payload"); | |
} | |
if (arg2_tinfo != .Pointer) { | |
@compileError("Generator specification: second parameter of 'generate' must be a pointer"); | |
} | |
const arg2_child = arg2_tinfo.Pointer.child; | |
if (@typeInfo(arg2_child) != .Union) { | |
@compileError("Generator specification: second parameter of 'generate' must be a pointer to a Scheduler"); | |
} | |
if (!@hasDecl(arg2_child, "ThisIsAScheduler") or arg2_child.ThisIsAScheduler != SchedulerTypeMarker) { | |
@compileError("Generator specification: second parameter of 'generate' must be a pointer to a Scheduler"); | |
} | |
const Awaitables = arg2_child.Awaitables; | |
return struct { | |
pub const Args = Ctx; | |
pub const Error = ret_tinfo.ErrorUnion.error_set; | |
pub const Item = arg2_child.Item; | |
state: enum { start, alive, end, post_end }, | |
err: ?Error, | |
gen_frame: @Frame(gen_fn), | |
awaiter_frame: @Frame(returnValueAwaiter), | |
ctx: Ctx, | |
Scheduler: Scheduler(Item, Awaitables), | |
pub fn init(ctx: Ctx) @This() { | |
return .{ | |
.state = .start, | |
.err = null, | |
.gen_frame = undefined, | |
.awaiter_frame = undefined, | |
.ctx = ctx, | |
.Scheduler = undefined, | |
}; | |
} | |
pub fn next(self: *@This()) Error!?Item { | |
switch (self.state) { | |
.start => self.awaiter_frame = async self.returnValueAwaiter(), | |
.alive => resume self.gen_frame, | |
.end => { | |
nosuspend await self.awaiter_frame; | |
self.state = .post_end; | |
if (self.err) |err| return err; | |
return null; | |
}, | |
.post_end => return null, | |
} | |
// generate has suspended | |
while (true) { | |
// await all frames that generate calls Scheduler.await_ on until generate yields a value. | |
switch (self.Scheduler) { | |
.yielded => |item| return item, | |
.awaited => |awaitspec| { | |
inline for (Awaitables.types) |_, i| { | |
if (awaitspec.result.get(i)) |result| result.* = await awaitspec.frame.get(i); | |
} | |
resume awaitspec.frame_to_resume; | |
}, | |
} | |
} | |
} | |
fn returnValueAwaiter(self: *@This()) void { | |
self.state = .alive; | |
defer self.state = .end; | |
self.gen_frame = async gen_fn(&self.ctx, &self.Scheduler); | |
await self.gen_frame catch |err| { | |
self.err = err; | |
}; | |
} | |
}; | |
} | |
} | |
const SchedulerTypeMarker = @OpaqueType(); | |
pub fn Scheduler(comptime _Item: type, comptime _Awaitables: type) type { | |
comptime { | |
var Frames: type = undefined; | |
var Results: type = undefined; | |
var frame_types: [_Awaitables.types.len]type = undefined; | |
for (_Awaitables.types) |AwaitableType, i| { | |
frame_types[i] = anyframe->AwaitableType; | |
} | |
// compiler bug workaround | |
Results = switch (_Awaitables.types.len) { | |
0 => tuples.Tuple0(), | |
1 => tuples.Tuple1(?*_Awaitables.types[0]), | |
2 => tuples.Tuple2(?*_Awaitables.types[0], ?*_Awaitables.types[1]), | |
3 => tuples.Tuple3(?*_Awaitables.types[0], ?*_Awaitables.types[1], ?*_Awaitables.types[2]), | |
4 => tuples.Tuple4(?*_Awaitables.types[0], ?*_Awaitables.types[1], ?*_Awaitables.types[2], ?*_Awaitables.types[3]), | |
5 => tuples.Tuple5(?*_Awaitables.types[0], ?*_Awaitables.types[1], ?*_Awaitables.types[2], ?*_Awaitables.types[3], ?*_Awaitables.types[4]), | |
else => @compileError("Scheduler: cannot declare more than 5 awaitable types"), | |
}; | |
Frames = Tuple(frame_types); | |
var empty_results: Results = undefined; | |
inline for (Results.types) |ResultType, i| { | |
empty_results.set(i, @as(ResultType, null)); | |
} | |
return union(enum) { | |
const ThisIsAScheduler = SchedulerTypeMarker; | |
const Item = _Item; | |
const Awaitables = _Awaitables; | |
yielded: Item, | |
awaited: struct { result: Results, frame: Frames, frame_to_resume: anyframe }, | |
pub fn yield(self: *@This(), item: Item) void { | |
suspend { | |
self.* = .{ .yielded = item }; | |
} | |
} | |
fn await_Aux(comptime Frame: type) struct { Return: type, index: usize } { | |
comptime { | |
var Result: type = undefined; | |
var awaitable_index: usize = undefined; | |
const frame_tinfo = @typeInfo(Frame); | |
if (frame_tinfo != .AnyFrame or frame_tinfo.AnyFrame.child == null) { | |
comptime_utils.compileErrorFmt("Scheduler.await_: expected anyframe->? in first argument, got: {}", .{@typeName(Frame)}); | |
} | |
Result = frame_tinfo.AnyFrame.child.?; | |
var valid_result_type = false; | |
for (Awaitables.types) |Awaitable, i| { | |
if (Result == Awaitable) { | |
valid_result_type = true; | |
awaitable_index = i; | |
break; | |
} | |
} | |
if (!valid_result_type) { | |
comptime_utils.compileErrorFmt("Must declare awaited type to Scheduler: {}", .{@typeName(Result)}); | |
} | |
return .{ .Return = Result, .index = awaitable_index }; | |
} | |
} | |
pub fn await_(self: *@This(), frame: var) await_Aux(@TypeOf(frame)).Return { | |
comptime const type_info = await_Aux(@TypeOf(frame)); | |
var result: type_info.Return = undefined; | |
suspend { | |
self.* = .{ | |
.awaited = .{ | |
.result = empty_results, | |
.frame = undefined, | |
.frame_to_resume = @frame(), | |
}, | |
}; | |
const opt_result_ptr: ?*type_info.Return = &result; | |
const frame_as_anyframe: anyframe->type_info.Return = frame; | |
self.awaited.result.set(type_info.index, opt_result_ptr); | |
self.awaited.frame.set(type_info.index, frame_as_anyframe); | |
} | |
return result; | |
} | |
}; | |
} | |
} | |
var global_frame: ?anyframe = null; | |
fn get5() u64 { | |
suspend { | |
global_frame = @frame(); | |
} | |
return 5; | |
} | |
const FiveIter = Generator(struct { | |
pub fn generate(_: *@This(), scheduler: *Scheduler(u64, Tuple(.{u64}))) !void { | |
const five = scheduler.await_(@as(anyframe->u64, &async get5())); | |
scheduler.yield(five); | |
} | |
}); | |
test "Async generators" { | |
var iter = FiveIter.init(.{}); | |
var next_frame = async iter.next(); | |
expect(global_frame != null); | |
resume global_frame.?; | |
expectEqual((try await next_frame).?, 5); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const std = @import("std"); | |
const bufFmt = std.fmt.bufPrint; | |
pub fn compileErrorFmt(comptime fmt: []const u8, comptime args: var) void { | |
comptime { | |
var buf: [4096]u8 = undefined; | |
@compileError(@as([]const u8, bufFmt(&buf, fmt, args) catch |_| { | |
@compileError("compileFmt: output is too long. Cannot output @compileLogs longer than 4096 bytes"); | |
})); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// TODO refactor to work like https://godbolt.org/z/9Q5aE- | |
const std = @import("std"); | |
const expectEqual = std.testing.expectEqual; | |
const comptime_utils = @import("comptime_utils.zig"); | |
pub fn Tuple(comptime types: var) type { | |
comptime { | |
return switch (types.len) { | |
0 => Tuple0, | |
1 => Tuple1(types[0]), | |
2 => Tuple2(types[0], types[1]), | |
3 => Tuple3(types[0], types[1], types[2]), | |
4 => Tuple4(types[0], types[1], types[2], types[3]), | |
5 => Tuple5(types[0], types[1], types[2], types[3], types[4]), | |
else => @compileError("Tuples above length 5 not supported"), | |
}; | |
} | |
} | |
pub fn tuple(elems: var) TupleReturn(@TypeOf(elems)) { | |
return @call(.{ .modifier = .always_inline }, TupleReturn(@TypeOf(elems)).init, elems); | |
} | |
fn invalidTupleArgType(comptime invalid_type: []const u8, comptime pos: usize) void { | |
comptime { | |
var buf: [4096]u8 = undefined; | |
@compileError(std.fmt.bufPrint(&buf, "tuple called with {} in position {}", .{ invalid_type, i }) catch unreachable); | |
} | |
} | |
fn TupleReturn(comptime ElemsType: type) type { | |
comptime { | |
const elems_tinfo = @typeInfo(ElemsType); | |
if (elems_tinfo != .Struct) @compileError("tuple expect an anonymous struct argument"); | |
const fields = elems_tinfo.Struct.fields; | |
var field_types: [5]type = undefined; | |
for (fields) |field, i| { | |
switch (@typeInfo(field.field_type)) { | |
.ComptimeInt => invalidTupleArgType("comptime_int", i), | |
.ComptimeFloat => invalidTupleArgType("comptime_float", i), | |
.EnumLiteral => invalidTupleArgType("enum literal", i), | |
.Null => invalidTupleArgType("null", i), | |
.Undefined => invalidTupleArgType("undefined", i), | |
.Void => invalidTupleArgType("void", i), | |
.NoReturn => invalidTupleArgType("noreturn", i), | |
else => field_types[i] = comptime_utils.GeneralizedType(field.field_type), | |
} | |
} | |
// return Tuple(field_types[0..fields.len]); // compiler bug | |
return switch (fields.len) { | |
0 => Tuple0, | |
1 => Tuple1(field_types[0]), | |
2 => Tuple2(field_types[0], field_types[1]), | |
3 => Tuple3(field_types[0], field_types[1], field_types[2]), | |
4 => Tuple4(field_types[0], field_types[1], field_types[2], field_types[3]), | |
5 => Tuple5(field_types[0], field_types[1], field_types[2], field_types[3], field_types[4]), | |
else => @compileError("Tuples above length 5 not supported"), | |
}; | |
} | |
} | |
const TupleTypeMarker = @OpaqueType(); | |
pub fn isATuple(maybe_a_tuple: var) bool { | |
comptime { | |
const MaybeATuple = switch (@TypeOf(maybe_a_tuple)) { | |
type => maybe_a_tuple, | |
else => @TypeOf(maybe_a_tuple), | |
}; | |
if (!@hasDecl(MaybeATuple, "ThisIsATuple")) return false; | |
if (@TypeOf(MaybeATuple.ThisIsATuple) != type) return false; | |
return MaybeATuple.ThisIsATuple == TupleTypeMarker; | |
} | |
} | |
fn unpackCheck(comptime args_type: type, comptime types: var) void { | |
comptime { | |
var buf: [4096]u8 = undefined; | |
const args_tinfo = @typeInfo(args_type); | |
if (args_tinfo != .Struct) { | |
@compileError(std.fmt.bufPrint(&buf, "Tuple.unpack expects an anonymous struct parameter, got a {}", .{@tagName(args_tinfo)}) catch unreachable); | |
} | |
const fields = args_tinfo.Struct.fields; | |
if (fields.len > types.len) { | |
@compileError(std.fmt.bufPrint(&buf, "Too many arguments passed to Tuple.unpack. Expected: {} Found: {}", .{ types.len, fields.len }) catch unreachable); | |
} | |
if (fields.len < types.len) { | |
@compileError(std.fmt.bufPrint(&buf, "Too few arguments passed to Tuple.unpack. Expected: {} Found: {}", .{ types.len, fields.len }) catch unreachable); | |
} | |
for (fields) |_, i| { | |
if (@typeInfo(fields[i].field_type) == .Null) continue; | |
if (fields[i].field_type != *types[i]) { | |
@compileError(std.fmt.bufPrint(&buf, "Incorrect argument type passed to Tuple.unpack. Arg index: {} Expected: {} Found: {}", .{ i, @typeName(*types[i]), @typeName(fields[i].field_type) }) catch unreachable); | |
} | |
} | |
} | |
} | |
pub const Tuple0 = struct { | |
const ThisIsATuple = TupleTypeMarker; | |
pub const types = [_]type{}; | |
pub fn init() @This() { | |
return .{}; | |
} | |
pub fn unpack(self: @This(), vars: var) void { | |
comptime unpackCheck(@TypeOf(vars), .{}); | |
} | |
pub fn set(self: *@This(), comptime index: usize, value: var) void { | |
comptime_utils.compileErrorFmt("Invalid index for Tuple0: {}", .{index}); | |
} | |
pub fn GetReturn(comptime index: usize) type { | |
comptime_utils.compileErrorFmt("Invalid index for Tuple0: {}", .{index}); | |
return undefined; | |
} | |
pub fn get(self: *const @This(), comptime index: usize) GetReturn(index) { | |
return undefined; | |
} | |
}; | |
pub fn Tuple1(comptime T0: type) type { | |
return struct { | |
const ThisIsATuple = TupleTypeMarker; | |
pub const types = [_]type{T0}; | |
@"0": T0, | |
pub fn init(x0: T0) @This() { | |
return .{ .@"0" = x0 }; | |
} | |
pub fn unpack(self: @This(), vars: var) void { | |
comptime unpackCheck(@TypeOf(vars), .{ T0, T1 }); | |
if (comptime @typeInfo(@TypeOf(vars[0])) != .Null) vars[0].* = self.@"0"; | |
} | |
pub fn set(self: *@This(), comptime index: usize, value: var) void { | |
comptime { | |
if (@TypeOf(value) != types[index]) { | |
comptime_utils.compileErrorFmt("Invalid type for index {}: {}", .{ index, @typeName(@TypeOf(value)) }); | |
} | |
} | |
switch (comptime index) { | |
0 => self.@"0" = value, | |
else => comptime comptime_utils.compileErrorFmt("Invalid index for Tuple1: {}", .{index}), | |
} | |
} | |
pub fn GetReturn(comptime index: usize) type { | |
comptime return switch (index) { | |
0 => T0, | |
else => comptime_utils.compileErrorFmt("Invalid index for Tuple0: {}", .{index}), | |
}; | |
} | |
pub fn get(self: *const @This(), comptime index: usize) GetReturn(index) { | |
return switch (comptime index) { | |
0 => self.@"0", | |
else => undefined, | |
}; | |
} | |
}; | |
} | |
pub fn Tuple2(comptime T0: type, comptime T1: type) type { | |
return struct { | |
const ThisIsATuple = TupleTypeMarker; | |
pub const types = [_]type{ T0, T1 }; | |
@"0": T0, | |
@"1": T1, | |
pub fn init(x0: T0, x1: T1) @This() { | |
return .{ .@"0" = x0, .@"1" = x1 }; | |
} | |
pub fn unpack(self: @This(), vars: var) void { | |
comptime unpackCheck(@TypeOf(vars), .{ T0, T1 }); | |
if (comptime @typeInfo(@TypeOf(vars[0])) != .Null) vars[0].* = self.@"0"; | |
if (comptime @typeInfo(@TypeOf(vars[1])) != .Null) vars[1].* = self.@"1"; | |
} | |
pub fn set(self: *@This(), comptime index: usize, value: var) void { | |
comptime { | |
if (@TypeOf(value) != types[index]) { | |
comptime_utils.compileErrorFmt("Invalid type for index {}: {}", .{ index, @typeName(@TypeOf(value)) }); | |
} | |
} | |
switch (comptime index) { | |
0 => self.@"0" = value, | |
1 => self.@"1" = value, | |
else => comptime comptime_utils.compileErrorFmt("Invalid index for Tuple2: {}", .{index}), | |
} | |
} | |
pub fn GetReturn(comptime index: usize) type { | |
comptime return switch (index) { | |
0 => T0, | |
1 => T1, | |
else => comptime_utils.compileErrorFmt("Invalid index for Tuple0: {}", .{index}), | |
}; | |
} | |
pub fn get(self: *const @This(), comptime index: usize) GetReturn(index) { | |
return switch (comptime index) { | |
0 => self.@"0", | |
1 => self.@"1", | |
else => undefined, | |
}; | |
} | |
}; | |
} | |
pub fn Tuple3(comptime T0: type, comptime T1: type, comptime T2: type) type { | |
return struct { | |
const ThisIsATuple = TupleTypeMarker; | |
pub const types = [_]type{ T0, T1, T2 }; | |
@"0": T0, | |
@"1": T1, | |
@"2": T2, | |
pub fn init(x0: T0, x1: T1, x2: T2) @This() { | |
return .{ .@"0" = x0, .@"1" = x1, .@"2" = x2 }; | |
} | |
pub fn unpack(self: @This(), vars: var) void { | |
comptime unpackCheck(@TypeOf(vars), .{ T0, T1, T2 }); | |
if (comptime @typeInfo(@TypeOf(vars[0])) != .Null) vars[0].* = self.@"0"; | |
if (comptime @typeInfo(@TypeOf(vars[1])) != .Null) vars[1].* = self.@"1"; | |
if (comptime @typeInfo(@TypeOf(vars[2])) != .Null) vars[2].* = self.@"2"; | |
} | |
pub fn set(self: *@This(), comptime index: usize, value: var) void { | |
comptime { | |
if (@TypeOf(value) != types[index]) { | |
comptime_utils.compileErrorFmt("Invalid type for index {}: {}", .{ index, @typeName(@TypeOf(value)) }); | |
} | |
} | |
switch (comptime index) { | |
0 => self.@"0" = value, | |
1 => self.@"1" = value, | |
2 => self.@"2" = value, | |
else => comptime comptime_utils.compileErrorFmt("Invalid index for Tuple3: {}", .{index}), | |
} | |
} | |
pub fn GetReturn(comptime index: usize) type { | |
comptime return switch (index) { | |
0 => T0, | |
1 => T1, | |
2 => T2, | |
else => comptime_utils.compileErrorFmt("Invalid index for Tuple0: {}", .{index}), | |
}; | |
} | |
pub fn get(self: *const @This(), comptime index: usize) GetReturn(index) { | |
return switch (comptime index) { | |
0 => self.@"0", | |
1 => self.@"1", | |
2 => self.@"2", | |
else => undefined, | |
}; | |
} | |
}; | |
} | |
pub fn Tuple4(comptime T0: type, comptime T1: type, comptime T2: type, comptime T3: type) type { | |
return struct { | |
const ThisIsATuple = TupleTypeMarker; | |
pub const types = [_]type{ T0, T1, T2, T3 }; | |
@"0": T0, | |
@"1": T1, | |
@"2": T2, | |
@"3": T3, | |
pub fn init(x0: T0, x1: T1, x2: T2, x3: T3) @This() { | |
return .{ .@"0" = x0, .@"1" = x1, .@"2" = x2, .@"3" = x3 }; | |
} | |
pub fn unpack(self: @This(), vars: var) void { | |
comptime unpackCheck(@TypeOf(vars), .{ T0, T1, T2, T3 }); | |
if (comptime @typeInfo(@TypeOf(vars[0])) != .Null) vars[0].* = self.@"0"; | |
if (comptime @typeInfo(@TypeOf(vars[1])) != .Null) vars[1].* = self.@"1"; | |
if (comptime @typeInfo(@TypeOf(vars[2])) != .Null) vars[2].* = self.@"2"; | |
if (comptime @typeInfo(@TypeOf(vars[3])) != .Null) vars[3].* = self.@"3"; | |
} | |
pub fn set(self: *@This(), comptime index: usize, value: var) void { | |
comptime { | |
if (@TypeOf(value) != types[index]) { | |
comptime_utils.compileErrorFmt("Invalid type for index {}: {}", .{ index, @typeName(@TypeOf(value)) }); | |
} | |
} | |
switch (comptime index) { | |
0 => self.@"0" = value, | |
1 => self.@"1" = value, | |
2 => self.@"2" = value, | |
3 => self.@"3" = value, | |
else => comptime comptime_utils.compileErrorFmt("Invalid index for Tuple4: {}", .{index}), | |
} | |
} | |
pub fn GetReturn(comptime index: usize) type { | |
comptime return switch (index) { | |
0 => T0, | |
1 => T1, | |
2 => T2, | |
3 => T3, | |
else => comptime_utils.compileErrorFmt("Invalid index for Tuple0: {}", .{index}), | |
}; | |
} | |
pub fn get(self: *const @This(), comptime index: usize) GetReturn(index) { | |
return switch (comptime index) { | |
0 => self.@"0", | |
1 => self.@"1", | |
2 => self.@"2", | |
3 => self.@"3", | |
else => undefined, | |
}; | |
} | |
}; | |
} | |
pub fn Tuple5(comptime T0: type, comptime T1: type, comptime T2: type, comptime T3: type, comptime T4: type) type { | |
return struct { | |
const ThisIsATuple = TupleTypeMarker; | |
pub const types = [_]type{ T0, T1, T2, T3, T4 }; | |
@"0": T0, | |
@"1": T1, | |
@"2": T2, | |
@"3": T3, | |
@"4": T4, | |
pub fn init(x0: T0, x1: T1, x2: T2, x3: T3, x4: T4) @This() { | |
return .{ .@"0" = x0, .@"1" = x1, .@"2" = x2, .@"3" = x3, .@"4" = x4 }; | |
} | |
pub fn unpack(self: @This(), vars: var) void { | |
comptime unpackCheck(vars, .{ T0, T1, T2, T3, T4 }); | |
if (comptime @typeInfo(@TypeOf(vars[0])) != .Null) vars[0].* = self.@"0"; | |
if (comptime @typeInfo(@TypeOf(vars[1])) != .Null) vars[1].* = self.@"1"; | |
if (comptime @typeInfo(@TypeOf(vars[2])) != .Null) vars[2].* = self.@"2"; | |
if (comptime @typeInfo(@TypeOf(vars[3])) != .Null) vars[3].* = self.@"3"; | |
if (comptime @typeInfo(@TypeOf(vars[4])) != .Null) vars[4].* = self.@"4"; | |
} | |
pub fn set(self: *@This(), comptime index: usize, value: var) void { | |
comptime { | |
if (@TypeOf(value) != types[index]) { | |
comptime_utils.compileErrorFmt("Invalid type for index {}: {}", .{ index, @typeName(@TypeOf(value)) }); | |
} | |
} | |
switch (comptime index) { | |
0 => self.@"0" = value, | |
1 => self.@"1" = value, | |
2 => self.@"2" = value, | |
3 => self.@"3" = value, | |
4 => self.@"4" = value, | |
else => comptime comptime_utils.compileErrorFmt("Invalid index for Tuple5: {}", .{index}), | |
} | |
} | |
pub fn GetReturn(comptime index: usize) type { | |
comptime return switch (index) { | |
0 => T0, | |
1 => T1, | |
2 => T2, | |
3 => T3, | |
4 => T4, | |
else => comptime_utils.compileErrorFmt("Invalid index for Tuple0: {}", .{index}), | |
}; | |
} | |
pub fn get(self: *const @This(), comptime index: usize) GetReturn(index) { | |
return switch (comptime index) { | |
0 => self.@"0", | |
1 => self.@"1", | |
2 => self.@"2", | |
3 => self.@"3", | |
4 => self.@"4", | |
else => undefined, | |
}; | |
} | |
}; | |
} | |
test "Tuple([_]type)" { | |
comptime const raw_types = [_]type{ u8, u16, u32 }; | |
comptime var types: [raw_types.len]type = undefined; | |
comptime { | |
inline for (raw_types) |raw_type, i| { | |
types[i] = ?*raw_type; | |
} | |
} | |
const built_tuple = Tuple(types).init(undefined, undefined, undefined); | |
} | |
test "Tuple.unpack" { | |
const x = Tuple(.{ []const u8, []const u8, []const u8 }).init("foo", "bar", "baz"); | |
var a: []const u8 = undefined; | |
var b: []const u8 = "unchanged"; | |
var c: []const u8 = undefined; | |
x.unpack(.{ &a, null, &c }); | |
expectEqual(a, "foo"); | |
expectEqual(b, "unchanged"); | |
expectEqual(c, "baz"); | |
} | |
test "tuple" { | |
const x: u8 = 0; | |
expectEqual(Tuple1(u8), @TypeOf(tuple(.{x}))); | |
expectEqual(Tuple1([]const u8), @TypeOf(tuple(.{"foo"}))); | |
expectEqual(Tuple2(u8, u8), @TypeOf(tuple(.{ x, x }))); | |
expectEqual(Tuple3(u8, u8, u8), @TypeOf(tuple(.{ x, x, x }))); | |
expectEqual(Tuple4(u8, u8, u8, u8), @TypeOf(tuple(.{ x, x, x, x }))); | |
expectEqual(Tuple5(u8, u8, u8, u8, u8), @TypeOf(tuple(.{ x, x, x, x, x }))); | |
} | |
test "Tuple" { | |
const x: u8 = 0; | |
expectEqual(Tuple1(u8), Tuple(.{u8})); | |
expectEqual(Tuple2(u8, u8), Tuple(.{ u8, u8 })); | |
expectEqual(Tuple3(u8, u8, u8), Tuple(.{ u8, u8, u8 })); | |
expectEqual(Tuple4(u8, u8, u8, u8), Tuple(.{ u8, u8, u8, u8 })); | |
expectEqual(Tuple5(u8, u8, u8, u8, u8), Tuple(.{ u8, u8, u8, u8, u8 })); | |
} | |
test "Tuple.set" { | |
const baz: []const u8 = "baz"; | |
const four_twenty = @as(u16, 420); | |
var x = tuple(.{ "foo", "bar", @as(u16, 69) }); | |
inline for (@TypeOf(x).types) |T, i| { | |
x.set(i, switch (T) { | |
[]const u8 => baz, | |
u16 => four_twenty, | |
else => unreachable, | |
}); | |
} | |
expectEqual(tuple(.{ "baz", "baz", @as(u16, 420) }), x); | |
} | |
test "Tuple.set" { | |
const foo: []const u8 = "foo"; | |
const bar: []const u8 = "bar"; | |
var x = tuple(.{ "foo", "bar", @as(u16, 69) }); | |
inline for (@TypeOf(x).types) |T, i| { | |
switch (i) { | |
0 => expectEqual(foo, x.get(i)), | |
1 => expectEqual(bar, x.get(i)), | |
2 => expectEqual(@as(u16, 69), x.get(i)), | |
else => unreachable, | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment