1// Copyright (c) 2023 Google LLC. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15#ifndef INCLUDE_SPIRV_TOOLS_UTIL_FLAGS_HPP_ 16#define INCLUDE_SPIRV_TOOLS_UTIL_FLAGS_HPP_ 17 18#include <stdint.h> 19 20#include <functional> 21#include <string> 22#include <variant> 23#include <vector> 24 25// This file provides some utils to define a command-line interface with 26// required and optional flags. 27// - Flag order is not checked. 28// - Currently supported flag types: BOOLEAN, STRING 29// - As with most nix tools, using '--' in the command-line means all following 30// tokens will be considered positional 31// arguments. 32// Example: binary -g -- -g --some-other-flag 33// - the first `-g` is a flag. 34// - the second `-g` is not a flag. 35// - `--some-other-flag` is not a flag. 36// - Both long-form and short-form flags are supported, but boolean flags don't 37// support split boolean literals (short and long form). 38// Example: 39// -g : allowed, sets g to true. 40// --my-flag : allowed, sets --my-flag to true. 41// --my-flag=true : allowed, sets --my-flag to true. 42// --my-flag true : NOT allowed. 43// -g true : NOT allowed. 44// --my-flag=TRUE : NOT allowed. 45// 46// - This implementation also supports string flags: 47// -o myfile.spv : allowed, sets -o to `myfile.spv`. 48// --output=myfile.spv : allowed, sets --output to `myfile.spv`. 49// --output myfile.spv : allowd, sets --output to `myfile.spv`. 50// 51// Note: then second token is NOT checked for hyphens. 52// --output -file.spv 53// flag name: `output` 54// flag value: `-file.spv` 55// 56// - This implementation generates flag at compile time. Meaning flag names 57// must be valid C++ identifiers. 58// However, flags are usually using hyphens for word separation. Hence 59// renaming is done behind the scenes. Example: 60// // Declaring a long-form flag. 61// FLAG_LONG_bool(my_flag, [...]) 62// 63// -> in the code: flags::my_flag.value() 64// -> command-line: --my-flag 65// 66// - The only additional lexing done is around '='. Otherwise token list is 67// processed as received in the Parse() 68// function. 69// Lexing the '=' sign: 70// - This is only done when parsing a long-form flag name. 71// - the first '=' found is considered a marker for long-form, splitting 72// the token into 2. 73// Example: --option=value=abc -> [--option, value=abc] 74// 75// In most cases, you want to define some flags, parse them, and query them. 76// Here is a small code sample: 77// 78// ```c 79// // Defines a '-h' boolean flag for help printing, optional. 80// FLAG_SHORT_bool(h, /*default=*/ false, "Print the help.", false); 81// // Defines a '--my-flag' string flag, required. 82// FLAG_LONG_string(my_flag, /*default=*/ "", "A magic flag!", true); 83// 84// int main(int argc, const char** argv) { 85// if (!flags::Parse(argv)) { 86// return -1; 87// } 88// 89// if (flags::h.value()) { 90// printf("usage: my-bin --my-flag=<value>\n"); 91// return 0; 92// } 93// 94// printf("flag value: %s\n", flags::my_flag.value().c_str()); 95// for (const std::string& arg : flags::positional_arguments) { 96// printf("arg: %s\n", arg.c_str()); 97// } 98// return 0; 99// } 100// ```c 101 102// Those macros can be used to define flags. 103// - They should be used in the global scope. 104// - Underscores in the flag variable name are replaced with hyphens ('-'). 105// 106// Example: 107// FLAG_SHORT_bool(my_flag, false, "some help", false); 108// - in the code: flags::my_flag 109// - command line: --my-flag=true 110// 111#define FLAG_LONG_string(Name, Default, Required) \ 112 UTIL_FLAGS_FLAG_LONG(std::string, Name, Default, Required) 113#define FLAG_LONG_bool(Name, Default, Required) \ 114 UTIL_FLAGS_FLAG_LONG(bool, Name, Default, Required) 115#define FLAG_LONG_uint(Name, Default, Required) \ 116 UTIL_FLAGS_FLAG_LONG(uint32_t, Name, Default, Required) 117 118#define FLAG_SHORT_string(Name, Default, Required) \ 119 UTIL_FLAGS_FLAG_SHORT(std::string, Name, Default, Required) 120#define FLAG_SHORT_bool(Name, Default, Required) \ 121 UTIL_FLAGS_FLAG_SHORT(bool, Name, Default, Required) 122#define FLAG_SHORT_uint(Name, Default, Required) \ 123 UTIL_FLAGS_FLAG_SHORT(uint32_t, Name, Default, Required) 124 125namespace flags { 126 127// Parse the command-line arguments, checking flags, and separating positional 128// arguments from flags. 129// 130// * argv: the argv array received in the main function. This utility expects 131// the last pointer to 132// be NULL, as it should if coming from the main() function. 133// 134// Returns `true` if the parsing succeeds, `false` otherwise. 135bool Parse(const char** argv); 136 137} // namespace flags 138 139// ===================== BEGIN NON-PUBLIC SECTION ============================= 140// All the code below belongs to the implementation, and there is no guaranteed 141// around the API stability. Please do not use it directly. 142 143// Defines the static variable holding the flag, allowing access like 144// flags::my_flag. 145// By creating the FlagRegistration object, the flag can be added to 146// the global list. 147// The final `extern` definition is ONLY useful for clang-format: 148// - if the macro doesn't ends with a semicolon, clang-format goes wild. 149// - cannot disable clang-format for those macros on clang < 16. 150// (https://github.com/llvm/llvm-project/issues/54522) 151// - cannot allow trailing semi (-Wextra-semi). 152#define UTIL_FLAGS_FLAG(Type, Prefix, Name, Default, Required, IsShort) \ 153 namespace flags { \ 154 Flag<Type> Name(Default); \ 155 namespace { \ 156 static FlagRegistration Name##_registration(Name, Prefix #Name, Required, \ 157 IsShort); \ 158 } \ 159 } \ 160 extern flags::Flag<Type> flags::Name 161 162#define UTIL_FLAGS_FLAG_LONG(Type, Name, Default, Required) \ 163 UTIL_FLAGS_FLAG(Type, "--", Name, Default, Required, false) 164#define UTIL_FLAGS_FLAG_SHORT(Type, Name, Default, Required) \ 165 UTIL_FLAGS_FLAG(Type, "-", Name, Default, Required, true) 166 167namespace flags { 168 169// Just a wrapper around the flag value. 170template <typename T> 171struct Flag { 172 public: 173 Flag(T&& default_value) : value_(default_value) {} 174 Flag(Flag&& other) = delete; 175 Flag(const Flag& other) = delete; 176 177 const T& value() const { return value_; } 178 T& value() { return value_; } 179 180 private: 181 T value_; 182}; 183 184// To add support for new flag-types, this needs to be extended, and the visitor 185// below. 186using FlagType = std::variant<std::reference_wrapper<Flag<std::string>>, 187 std::reference_wrapper<Flag<bool>>, 188 std::reference_wrapper<Flag<uint32_t>>>; 189 190template <class> 191inline constexpr bool always_false_v = false; 192 193extern std::vector<std::string> positional_arguments; 194 195// Static class keeping track of the flags/arguments values. 196class FlagList { 197 struct FlagInfo { 198 FlagInfo(FlagType&& flag_, std::string&& name_, bool required_, 199 bool is_short_) 200 : flag(std::move(flag_)), 201 name(std::move(name_)), 202 required(required_), 203 is_short(is_short_) {} 204 205 FlagType flag; 206 std::string name; 207 bool required; 208 bool is_short; 209 }; 210 211 public: 212 template <typename T> 213 static void register_flag(Flag<T>& flag, std::string&& name, bool required, 214 bool is_short) { 215 get_flags().emplace_back(flag, std::move(name), required, is_short); 216 } 217 218 static bool parse(const char** argv); 219 220#ifdef TESTING 221 // Flags are supposed to be constant for the whole app execution, hence the 222 // static storage. Gtest doesn't fork before running a test, meaning we have 223 // to manually clear the context at teardown. 224 static void reset() { 225 get_flags().clear(); 226 positional_arguments.clear(); 227 } 228#endif 229 230 private: 231 static std::vector<FlagInfo>& get_flags() { 232 static std::vector<FlagInfo> flags; 233 return flags; 234 } 235 236 static bool parse_flag_info(FlagInfo& info, const char*** iterator); 237 static void print_usage(const char* binary_name, 238 const std::string& usage_format); 239}; 240 241template <typename T> 242struct FlagRegistration { 243 FlagRegistration(Flag<T>& flag, std::string&& name, bool required, 244 bool is_short) { 245 std::string fixed_name = name; 246 for (auto& c : fixed_name) { 247 if (c == '_') { 248 c = '-'; 249 } 250 } 251 252 FlagList::register_flag(flag, std::move(fixed_name), required, is_short); 253 } 254}; 255 256// Explicit deduction guide to avoid `-Wctad-maybe-unsupported`. 257template <typename T> 258FlagRegistration(Flag<T>&, std::string&&, bool, bool) -> FlagRegistration<T>; 259 260} // namespace flags 261 262#endif // INCLUDE_SPIRV_TOOLS_UTIL_FLAGS_HPP_ 263