| // Copyright (c) 2023 Google LLC. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #ifndef INCLUDE_SPIRV_TOOLS_UTIL_FLAGS_HPP_ |
| #define INCLUDE_SPIRV_TOOLS_UTIL_FLAGS_HPP_ |
| |
| #include <stdint.h> |
| |
| #include <functional> |
| #include <string> |
| #include <variant> |
| #include <vector> |
| |
| // This file provides some utils to define a command-line interface with |
| // required and optional flags. |
| // - Flag order is not checked. |
| // - Currently supported flag types: BOOLEAN, STRING |
| // - As with most nix tools, using '--' in the command-line means all following |
| // tokens will be considered positional |
| // arguments. |
| // Example: binary -g -- -g --some-other-flag |
| // - the first `-g` is a flag. |
| // - the second `-g` is not a flag. |
| // - `--some-other-flag` is not a flag. |
| // - Both long-form and short-form flags are supported, but boolean flags don't |
| // support split boolean literals (short and long form). |
| // Example: |
| // -g : allowed, sets g to true. |
| // --my-flag : allowed, sets --my-flag to true. |
| // --my-flag=true : allowed, sets --my-flag to true. |
| // --my-flag true : NOT allowed. |
| // -g true : NOT allowed. |
| // --my-flag=TRUE : NOT allowed. |
| // |
| // - This implementation also supports string flags: |
| // -o myfile.spv : allowed, sets -o to `myfile.spv`. |
| // --output=myfile.spv : allowed, sets --output to `myfile.spv`. |
| // --output myfile.spv : allowd, sets --output to `myfile.spv`. |
| // |
| // Note: then second token is NOT checked for hyphens. |
| // --output -file.spv |
| // flag name: `output` |
| // flag value: `-file.spv` |
| // |
| // - This implementation generates flag at compile time. Meaning flag names |
| // must be valid C++ identifiers. |
| // However, flags are usually using hyphens for word separation. Hence |
| // renaming is done behind the scenes. Example: |
| // // Declaring a long-form flag. |
| // FLAG_LONG_bool(my_flag, [...]) |
| // |
| // -> in the code: flags::my_flag.value() |
| // -> command-line: --my-flag |
| // |
| // - The only additional lexing done is around '='. Otherwise token list is |
| // processed as received in the Parse() |
| // function. |
| // Lexing the '=' sign: |
| // - This is only done when parsing a long-form flag name. |
| // - the first '=' found is considered a marker for long-form, splitting |
| // the token into 2. |
| // Example: --option=value=abc -> [--option, value=abc] |
| // |
| // In most cases, you want to define some flags, parse them, and query them. |
| // Here is a small code sample: |
| // |
| // ```c |
| // // Defines a '-h' boolean flag for help printing, optional. |
| // FLAG_SHORT_bool(h, /*default=*/ false, "Print the help.", false); |
| // // Defines a '--my-flag' string flag, required. |
| // FLAG_LONG_string(my_flag, /*default=*/ "", "A magic flag!", true); |
| // |
| // int main(int argc, const char** argv) { |
| // if (!flags::Parse(argv)) { |
| // return -1; |
| // } |
| // |
| // if (flags::h.value()) { |
| // printf("usage: my-bin --my-flag=<value>\n"); |
| // return 0; |
| // } |
| // |
| // printf("flag value: %s\n", flags::my_flag.value().c_str()); |
| // for (const std::string& arg : flags::positional_arguments) { |
| // printf("arg: %s\n", arg.c_str()); |
| // } |
| // return 0; |
| // } |
| // ```c |
| |
| // Those macros can be used to define flags. |
| // - They should be used in the global scope. |
| // - Underscores in the flag variable name are replaced with hyphens ('-'). |
| // |
| // Example: |
| // FLAG_SHORT_bool(my_flag, false, "some help", false); |
| // - in the code: flags::my_flag |
| // - command line: --my-flag=true |
| // |
| #define FLAG_LONG_string(Name, Default, Required) \ |
| UTIL_FLAGS_FLAG_LONG(std::string, Name, Default, Required) |
| #define FLAG_LONG_bool(Name, Default, Required) \ |
| UTIL_FLAGS_FLAG_LONG(bool, Name, Default, Required) |
| #define FLAG_LONG_uint(Name, Default, Required) \ |
| UTIL_FLAGS_FLAG_LONG(uint32_t, Name, Default, Required) |
| |
| #define FLAG_SHORT_string(Name, Default, Required) \ |
| UTIL_FLAGS_FLAG_SHORT(std::string, Name, Default, Required) |
| #define FLAG_SHORT_bool(Name, Default, Required) \ |
| UTIL_FLAGS_FLAG_SHORT(bool, Name, Default, Required) |
| #define FLAG_SHORT_uint(Name, Default, Required) \ |
| UTIL_FLAGS_FLAG_SHORT(uint32_t, Name, Default, Required) |
| |
| namespace flags { |
| |
| // Parse the command-line arguments, checking flags, and separating positional |
| // arguments from flags. |
| // |
| // * argv: the argv array received in the main function. This utility expects |
| // the last pointer to |
| // be NULL, as it should if coming from the main() function. |
| // |
| // Returns `true` if the parsing succeeds, `false` otherwise. |
| bool Parse(const char** argv); |
| |
| } // namespace flags |
| |
| // ===================== BEGIN NON-PUBLIC SECTION ============================= |
| // All the code below belongs to the implementation, and there is no guaranteed |
| // around the API stability. Please do not use it directly. |
| |
| // Defines the static variable holding the flag, allowing access like |
| // flags::my_flag. |
| // By creating the FlagRegistration object, the flag can be added to |
| // the global list. |
| // The final `extern` definition is ONLY useful for clang-format: |
| // - if the macro doesn't ends with a semicolon, clang-format goes wild. |
| // - cannot disable clang-format for those macros on clang < 16. |
| // (https://github.com/llvm/llvm-project/issues/54522) |
| // - cannot allow trailing semi (-Wextra-semi). |
| #define UTIL_FLAGS_FLAG(Type, Prefix, Name, Default, Required, IsShort) \ |
| namespace flags { \ |
| Flag<Type> Name(Default); \ |
| namespace { \ |
| static FlagRegistration Name##_registration(Name, Prefix #Name, Required, \ |
| IsShort); \ |
| } \ |
| } \ |
| extern flags::Flag<Type> flags::Name |
| |
| #define UTIL_FLAGS_FLAG_LONG(Type, Name, Default, Required) \ |
| UTIL_FLAGS_FLAG(Type, "--", Name, Default, Required, false) |
| #define UTIL_FLAGS_FLAG_SHORT(Type, Name, Default, Required) \ |
| UTIL_FLAGS_FLAG(Type, "-", Name, Default, Required, true) |
| |
| namespace flags { |
| |
| // Just a wrapper around the flag value. |
| template <typename T> |
| struct Flag { |
| public: |
| Flag(T&& default_value) : value_(default_value) {} |
| Flag(Flag&& other) = delete; |
| Flag(const Flag& other) = delete; |
| |
| const T& value() const { return value_; } |
| T& value() { return value_; } |
| |
| private: |
| T value_; |
| }; |
| |
| // To add support for new flag-types, this needs to be extended, and the visitor |
| // below. |
| using FlagType = std::variant<std::reference_wrapper<Flag<std::string>>, |
| std::reference_wrapper<Flag<bool>>, |
| std::reference_wrapper<Flag<uint32_t>>>; |
| |
| template <class> |
| inline constexpr bool always_false_v = false; |
| |
| extern std::vector<std::string> positional_arguments; |
| |
| // Static class keeping track of the flags/arguments values. |
| class FlagList { |
| struct FlagInfo { |
| FlagInfo(FlagType&& flag_, std::string&& name_, bool required_, |
| bool is_short_) |
| : flag(std::move(flag_)), |
| name(std::move(name_)), |
| required(required_), |
| is_short(is_short_) {} |
| |
| FlagType flag; |
| std::string name; |
| bool required; |
| bool is_short; |
| }; |
| |
| public: |
| template <typename T> |
| static void register_flag(Flag<T>& flag, std::string&& name, bool required, |
| bool is_short) { |
| get_flags().emplace_back(flag, std::move(name), required, is_short); |
| } |
| |
| static bool parse(const char** argv); |
| |
| #ifdef TESTING |
| // Flags are supposed to be constant for the whole app execution, hence the |
| // static storage. Gtest doesn't fork before running a test, meaning we have |
| // to manually clear the context at teardown. |
| static void reset() { |
| get_flags().clear(); |
| positional_arguments.clear(); |
| } |
| #endif |
| |
| private: |
| static std::vector<FlagInfo>& get_flags() { |
| static std::vector<FlagInfo> flags; |
| return flags; |
| } |
| |
| static bool parse_flag_info(FlagInfo& info, const char*** iterator); |
| static void print_usage(const char* binary_name, |
| const std::string& usage_format); |
| }; |
| |
| template <typename T> |
| struct FlagRegistration { |
| FlagRegistration(Flag<T>& flag, std::string&& name, bool required, |
| bool is_short) { |
| std::string fixed_name = name; |
| for (auto& c : fixed_name) { |
| if (c == '_') { |
| c = '-'; |
| } |
| } |
| |
| FlagList::register_flag(flag, std::move(fixed_name), required, is_short); |
| } |
| }; |
| |
| // Explicit deduction guide to avoid `-Wctad-maybe-unsupported`. |
| template <typename T> |
| FlagRegistration(Flag<T>&, std::string&&, bool, bool) -> FlagRegistration<T>; |
| |
| } // namespace flags |
| |
| #endif // INCLUDE_SPIRV_TOOLS_UTIL_FLAGS_HPP_ |