From 16cbe5f469594439035da63e1f40d3fffe66d9a9 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Wed, 28 Dec 2022 19:47:09 +0100 Subject: flags: rewrite to allow [:0]const u8 arguments This also cleans up the code by using @Type(), eliminating the need for the argFlag() and boolFlag() functions. Allowing [:0]const u8 arguments makes this parser useful for river-control commands as well. --- common/flags.zig | 165 +++++++++++++++++++++++++++---------------------------- 1 file changed, 80 insertions(+), 85 deletions(-) (limited to 'common/flags.zig') diff --git a/common/flags.zig b/common/flags.zig index 561d52b..de5304f 100644 --- a/common/flags.zig +++ b/common/flags.zig @@ -1,105 +1,100 @@ -// This file is part of river, a dynamic tiling wayland compositor. +// Zero allocation argument parsing for unix-like systems. +// Released under the Zero Clause BSD (0BSD) license: // -// Copyright 2021 The River Developers +// Copyright 2023 Isaac Freund // -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 3. +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted. // -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. const std = @import("std"); -const cstr = std.cstr; +const mem = std.mem; pub const Flag = struct { - name: [*:0]const u8, + name: []const u8, kind: enum { boolean, arg }, }; -pub fn ParseResult(comptime flags: []const Flag) type { +pub fn parser(comptime Arg: type, comptime flags: []const Flag) type { + switch (Arg) { + // TODO consider allowing []const u8 + [:0]const u8, [*:0]const u8 => {}, // ok + else => @compileError("invalid argument type: " ++ @typeName(Arg)), + } return struct { - const Self = @This(); - - const FlagData = struct { - name: [*:0]const u8, - value: union { - boolean: bool, - arg: ?[*:0]const u8, - }, - }; - - /// Remaining args after the recognized flags - args: [][*:0]const u8, - /// Data obtained from parsed flags - flag_data: [flags.len]FlagData = blk: { - // Init all flags to false/null - var flag_data: [flags.len]FlagData = undefined; - inline for (flags) |flag, i| { - flag_data[i] = switch (flag.kind) { - .boolean => .{ - .name = flag.name, - .value = .{ .boolean = false }, - }, - .arg => .{ - .name = flag.name, - .value = .{ .arg = null }, - }, - }; - } - break :blk flag_data; - }, + pub const Result = struct { + /// Remaining args after the recognized flags + args: []const Arg, + /// Data obtained from parsed flags + flags: Flags, - pub fn boolFlag(self: Self, flag_name: [*:0]const u8) bool { - for (self.flag_data) |flag_data| { - if (cstr.cmp(flag_data.name, flag_name) == 0) return flag_data.value.boolean; - } - unreachable; // Invalid flag_name - } - - pub fn argFlag(self: Self, flag_name: [*:0]const u8) ?[:0]const u8 { - for (self.flag_data) |flag_data| { - if (cstr.cmp(flag_data.name, flag_name) == 0) { - return std.mem.span(flag_data.value.arg); + pub const Flags = flags_type: { + var fields: []const std.builtin.TypeInfo.StructField = &.{}; + inline for (flags) |flag| { + const field: std.builtin.TypeInfo.StructField = switch (flag.kind) { + .boolean => .{ + .name = flag.name, + .field_type = bool, + .default_value = false, + .is_comptime = false, + .alignment = @alignOf(bool), + }, + .arg => .{ + .name = flag.name, + .field_type = ?[:0]const u8, + .default_value = @as(??[:0]const u8, @as(?[:0]const u8, null)), + .is_comptime = false, + .alignment = @alignOf(?[:0]const u8), + }, + }; + fields = fields ++ [_]std.builtin.TypeInfo.StructField{field}; } - } - unreachable; // Invalid flag_name - } - }; -} + break :flags_type @Type(.{ .Struct = .{ + .layout = .Auto, + .fields = fields, + .decls = &.{}, + .is_tuple = false, + } }); + }; + }; -pub fn parse(args: [][*:0]const u8, comptime flags: []const Flag) !ParseResult(flags) { - var ret: ParseResult(flags) = .{ .args = undefined }; + pub fn parse(args: []const Arg) !Result { + var result_flags: Result.Flags = .{}; - var arg_idx: usize = 0; - while (arg_idx < args.len) : (arg_idx += 1) { - var parsed_flag = false; - inline for (flags) |flag, flag_idx| { - if (cstr.cmp(flag.name, args[arg_idx]) == 0) { - switch (flag.kind) { - .boolean => ret.flag_data[flag_idx].value.boolean = true, - .arg => { - arg_idx += 1; - if (arg_idx == args.len) { - std.log.err("option '" ++ flag.name ++ - "' requires an argument but none was provided!", .{}); - return error.MissingFlagArgument; + var i: usize = 0; + while (i < args.len) : (i += 1) { + var parsed_flag = false; + inline for (flags) |flag| { + if (mem.eql(u8, flag.name, mem.span(args[i]))) { + switch (flag.kind) { + .boolean => @field(result_flags, flag.name) = true, + .arg => { + i += 1; + if (i == args.len) { + std.log.err("option '" ++ flag.name ++ + "' requires an argument but none was provided!", .{}); + return error.MissingFlagArgument; + } + @field(result_flags, flag.name) = mem.span(args[i]); + }, } - ret.flag_data[flag_idx].value.arg = args[arg_idx]; - }, + parsed_flag = true; + } } - parsed_flag = true; + if (!parsed_flag) break; } - } - if (!parsed_flag) break; - } - - ret.args = args[arg_idx..]; - return ret; + return Result{ + .args = args[i..], + .flags = result_flags, + }; + } + }; } -- cgit v1.2.3