diff --git a/src/tests.zig b/src/tests.zig index ed6db82..bb91bd2 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -3,6 +3,7 @@ const Allocator = std.mem.Allocator; const command = @import("./command.zig"); const ppack = @import("./parser.zig"); +const mkRef = @import("./value_ref.zig").mkRef; const Parser = ppack.Parser; const ParseResult = ppack.ParseResult; @@ -38,10 +39,11 @@ fn run(app: *command.App, items: []const []const u8) !ParseResult { fn dummy_action(_: []const []const u8) !void {} test "long option" { + var aa: []const u8 = "test"; var opt = command.Option{ .long_name = "aa", .help = "option aa", - .value = command.OptionValue{ .string = null }, + .value_ref = mkRef(&aa), }; var cmd = command.App{ .name = "abc", @@ -50,18 +52,19 @@ test "long option" { }; _ = try run(&cmd, &.{ "cmd", "--aa", "val" }); - try expect(std.mem.eql(u8, opt.value.string.?, "val")); + try std.testing.expectEqualStrings("val", aa); _ = try run(&cmd, &.{ "cmd", "--aa=bb" }); - try expect(std.mem.eql(u8, opt.value.string.?, "bb")); + try std.testing.expectEqualStrings("bb", aa); } test "short option" { + var aa: []const u8 = undefined; var opt = command.Option{ .long_name = "aa", .short_alias = 'a', .help = "option aa", - .value = command.OptionValue{ .string = null }, + .value_ref = mkRef(&aa), }; var app = command.App{ .name = "abc", @@ -70,92 +73,124 @@ test "short option" { }; _ = try run(&app, &.{ "abc", "-a", "val" }); - try expect(std.mem.eql(u8, opt.value.string.?, "val")); + try std.testing.expectEqualStrings("val", aa); _ = try run(&app, &.{ "abc", "-a=bb" }); - try expect(std.mem.eql(u8, opt.value.string.?, "bb")); + try std.testing.expectEqualStrings("bb", aa); } test "concatenated aliases" { - var bb = command.Option{ + var aa: []const u8 = undefined; + var bb: bool = false; + var bbopt = command.Option{ .long_name = "bb", .short_alias = 'b', .help = "option bb", - .value = command.OptionValue{ .bool = false }, + .value_ref = mkRef(&bb), }; var opt = command.Option{ .long_name = "aa", .short_alias = 'a', .help = "option aa", - .value = command.OptionValue{ .string = null }, + .value_ref = mkRef(&aa), }; var app = command.App{ .name = "abc", - .options = &.{ &bb, &opt }, + .options = &.{ &bbopt, &opt }, .action = dummy_action, }; _ = try run(&app, &.{ "abc", "-ba", "val" }); - try expect(std.mem.eql(u8, opt.value.string.?, "val")); - try expect(bb.value.bool); + try std.testing.expectEqualStrings("val", aa); + try expect(bb); } test "int and float" { - var aa = command.Option{ + var aa: i32 = undefined; + var bb: f64 = undefined; + var aa_opt = command.Option{ .long_name = "aa", .help = "option aa", - .value = command.OptionValue{ .int = null }, + .value_ref = mkRef(&aa), }; - var bb = command.Option{ + var bb_opt = command.Option{ .long_name = "bb", .help = "option bb", - .value = command.OptionValue{ .float = null }, + .value_ref = mkRef(&bb), }; var app = command.App{ .name = "abc", - .options = &.{ &aa, &bb }, + .options = &.{ &aa_opt, &bb_opt }, .action = dummy_action, }; _ = try run(&app, &.{ "abc", "--aa=34", "--bb", "15.25" }); - try expect(aa.value.int.? == 34); - try expect(bb.value.float.? == 15.25); + try expect(34 == aa); + try expect(15.25 == bb); +} + +test "int list" { + var aa: []u64 = undefined; + var aa_opt = command.Option{ + .long_name = "aa", + .short_alias = 'a', + .help = "option aa", + .value_ref = mkRef(&aa), + }; + var app = command.App{ + .name = "abc", + .options = &.{&aa_opt}, + .action = dummy_action, + }; + + _ = try run(&app, &.{ "abc", "--aa=100", "--aa", "200", "-a", "300", "-a=400" }); + try expect(aa.len == 4); + try expect(aa[0] == 100); + try expect(aa[1] == 200); + try expect(aa[2] == 300); + try expect(aa[3] == 400); + + // FIXME: it tries to deallocated u64 while the memory was allocated using u8 alignment + alloc.free(aa); } test "string list" { - var aa = command.Option{ + var aa: [][]const u8 = undefined; + var aa_opt = command.Option{ .long_name = "aa", .short_alias = 'a', .help = "option aa", - .value = command.OptionValue{ .string_list = null }, + .value_ref = mkRef(&aa), }; var app = command.App{ .name = "abc", - .options = &.{&aa}, + .options = &.{&aa_opt}, .action = dummy_action, }; _ = try run(&app, &.{ "abc", "--aa=a1", "--aa", "a2", "-a", "a3", "-a=a4" }); - try expect(aa.value.string_list.?.len == 4); - try expect(std.mem.eql(u8, aa.value.string_list.?[0], "a1")); - try expect(std.mem.eql(u8, aa.value.string_list.?[1], "a2")); - try expect(std.mem.eql(u8, aa.value.string_list.?[2], "a3")); - try expect(std.mem.eql(u8, aa.value.string_list.?[3], "a4")); + try expect(aa.len == 4); + try std.testing.expectEqualStrings("a1", aa[0]); + try std.testing.expectEqualStrings("a2", aa[1]); + try std.testing.expectEqualStrings("a3", aa[2]); + try std.testing.expectEqualStrings("a4", aa[3]); - alloc.free(aa.value.string_list.?); + alloc.free(aa); } test "mix positional arguments and options" { + var aav: []const u8 = undefined; + var bbv: []const u8 = undefined; var aa = command.Option{ .long_name = "aa", .short_alias = 'a', .help = "option aa", - .value = command.OptionValue{ .string = null }, + .value_ref = mkRef(&aav), }; var bb = command.Option{ .long_name = "bb", .help = "option bb", - .value = command.OptionValue{ .string = null }, + .value_ref = mkRef(&bbv), }; var app = command.App{ .name = "abc", @@ -165,11 +200,11 @@ test "mix positional arguments and options" { var result = try run(&app, &.{ "cmd", "--bb", "tt", "arg1", "-a", "val", "arg2", "--", "--arg3", "-arg4" }); defer std.testing.allocator.free(result.args); - try expect(std.mem.eql(u8, aa.value.string.?, "val")); - try expect(std.mem.eql(u8, bb.value.string.?, "tt")); + try std.testing.expectEqualStrings("val", aav); + try std.testing.expectEqualStrings("tt", bbv); try expect(result.args.len == 4); - try expect(std.mem.eql(u8, result.args[0], "arg1")); - try expect(std.mem.eql(u8, result.args[1], "arg2")); - try expect(std.mem.eql(u8, result.args[2], "--arg3")); - try expect(std.mem.eql(u8, result.args[3], "-arg4")); + try std.testing.expectEqualStrings("arg1", result.args[0]); + try std.testing.expectEqualStrings("arg2", result.args[1]); + try std.testing.expectEqualStrings("--arg3", result.args[2]); + try std.testing.expectEqualStrings("-arg4", result.args[3]); } diff --git a/src/value_parser.zig b/src/value_parser.zig index 2abeeda..05a4e17 100644 --- a/src/value_parser.zig +++ b/src/value_parser.zig @@ -32,7 +32,7 @@ fn intData(comptime ValueType: type, comptime DestinationType: type) ValueData { .value_size = @sizeOf(DestinationType), .value_parser = struct { fn parser(dest: *anyopaque, value: []const u8) anyerror!void { - const dt: *DestinationType = @ptrCast(@alignCast(dest)); + const dt: *DestinationType = @alignCast(@ptrCast(dest)); dt.* = try std.fmt.parseInt(ValueType, value, 10); } }.parser, diff --git a/src/value_ref.zig b/src/value_ref.zig index aa9381c..3ca6237 100644 --- a/src/value_ref.zig +++ b/src/value_ref.zig @@ -1,6 +1,7 @@ const std = @import("std"); const command = @import("./command.zig"); const vp = @import("./value_parser.zig"); +const Allocator = std.mem.Allocator; pub const ValueRef = struct { dest: *anyopaque, @@ -10,31 +11,30 @@ pub const ValueRef = struct { const Self = @This(); - pub fn put(self: *Self, value: []const u8, alloc: std.mem.Allocator) anyerror!void { + pub fn put(self: *Self, value: []const u8, alloc: Allocator) anyerror!void { + self.element_count += 1; switch (self.value_type) { .single => { - self.element_count += 1; return self.value_data.value_parser(self.dest, value); }, .multi => |*list| { - self.element_count += 1; - try list.ensureTotalCapacity(alloc, self.element_count * self.value_data.value_size); - const value_ptr = list.items.ptr + ((self.element_count - 1) * self.value_data.value_size); + if (list.list_ptr == null) { + list.list_ptr = try list.vtable.createList(alloc); + } + var value_ptr = try list.vtable.addOne(list.list_ptr.?, alloc); try self.value_data.value_parser(value_ptr, value); - list.items.len += self.value_data.value_size; }, } } - pub fn finalize(self: *Self, alloc: std.mem.Allocator) anyerror!void { + pub fn finalize(self: *Self, alloc: Allocator) anyerror!void { switch (self.value_type) { .single => {}, .multi => |*list| { - var sl = try list.toOwnedSlice(alloc); - sl.len = self.element_count; - - var dest: *[]u8 = @alignCast(@ptrCast(self.dest)); - dest.* = sl; + if (list.list_ptr == null) { + list.list_ptr = try list.vtable.createList(alloc); + } + try list.vtable.finalize(list.list_ptr.?, self.dest, alloc); }, } } @@ -42,10 +42,10 @@ pub const ValueRef = struct { const ValueType = union(enum) { single, - multi: std.ArrayListUnmanaged(u8), + multi: ValueList, }; -const AllocError = std.mem.Allocator.Error; +const AllocError = Allocator.Error; pub const Error = AllocError; // | error{NotImplemented}; pub fn mkRef(dest: anytype) ValueRef { @@ -66,7 +66,7 @@ pub fn mkRef(dest: anytype) ValueRef { return ValueRef{ .dest = @ptrCast(dest), .value_data = vp.getValueData(pinfo.child), - .value_type = ValueType{ .multi = std.ArrayListUnmanaged(u8){} }, + .value_type = ValueType{ .multi = ValueList.init(pinfo.child) }, }; } }, @@ -82,3 +82,40 @@ pub fn mkRef(dest: anytype) ValueRef { }, } } + +const ValueList = struct { + list_ptr: ?*anyopaque = null, + vtable: VTable, + + const VTable = struct { + createList: *const fn (Allocator) anyerror!*anyopaque, + addOne: *const fn (list_ptr: *anyopaque, alloc: Allocator) anyerror!*anyopaque, + finalize: *const fn (list_ptr: *anyopaque, dest: *anyopaque, alloc: Allocator) anyerror!void, + }; + + fn init(comptime T: type) ValueList { + const List = std.ArrayListUnmanaged(T); + const gen = struct { + fn createList(alloc: Allocator) anyerror!*anyopaque { + var list = try alloc.create(List); + list.* = List{}; + return list; + } + fn addOne(list_ptr: *anyopaque, alloc: Allocator) anyerror!*anyopaque { + const list: *List = @alignCast(@ptrCast(list_ptr)); + return @ptrCast(try list.addOne(alloc)); + } + fn finalize(list_ptr: *anyopaque, dest: *anyopaque, alloc: Allocator) anyerror!void { + const list: *List = @alignCast(@ptrCast(list_ptr)); + var destSlice: *[]T = @alignCast(@ptrCast(dest)); + destSlice.* = try list.toOwnedSlice(alloc); + alloc.destroy(list); + } + }; + return ValueList{ .vtable = VTable{ + .createList = gen.createList, + .addOne = gen.addOne, + .finalize = gen.finalize, + } }; + } +};