diff --git a/source/command.zig b/source/command.zig index f8780f3..e6b3eb4 100644 --- a/source/command.zig +++ b/source/command.zig @@ -72,6 +72,61 @@ fn BuilderGenerics(comptime UserContext: type) type { }; } +fn InterfaceCreator(comptime Command: type) type { + return if (Command.ICC.InputType()) |Type| + struct { + pub fn createInterface( + comptime self: Command, + allocator: std.mem.Allocator, + comptime callback: self.CallbackSignature(), + context: Type, + ) !ParserInterface { + return try self._createInterfaceImpl(allocator, callback, context); + } + } + else + struct { + pub fn createInterface( + comptime self: Command, + allocator: std.mem.Allocator, + comptime callback: self.CallbackSignature(), + ) !ParserInterface { + return try self._createInterfaceImpl(allocator, callback, void{}); + } + }; +} + +pub const InterfaceContextCategory = union(enum) { + empty, + pointer: type, + value: type, + + pub fn fromType(comptime ContextType: type) InterfaceContextCategory { + return switch (@typeInfo(ContextType)) { + .Void => .empty, + .Pointer => |info| if (info.size == .Slice) .{ .value = ContextType } else .{ .pointer = ContextType }, + // technically, i0, u0, and struct{} should be treated as empty, probably + else => .{ .value = ContextType }, + }; + } + + pub fn InputType(comptime self: InterfaceContextCategory) ?type { + return switch (self) { + .empty => null, + .pointer => |Type| Type, + .value => |Type| *const Type, + }; + } + + pub fn OutputType(comptime self: InterfaceContextCategory) type { + return switch (self) { + .empty => void, + .pointer => |Type| Type, + .value => |Type| Type, + }; + } +}; + pub fn CommandBuilder(comptime UserContext: type) type { return struct { param_spec: ncmeta.TupleBuilder = .{}, @@ -82,6 +137,7 @@ pub fn CommandBuilder(comptime UserContext: type) type { description: []const u8, pub const UserContextType = UserContext; + pub const ICC: InterfaceContextCategory = InterfaceContextCategory.fromType(UserContextType); pub fn createParser( comptime self: @This(), @@ -101,11 +157,13 @@ pub fn CommandBuilder(comptime UserContext: type) type { }; } - pub fn createInterface( + pub usingnamespace InterfaceCreator(@This()); + + fn _createInterfaceImpl( comptime self: @This(), - comptime callback: self.CallbackSignature(), allocator: std.mem.Allocator, - context: UserContextType, + comptime callback: self.CallbackSignature(), + context: (ICC.InputType() orelse void), ) !ParserInterface { var arena = try allocator.create(std.heap.ArenaAllocator); arena.* = std.heap.ArenaAllocator.init(allocator); @@ -119,7 +177,7 @@ pub fn CommandBuilder(comptime UserContext: type) type { .help_builder = help.HelpBuilder(self).init(arena_alloc), }; - if (UserContextType == void) { + if (comptime ICC == .empty) { return this_parser.interface(); } else { return this_parser.interface(context); @@ -272,7 +330,7 @@ pub fn CommandBuilder(comptime UserContext: type) type { } pub fn CallbackSignature(comptime self: @This()) type { - return *const fn (UserContext, self.Output()) anyerror!void; + return *const fn (UserContextType, self.Output()) anyerror!void; } pub fn Output(comptime self: @This()) type { diff --git a/source/parser.zig b/source/parser.zig index 29802ff..de90994 100644 --- a/source/parser.zig +++ b/source/parser.zig @@ -99,7 +99,7 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type { // This is a slightly annoying hack to work around the fact that there's no way // to provide a method signature conditionally. - pub usingnamespace InterfaceGen(@This(), UserContext); + pub usingnamespace InterfaceGen(@This(), @TypeOf(command).ICC); // This is attached to the struct this way because these are all "private" // methods that exist exclusively to cast the type-erased interface object back // into something usable. Their implementations aren't meaningful and just @@ -536,32 +536,35 @@ fn InterfaceWrappers(comptime ParserType: type) type { }; } -fn InterfaceGen(comptime ParserType: type, comptime UserContext: type) type { - const CtxInfo = @typeInfo(UserContext); +// TODO: figure out a better way of consolidating this logic with that in command.zig? +fn InterfaceGen(comptime ParserType: type, comptime ICC: anytype) type { + return switch (ICC) { + .empty => struct { + pub fn interface(self: *ParserType) ParserInterface { + return ParserInterface.create(ParserType, self, @constCast(&void{})); + } - return if (CtxInfo == .Void) struct { - pub fn interface(self: *ParserType) ParserInterface { - return ParserInterface.create(ParserType, self, @constCast(&void{})); - } + fn castContext(_: ParserType, _: *anyopaque) void { + return void{}; + } + }, + .pointer => struct { + pub fn interface(self: *ParserType, context: ICC.InputType().?) ParserInterface { + return ParserInterface.create(ParserType, self, @constCast(context)); + } - fn castContext(_: ParserType, _: *anyopaque) void { - return void{}; - } - } else if (CtxInfo == .Pointer and CtxInfo.Pointer.size != .Slice) struct { - pub fn interface(self: *ParserType, context: UserContext) ParserInterface { - return ParserInterface.create(ParserType, self, @constCast(context)); - } + fn castContext(_: ParserType, ctx: *anyopaque) ICC.OutputType() { + return @ptrCast(@alignCast(ctx)); + } + }, + .value => struct { + pub fn interface(self: *ParserType, context: ICC.InputType().?) ParserInterface { + return ParserInterface.create(ParserType, self, @ptrCast(@constCast(context))); + } - fn castContext(_: ParserType, ctx: *anyopaque) UserContext { - return @ptrCast(@alignCast(ctx)); - } - } else struct { - pub fn interface(self: *ParserType, context: *const UserContext) ParserInterface { - return ParserInterface.create(ParserType, self, @ptrCast(@constCast(context))); - } - - fn castContext(_: ParserType, ctx: *anyopaque) UserContext { - return @as(*const UserContext, @ptrCast(@alignCast(ctx))).*; - } + fn castContext(_: ParserType, ctx: *anyopaque) ICC.OutputType() { + return @as(ICC.InputType().?, @ptrCast(@alignCast(ctx))).*; + } + }, }; }