SPIRV-Tools/tools/util/flags.h
Nathan Gauër 2e0f4b52c9
tools: refactorize tools flags parsing. (#5111)
* tools: refactorize tools flags parsing.

Each SPIR-V tool was handing their own flag parsing. This PR
adds a new flag parsing utility that should be flexible enough
for our usage, but generic enough so it can be shared across tools while
remaining simple to use.

* cfg: replace cfg option parsing with the new one.

* change spirv-dis parsing + title + summary

* clang format

* flags: fix static init fiasco & remove help

Static initialization order is important, and was
working just by sheer luck.

Also removing the help generation tooling. It's less flexible than the
hand-written string, and making it as-good and as-flexible brings too
much complexity.

* review feedback
2023-02-27 18:45:14 +01:00

252 lines
8.8 KiB
C++

// 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 <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_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)
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>>>;
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);
}
};
} // namespace flags
#endif // INCLUDE_SPIRV_TOOLS_UTIL_FLAGS_HPP_