diff --git a/src/lib/enum.zig b/src/lib/enum.zig new file mode 100644 index 000000000..01006f46f --- /dev/null +++ b/src/lib/enum.zig @@ -0,0 +1,85 @@ +const std = @import("std"); + +/// Create an enum type with the given keys that is C ABI compatible +/// if we're targeting C, otherwise a Zig enum with smallest possible +/// backing type. +/// +/// In all cases, the enum keys will be created in the order given. +/// For C ABI, this means that the order MUST NOT be changed in order +/// to preserve ABI compatibility. You can set a key to null to +/// remove it from the Zig enum while keeping the "hole" in the C enum +/// to preserve ABI compatibility. +/// +/// C detection is up to the caller, since there are multiple ways +/// to do that. We rely on the `target` parameter to determine whether we +/// should create a C compatible enum or a Zig enum. +/// +/// For the Zig enum, the enum value is not guaranteed to be stable, so +/// it shouldn't be relied for things like serialization. +pub fn Enum( + target: Target, + keys: []const ?[:0]const u8, +) type { + var fields: [keys.len]std.builtin.Type.EnumField = undefined; + var fields_i: usize = 0; + for (keys, 0..) |key_, key_i| { + const key: [:0]const u8 = key_ orelse switch (target) { + .c => std.fmt.comptimePrint("__unused_{d}", .{key_i}), + .zig => continue, + }; + + fields[fields_i] = .{ + .name = key, + .value = fields_i, + }; + fields_i += 1; + } + + return @Type(.{ .@"enum" = .{ + .tag_type = switch (target) { + .c => c_int, + .zig => std.math.IntFittingRange(0, fields_i - 1), + }, + .fields = fields[0..fields_i], + .decls = &.{}, + .is_exhaustive = true, + } }); +} + +pub const Target = union(enum) { + c, + zig, +}; + +test "zig" { + const testing = std.testing; + const T = Enum(.zig, &.{ "a", "b", "c", "d" }); + const info = @typeInfo(T).@"enum"; + try testing.expectEqual(u2, info.tag_type); +} + +test "c" { + const testing = std.testing; + const T = Enum(.c, &.{ "a", "b", "c", "d" }); + const info = @typeInfo(T).@"enum"; + try testing.expectEqual(c_int, info.tag_type); +} + +test "abi by removing a key" { + const testing = std.testing; + // C + { + const T = Enum(.c, &.{ "a", "b", null, "d" }); + const info = @typeInfo(T).@"enum"; + try testing.expectEqual(c_int, info.tag_type); + try testing.expectEqual(3, @intFromEnum(T.d)); + } + + // Zig + { + const T = Enum(.zig, &.{ "a", "b", null, "d" }); + const info = @typeInfo(T).@"enum"; + try testing.expectEqual(u2, info.tag_type); + try testing.expectEqual(2, @intFromEnum(T.d)); + } +} diff --git a/src/lib/main.zig b/src/lib/main.zig new file mode 100644 index 000000000..4ef8dcb2d --- /dev/null +++ b/src/lib/main.zig @@ -0,0 +1,10 @@ +const std = @import("std"); +const enumpkg = @import("enum.zig"); + +pub const allocator = @import("allocator.zig"); +pub const Enum = enumpkg.Enum; +pub const EnumTarget = enumpkg.Target; + +test { + std.testing.refAllDecls(@This()); +} diff --git a/src/lib_vt.zig b/src/lib_vt.zig index 63a84ad63..6d9c042d8 100644 --- a/src/lib_vt.zig +++ b/src/lib_vt.zig @@ -80,6 +80,7 @@ comptime { test { _ = terminal; - // Tests always test the C API + // Tests always test the C API and shared C functions _ = terminal.c_api; + _ = @import("lib/main.zig"); }