Skip to content

Instantly share code, notes, and snippets.

@travisstaloch
Last active October 7, 2022 03:17
Show Gist options
  • Save travisstaloch/3ef1e4c2cc29dcde7c3a703660861500 to your computer and use it in GitHub Desktop.
Save travisstaloch/3ef1e4c2cc29dcde7c3a703660861500 to your computer and use it in GitHub Desktop.
//! 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