Hi, I’ve been reading Zig compiler code for a long in hope of writing idiomatic Zig code, thus I’m using a nightly Zig version (0.17.0-dev.1127+9b5851f00).
I start by writing some small utility programs like ls(1), cat(1), etc. (FreeBSD ones, because macOS ones are based on the former)
When implementing an ArgsIteratorWithOpt which is able to parse optstring for short options, I want use a std.StaticStringMap(Option.Tag).initComptime(list) to decide whether next argument is “option_argument” based on the last character if current argument is “option”.
Part of ArgsIteratorWithOpt (Full is at end):
// ArgsIteratorWithOpt.kind()
pub fn kind(self: *Self, arg: []const u8) Tag {
if (self.index != 0) switch (self.tags.items[self.index - 1]) {
.operand,
.end_of_options,
=> return .operand,
// To be implemented.
// The `option_argument` is returned if `optmap.get(arg[arg.len]).@"1"` is `.req_arg`.
.option => {},
};
if (arg.len >= 2 and mem.startsWith(u8, arg, "-") and !mem.eql(u8, arg[1], "-")) {
return .option;
} else if (arg.len >= 3 and mem.startsWith(u8, arg, "--")) {
return .option_long;
} else if (mem.eql(u8, arg, "--")) {
return .end_of_options;
} else {
return .operand;
}
}
where ArgsIteratorWithOpt.Tag is an enum:
// ArgsIteratorWithOpt.Tag
const Tag = enum(u3) {
option,
option_argument,
operand,
end_of_options,
//
option_long,
};
The problem is at Option.buildList which should output a compile-known list to be consumed by StaticStringMap:
// test_list_from_optstring.zig
const std = @import("std");
const testing = std.testing;
const Option = struct {
tag: Tag,
const Tag = enum(u2) {
/// no argument to the option is expected
no_arg,
/// an argument to the option is required
req_arg,
/// an argument to the option may be presented
opt_arg,
};
const Error = error{
FindNone,
DuplicateOption,
/// Historically, a leading ':' in optstring for getopt(3) function is
/// used to distinguish runtime-known error "unknown option" and "missing option argument"
/// Thus it is not necessary here.
LeadingColon,
TooManyColons,
};
// E.g, Validate and parse "a:bc::d" to a list
// `.{'a', .req_arg}, .{'b', .no_arg}, .{'c', .opt_arg}, .{'d', .no_arg}`.
pub fn buildList(comptime opts: []const u8) Error![]struct { u8, Tag } {
if (opts.len == 0) return error.FindNone;
if (opts[0] == ':') return error.LeadingColon;
var seen: std.bit_set.Integer(256) = .empty;
var list: []struct { u8, Tag } = undefined;
var i: usize = 0;
var list_i: usize = 0;
while (i < opts.len) : (i += 1) {
const opt = opts[i];
const tag: Tag = .no_arg;
if (seen.isSet(opt)) return error.DuplicateOption;
if (opt != ':') {
seen.set(opt);
list[list_i] = .{ opt, tag };
list_i += 1;
} else {
if (opts[i - 1] != ':') {
list[list_i - 1].@"1" = .req_arg;
} else {
if (i < 2 or opts[i - 2] == ':') return error.TooManyColons;
list[list_i - 1].@"1" = .opt_arg;
}
}
}
// It is going to be consumed by
// `const optmap = std.StaticStringMap(Option.Tag).initComptime(list);` at caller.
return list[0..list_i];
}
};
test "list basic" {
const Tag = Option.Tag;
{
const opts = "a:bc::d";
const list = try Option.buildList(opts);
try testing.expectEqual('a', list[0].@"0");
try testing.expectEqual(Tag.req_arg, list[0].@"1");
try testing.expectEqual('b', list[1].@"0");
try testing.expectEqual(Tag.no_arg, list[1].@"1");
try testing.expectEqual('c', list[2].@"0");
try testing.expectEqual(Tag.opt_arg, list[2].@"1");
try testing.expectEqual('d', list[3].@"0");
try testing.expectEqual(Tag.no_arg, list[3].@"1");
}
}
It failed the test and I did’t know why from error message.
Segmentation fault at address 0xaaaaaaaaaaaaaaaa
/Users/zeeq/Code/cmds/example/list_from_optstring.zig:45:21: 0x1029b3ad0 in buildList__anon_34228 (test)
list[list_i] = .{ opt, tag };
^
/Users/zeeq/Code/cmds/example/list_from_optstring.zig:67:42: 0x1029b311f in test.list basic (test)
const list = try Option.buildList(opts);