Last active
October 7, 2022 03:17
-
-
Save travisstaloch/3ef1e4c2cc29dcde7c3a703660861500 to your computer and use it in GitHub Desktop.
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
//! Shows how to manually convert a group of enums / ints into a packed struct and then into an integer | |
//! which can be used in a switch statement. Useful for eliminting complicated nested switches / ifs. | |
//! | |
//! Then creates helpers switchable() and switchableAny() for doing the same. | |
//! | |
//! Note: the second part only works with stage1 as of 9/25/22 (zig version | |
//! 0.10.0-dev.4115+75e9a8c7f). stage2 currently crashes on this but will likely work soon. | |
const std = @import("std"); | |
const E1 = enum { a, b }; | |
const E2 = enum { a, b, c }; | |
const E3 = enum { a, b, c, d }; | |
const S = packed struct { | |
e1: I1, | |
e2: I2, | |
e3: I3, | |
int1: u5, | |
const I1 = std.meta.Tag(E1); | |
const I2 = std.meta.Tag(E2); | |
const I3 = std.meta.Tag(E3); | |
const I = std.meta.Int(.unsigned, @bitSizeOf(S)); | |
pub fn init(e1: E1, e2: E2, e3: E3, int1: u5) I { | |
return @bitCast(I, S{ | |
.e1 = @enumToInt(e1), | |
.e2 = @enumToInt(e2), | |
.e3 = @enumToInt(e3), | |
.int1 = int1, | |
}); | |
} | |
}; | |
test "manual" { | |
var i = S.init(.a, .b, .c, 10); | |
const is_expected = switch (i) { | |
S.init(.a, .b, .c, 10) => true, | |
else => false, | |
}; | |
try std.testing.expect(is_expected); | |
} | |
pub fn Switchable(comptime Fields: []const type) type { | |
comptime { | |
var fields: [Fields.len]std.builtin.Type.StructField = undefined; | |
for (Fields) |T, i| { | |
const tinfo = @typeInfo(T); | |
const field_type = switch (tinfo) { | |
.Enum => |e| e.tag_type, | |
.Int => T, | |
else => @compileError("type '" ++ @typeName(T) ++ "' not supported. Switchable() only supports enum and integer types."), | |
}; | |
fields[i] = .{ | |
.name = std.fmt.comptimePrint("{}", .{i}), | |
.field_type = field_type, | |
.default_value = null, | |
.is_comptime = false, | |
.alignment = @alignOf(field_type), | |
}; | |
} | |
return @Type(.{ .Struct = .{ | |
.layout = .Packed, | |
.backing_integer = null, | |
.fields = &fields, | |
.decls = &.{}, | |
.is_tuple = false, | |
} }); | |
} | |
} | |
pub fn PackedInt(comptime T: type) type { | |
return std.meta.Int(.unsigned, @bitSizeOf(T)); | |
} | |
pub fn SwitchableT(comptime T: type) type { | |
return Switchable(FieldTypes(T)); | |
} | |
pub fn FieldTypes(comptime T: type) []const type { | |
comptime { | |
const fields = std.meta.fields(T); | |
var Fields: [fields.len]type = undefined; | |
for (fields) |f, i| Fields[i] = f.field_type; | |
return &Fields; | |
} | |
} | |
pub fn switchableAny(m: anytype) PackedInt(SwitchableT(@TypeOf(m))) { | |
return switchable(FieldTypes(@TypeOf(m)), m); | |
} | |
pub fn switchable(comptime Fields: []const type, m: anytype) PackedInt(Switchable(Fields)) { | |
const Sw = Switchable(Fields); | |
var t: Sw = undefined; | |
inline for (comptime std.meta.fields(@TypeOf(m))) |f, i| { | |
const finfo = @typeInfo(f.field_type); | |
const T = Fields[i]; | |
@field(t, f.name) = switch (finfo) { | |
.Enum => @enumToInt(m[i]), | |
.EnumLiteral => @enumToInt(@as(T, m[i])), | |
.Int => m[i], | |
.ComptimeInt => @as(T, m[i]), | |
else => @compileError("type '" ++ @typeName(Sw) ++ "' not supported. switchable() only supports enum and integer types."), | |
}; | |
} | |
return @bitCast(PackedInt(Sw), t); | |
} | |
test "switchable helpers" { | |
const Fields = &.{ E1, E2, E3, u5 }; | |
{ | |
var sw = switchable(Fields, .{ .a, .b, .c, 10 }); | |
var is_expected = switch (sw) { | |
switchable(Fields, .{ .a, .b, .c, 10 }) => true, | |
else => false, | |
}; | |
try std.testing.expect(is_expected); | |
} | |
{ | |
var sw = switchableAny(.{ E1.a, E2.b, E3.c, @as(u5, 10) }); | |
var is_expected = switch (sw) { | |
switchable(Fields, .{ .a, .b, .c, 10 }) => true, | |
else => false, | |
}; | |
try std.testing.expect(is_expected); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment