From 2e1416eb216791ed537bac4cf4f77813f6549709 Mon Sep 17 00:00:00 2001 From: Jacob Jonsson Date: Sun, 15 Mar 2026 15:08:43 +0100 Subject: [PATCH] refactor(main): remove one layer of indirection This commit removes the BotAdapter.erased_callback function, which served as a generic version of BotAdapter.callback that was legible for zircon.MessageClosure. However, it is clearer to document how that mechanism works. Therefore we remove the BotAdapter.erased_callback and instead makes BotAdapter.callback match the zircon.MessageClosure.callbackFn signature. --- src/main.zig | 81 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 34 deletions(-) diff --git a/src/main.zig b/src/main.zig index 0caba3b..7d494b7 100644 --- a/src/main.zig +++ b/src/main.zig @@ -44,11 +44,6 @@ pub fn main() !void { }; } -pub const Adapter = struct { - ptr: *anyopaque, - callbackFn: *const fn (*anyopaque, zircon.Message) ?zircon.Message, -}; - /// BotAdapter is the closure that we register in zircon as the /// message callback. /// @@ -82,10 +77,23 @@ pub const BotAdapter = struct { /// See /// - https://modern.ircdocs.horse/, for what kinds of messages exists in the IRC protocol documentation, /// - https://github.com/Jassob/zircon/blob/main/src/message.zig, for zircon documentation. - pub fn callback(self: *BotAdapter, message: zircon.Message) ?zircon.Message { + /// + /// NOTE: This function does not have a self-parameter, this is + /// because this function is called as a "generic" function + /// that is parameterized in the pointer argument (otherwise + /// zircon library would not be able to be reused). + /// + /// That's why the first thing we do is perform some casting + /// magic to convert our pointer back to a BotAdapter + /// pointer. This is safe as long as we know that we + /// registered a BotAdapter closure and there are no other + /// callbacks registered. + pub fn callback(ptr: *anyopaque, message: zircon.Message) ?zircon.Message { + const self: *@This() = @ptrCast(@alignCast(ptr)); + switch (message) { .PRIVMSG => |msg| { - const nick = if (msg.prefix != null and msg.prefix.?.nick != null) msg.prefix.?.nick.? else "unknown"; + const nick = nickFromPrefix(msg.prefix); // create message const bot_message = BotMessage.init_owned( @@ -125,19 +133,23 @@ pub const BotAdapter = struct { return .{ .PRIVMSG = .{ .prefix = null, .targets = "#eru-admin", .text = err_msg } }; } - pub fn erased_callback(self: *anyopaque, message: zircon.Message) ?zircon.Message { - const a: *@This() = @ptrCast(@alignCast(self)); - return a.callback(message); - } - pub fn closure(self: *BotAdapter) zircon.MessageClosure { return .{ .ptr = self, - .callbackFn = BotAdapter.erased_callback, + .callbackFn = BotAdapter.callback, }; } }; +/// nickFromPrefix returns a nick if it exists in the prefix or +/// "unknown" otherwise. +fn nickFromPrefix(prefix: ?zircon.Prefix) []const u8 { + if (prefix == null or prefix.?.nick == null) { + return "unknown"; + } + return prefix.?.nick.?; +} + /// toIRC converts a bot response and converts it to a IRC message. fn toIRC(response: BotResponse) zircon.Message { switch (response) { @@ -150,30 +162,27 @@ fn toIRC(response: BotResponse) zircon.Message { } } +fn priv_msg(text: []const u8) zircon.Message { + return .{ .PRIVMSG = .{ + .prefix = .{ .nick = "jassob", .host = "localhost", .user = "jassob" }, + .targets = "#test", + .text = text, + } }; +} + test "substitute" { - var bot_adapter = try BotAdapter.init(std.testing.allocator); - defer bot_adapter.deinit(); - const prefix = zircon.Prefix{ .nick = "jassob", .user = "jassob", .host = "localhost" }; - const msg = zircon.Message{ - .PRIVMSG = .{ - .prefix = prefix, - .targets = "#test", - .text = "hello world", - }, - }; - if (bot_adapter.callback(msg)) |_| { + var adapter = try BotAdapter.init(std.testing.allocator); + defer adapter.deinit(); + if (BotAdapter.callback(&adapter, priv_msg("hello world"))) |_| { @panic("unexpected response"); } - const cmd_msg = zircon.Message{ - .PRIVMSG = .{ - .prefix = prefix, - .targets = "#test", - .text = "s/world/zig/", - }, - }; - const response = bot_adapter.callback(cmd_msg); + const response = BotAdapter.callback(&adapter, priv_msg("s/world/zig/")); + try std.testing.expect(response != null); - try std.testing.expectEqualStrings("jassob: \"hello zig\"", response.?.PRIVMSG.text); + try std.testing.expectEqualStrings( + "jassob: \"hello zig\"", + response.?.PRIVMSG.text, + ); } test "get empty backlog message" { @@ -182,5 +191,9 @@ test "get empty backlog message" { const msg = zircon.Message{ .PRIVMSG = .{ .prefix = null, .targets = "#eru-admin", .text = "!backlog 0" }, }; - try std.testing.expectEqualDeep("no matching message", bot_adapter.callback(msg).?.PRIVMSG.text); + + try std.testing.expectEqualDeep( + "no matching message", + BotAdapter.callback(&bot_adapter, msg).?.PRIVMSG.text, + ); }