[tint][utils]: Add utilities for command line flag parsing
Use this to clean up src/tint/cmd/main.cc
Change-Id: If527fb69b441b224549ac420d74f68e5edbca054
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/134504
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Auto-Submit: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 83fcbf5..ce779bc 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -237,6 +237,8 @@
"utils/bump_allocator.h",
"utils/castable.cc",
"utils/castable.h",
+ "utils/cli.cc",
+ "utils/cli.h",
"utils/compiler_macros.h",
"utils/concat.h",
"utils/crc32.h",
@@ -1822,6 +1824,7 @@
"utils/block_allocator_test.cc",
"utils/bump_allocator_test.cc",
"utils/castable_test.cc",
+ "utils/cli_test.cc",
"utils/crc32_test.cc",
"utils/defer_test.cc",
"utils/enum_set_test.cc",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index c20c850..2ecb97a 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -525,6 +525,8 @@
utils/bump_allocator.h
utils/castable.cc
utils/castable.h
+ utils/cli.cc
+ utils/cli.h
utils/compiler_macros.h
utils/concat.h
utils/crc32.h
@@ -1048,6 +1050,7 @@
utils/bitset_test.cc
utils/block_allocator_test.cc
utils/bump_allocator_test.cc
+ utils/cli_test.cc
utils/castable_test.cc
utils/crc32_test.cc
utils/defer_test.cc
diff --git a/src/tint/cmd/main.cc b/src/tint/cmd/main.cc
index 5d82479..ee9ab88 100644
--- a/src/tint/cmd/main.cc
+++ b/src/tint/cmd/main.cc
@@ -41,6 +41,8 @@
#include "src/tint/ast/module.h"
#include "src/tint/cmd/generate_external_texture_bindings.h"
#include "src/tint/cmd/helper.h"
+#include "src/tint/utils/cli.h"
+#include "src/tint/utils/defer.h"
#include "src/tint/utils/io/command.h"
#include "src/tint/utils/string.h"
#include "src/tint/utils/string_stream.h"
@@ -54,6 +56,36 @@
#include "src/tint/ir/module.h" // nogncheck
#endif // TINT_BUILD_IR
+#if TINT_BUILD_SPV_WRITER
+#define SPV_WRITER_ONLY(x) x
+#else
+#define SPV_WRITER_ONLY(x)
+#endif
+
+#if TINT_BUILD_WGSL_WRITER
+#define WGSL_WRITER_ONLY(x) x
+#else
+#define WGSL_WRITER_ONLY(x)
+#endif
+
+#if TINT_BUILD_MSL_WRITER
+#define MSL_WRITER_ONLY(x) x
+#else
+#define MSL_WRITER_ONLY(x)
+#endif
+
+#if TINT_BUILD_HLSL_WRITER
+#define HLSL_WRITER_ONLY(x) x
+#else
+#define HLSL_WRITER_ONLY(x)
+#endif
+
+#if TINT_BUILD_GLSL_WRITER
+#define GLSL_WRITER_ONLY(x) x
+#else
+#define GLSL_WRITER_ONLY(x)
+#endif
+
namespace {
/// Prints the given hash value in a format string that the end-to-end test runner can parse.
@@ -73,7 +105,6 @@
};
struct Options {
- bool show_help = false;
bool verbose = false;
std::string input_filename;
@@ -99,12 +130,12 @@
tint::reader::spirv::Options spirv_reader_options;
#endif
- std::vector<std::string> transforms;
+ tint::utils::Vector<std::string, 4> transforms;
std::string fxc_path;
std::string dxc_path;
std::string xcrun_path;
- std::unordered_map<std::string, double> overrides;
+ tint::utils::Hashmap<std::string, double, 8> overrides;
std::optional<tint::sem::BindingPoint> hlsl_root_constant_binding_point;
#if TINT_BUILD_IR
@@ -114,95 +145,9 @@
#if TINT_BUILD_SYNTAX_TREE_WRITER
bool dump_syntax_tree = false;
-#endif // TINB_BUILD_SYNTAX_TREE_WRITER
+#endif // TINT_BUILD_SYNTAX_TREE_WRITER
};
-const char kUsage[] = R"(Usage: tint [options] <input-file>
-
- options:
- --format <spirv|spvasm|wgsl|msl|hlsl|none> -- Output format.
- If not provided, will be inferred from output
- filename extension:
- .spvasm -> spvasm
- .spv -> spirv
- .wgsl -> wgsl
- .metal -> msl
- .hlsl -> hlsl
- If none matches, then default to SPIR-V assembly.
- -ep <name> -- Output single entry point
- --output-file <name> -- Output file name. Use "-" for standard output
- -o <name> -- Output file name. Use "-" for standard output
- --transform <name list> -- Runs transforms, name list is comma separated
- Available transforms:
-${transforms} --parse-only -- Stop after parsing the input
- --allow-non-uniform-derivatives -- When using SPIR-V input, allow non-uniform derivatives by
- inserting a module-scope directive to suppress any uniformity
- violations that may be produced.
- --disable-workgroup-init -- Disable workgroup memory zero initialization.
- --dump-inspector-bindings -- Dump reflection data about bindins to stdout.
- -h -- This help text
- --hlsl-root-constant-binding-point <group>,<binding> -- Binding point for root constant.
- Specify the binding point for generated uniform buffer
- used for num_workgroups in HLSL. If not specified, then
- default to binding 0 of the largest used group plus 1,
- or group 0 if no resource bound.
- --validate -- Validates the generated shader with all available validators
- --skip-hash <hash list> -- Skips validation if the hash of the output is equal to any
- of the hash codes in the comma separated list of hashes
- --print-hash -- Emit the hash of the output program
- --fxc -- Path to FXC dll, used to validate HLSL output.
- When specified, automatically enables HLSL validation with FXC
- --dxc -- Path to DXC executable, used to validate HLSL output.
- When specified, automatically enables HLSL validation with DXC
- --xcrun -- Path to xcrun executable, used to validate MSL output.
- When specified, automatically enables MSL validation
- --overrides -- Override values as IDENTIFIER=VALUE, comma-separated.
- --rename-all -- Renames all symbols.
-)";
-
-Format parse_format(const std::string& fmt) {
- (void)fmt;
-
-#if TINT_BUILD_SPV_WRITER
- if (fmt == "spirv") {
- return Format::kSpirv;
- }
- if (fmt == "spvasm") {
- return Format::kSpvAsm;
- }
-#endif // TINT_BUILD_SPV_WRITER
-
-#if TINT_BUILD_WGSL_WRITER
- if (fmt == "wgsl") {
- return Format::kWgsl;
- }
-#endif // TINT_BUILD_WGSL_WRITER
-
-#if TINT_BUILD_MSL_WRITER
- if (fmt == "msl") {
- return Format::kMsl;
- }
-#endif // TINT_BUILD_MSL_WRITER
-
-#if TINT_BUILD_HLSL_WRITER
- if (fmt == "hlsl") {
- return Format::kHlsl;
- }
-#endif // TINT_BUILD_HLSL_WRITER
-
-#if TINT_BUILD_GLSL_WRITER
- if (fmt == "glsl") {
- return Format::kGlsl;
- }
-#endif // TINT_BUILD_GLSL_WRITER
-
- if (fmt == "none") {
- return Format::kNone;
- }
-
- return Format::kUnknown;
-}
-
/// @param filename the filename to inspect
/// @returns the inferred format for the filename suffix
Format infer_format(const std::string& filename) {
@@ -238,112 +183,154 @@
return Format::kUnknown;
}
-std::vector<std::string> split_on_char(std::string list, char c) {
- std::vector<std::string> res;
+bool ParseArgs(tint::utils::VectorRef<std::string_view> arguments,
+ std::string transform_names,
+ Options* opts) {
+ using namespace tint::utils::cli; // NOLINT(build/namespaces)
- std::istringstream str(list);
- while (str.good()) {
- std::string substr;
- getline(str, substr, c);
- res.push_back(substr);
- }
- return res;
-}
+ tint::utils::Vector<EnumName<Format>, 8> format_enum_names{
+ EnumName(Format::kNone, "none"),
+ };
-std::vector<std::string> split_on_comma(std::string list) {
- return split_on_char(list, ',');
-}
+ SPV_WRITER_ONLY(format_enum_names.Emplace(Format::kSpirv, "spirv"));
+ SPV_WRITER_ONLY(format_enum_names.Emplace(Format::kSpvAsm, "spvasm"));
+ WGSL_WRITER_ONLY(format_enum_names.Emplace(Format::kWgsl, "wgsl"));
+ MSL_WRITER_ONLY(format_enum_names.Emplace(Format::kMsl, "msl"));
+ HLSL_WRITER_ONLY(format_enum_names.Emplace(Format::kHlsl, "hlsl"));
+ GLSL_WRITER_ONLY(format_enum_names.Emplace(Format::kGlsl, "glsl"));
-std::vector<std::string> split_on_equal(std::string list) {
- return split_on_char(list, '=');
-}
+ OptionSet options;
+ auto& fmt = options.Add<EnumOption<Format>>("format",
+ R"(Output format.
+If not provided, will be inferred from output filename extension:
+ .spvasm -> spvasm
+ .spv -> spirv
+ .wgsl -> wgsl
+ .metal -> msl
+ .hlsl -> hlsl)",
+ format_enum_names, ShortName{"f"});
+ TINT_DEFER(opts->format = fmt.value.value_or(Format::kUnknown));
-std::optional<uint64_t> parse_unsigned_number(std::string number) {
- for (char c : number) {
- if (!std::isdigit(c)) {
- // Found a non-digital char, return nullopt
- return std::nullopt;
- }
- }
-
- errno = 0;
- char* p_end;
- uint64_t result;
- // std::strtoull will not throw exception.
- result = std::strtoull(number.c_str(), &p_end, 10);
- if ((errno != 0) || (static_cast<size_t>(p_end - number.c_str()) != number.length())) {
- // Unexpected conversion result
- return std::nullopt;
- }
-
- return result;
-}
-
-bool ParseArgs(const std::vector<std::string>& args, Options* opts) {
- for (size_t i = 1; i < args.size(); ++i) {
- const std::string& arg = args[i];
- if (arg == "--format") {
- ++i;
- if (i >= args.size()) {
- std::cerr << "Missing value for --format argument." << std::endl;
- return false;
- }
- opts->format = parse_format(args[i]);
-
- if (opts->format == Format::kUnknown) {
- std::cerr << "Unknown output format: " << args[i] << std::endl;
- return false;
- }
- } else if (arg == "-ep") {
- if (i + 1 >= args.size()) {
- std::cerr << "Missing value for -ep" << std::endl;
- return false;
- }
- i++;
- opts->ep_name = args[i];
+ auto& ep = options.Add<StringOption>("entry-point", "Output single entry point",
+ ShortName{"ep"}, Parameter{"name"});
+ TINT_DEFER({
+ if (ep.value.has_value()) {
+ opts->ep_name = *ep.value;
opts->emit_single_entry_point = true;
+ }
+ });
- } else if (arg == "-o" || arg == "--output-name") {
- ++i;
- if (i >= args.size()) {
- std::cerr << "Missing value for " << arg << std::endl;
- return false;
- }
- opts->output_file = args[i];
+ auto& output = options.Add<StringOption>("output-name", "Output file name", ShortName{"o"},
+ Parameter{"name"});
+ TINT_DEFER(opts->output_file = output.value.value_or(""));
- } else if (arg == "-h" || arg == "--help") {
- opts->show_help = true;
- } else if (arg == "-v" || arg == "--verbose") {
- opts->verbose = true;
- } else if (arg == "--transform") {
- ++i;
- if (i >= args.size()) {
- std::cerr << "Missing value for " << arg << std::endl;
- return false;
- }
- opts->transforms = split_on_comma(args[i]);
- } else if (arg == "--parse-only") {
- opts->parse_only = true;
- } else if (arg == "--allow-non-uniform-derivatives") {
-#if TINT_BUILD_SPV_READER
- opts->spirv_reader_options.allow_non_uniform_derivatives = true;
-#else
- std::cerr << "Tint not built with the SPIR-V reader enabled" << std::endl;
- return false;
-#endif
- } else if (arg == "--disable-workgroup-init") {
- opts->disable_workgroup_init = true;
- } else if (arg == "--dump-inspector-bindings") {
- opts->dump_inspector_bindings = true;
- } else if (arg == "--validate") {
+ auto& fxc_path =
+ options.Add<StringOption>("fxc", R"(Path to FXC dll, used to validate HLSL output.
+When specified, automatically enables HLSL validation with FXC)",
+ Parameter{"path"});
+ TINT_DEFER(opts->fxc_path = fxc_path.value.value_or(""));
+
+ auto& dxc_path =
+ options.Add<StringOption>("dxc", R"(Path to DXC dll, used to validate HLSL output.
+When specified, automatically enables HLSL validation with DXC)",
+ Parameter{"path"});
+ TINT_DEFER(opts->dxc_path = dxc_path.value.value_or(""));
+
+ auto& xcrun =
+ options.Add<StringOption>("xcrun", R"(Path to xcrun executable, used to validate MSL output.
+When specified, automatically enables MSL validation)",
+ Parameter{"path"});
+ TINT_DEFER({
+ if (xcrun.value.has_value()) {
+ opts->xcrun_path = *xcrun.value;
opts->validate = true;
- } else if (arg == "--skip-hash") {
- ++i;
- if (i >= args.size()) {
- std::cerr << "Missing hash value for " << arg << std::endl;
- return false;
+ }
+ });
+
+#if TINT_BUILD_IR
+ auto& dump_ir = options.Add<BoolOption>("dump-ir", "Writes the IR to stdout", Alias{"emit-ir"},
+ Default{false});
+ TINT_DEFER(opts->dump_ir = *dump_ir.value);
+
+ auto& use_ir = options.Add<BoolOption>(
+ "use-ir", "Use the IR for writers and transforms when possible", Default{false});
+ TINT_DEFER(opts->use_ir = *use_ir.value);
+#endif // TINT_BUILD_IR
+
+ auto& verbose =
+ options.Add<BoolOption>("verbose", "Verbose output", ShortName{"v"}, Default{false});
+ TINT_DEFER(opts->verbose = *verbose.value);
+
+ auto& validate = options.Add<BoolOption>(
+ "validate", "Validates the generated shader with all available validators", Default{false});
+ TINT_DEFER(opts->validate = *validate.value);
+
+ auto& parse_only =
+ options.Add<BoolOption>("parse-only", "Stop after parsing the input", Default{false});
+ TINT_DEFER(opts->parse_only = *parse_only.value);
+
+#if TINT_BUILD_SPV_READER
+ auto& allow_nud =
+ options.Add<BoolOption>("allow-non-uniform-derivatives",
+ R"(When using SPIR-V input, allow non-uniform derivatives by
+inserting a module-scope directive to suppress any uniformity
+violations that may be produced)",
+ Default{false});
+ TINT_DEFER({
+ if (allow_nud.value.value_or(false)) {
+ opts->spirv_reader_options.allow_non_uniform_derivatives = true;
+ }
+ });
+#endif
+
+ auto& disable_wg_init = options.Add<BoolOption>(
+ "disable-workgroup-init", "Disable workgroup memory zero initialization", Default{false});
+ TINT_DEFER(opts->disable_workgroup_init = *disable_wg_init.value);
+
+ auto& rename_all = options.Add<BoolOption>("rename-all", "Renames all symbols", Default{false});
+ TINT_DEFER(opts->rename_all = *rename_all.value);
+
+ auto& dump_inspector_bindings = options.Add<BoolOption>(
+ "dump-inspector-bindings", "Dump reflection data about bindings to stdout",
+ Alias{"emit-inspector-bindings"}, Default{false});
+ TINT_DEFER(opts->dump_inspector_bindings = *dump_inspector_bindings.value);
+
+#if TINT_BUILD_SYNTAX_TREE_WRITER
+ auto& dump_ast = options.Add<BoolOption>("dump-ast", "Writes the AST to stdout",
+ Alias{"emit-ast"}, Default{false});
+ TINT_DEFER(opts->dump_ast = *dump_ast.value);
+#endif // TINT_BUILD_SYNTAX_TREE_WRITER
+
+ auto& print_hash = options.Add<BoolOption>("print-hash", "Emit the hash of the output program",
+ Default{false});
+ TINT_DEFER(opts->print_hash = *print_hash.value);
+
+ auto& transforms =
+ options.Add<StringOption>("transform", R"(Runs transforms, name list is comma separated
+Available transforms:
+)" + transform_names,
+ ShortName{"t"});
+ TINT_DEFER({
+ if (transforms.value.has_value()) {
+ for (auto transform : tint::utils::Split(*transforms.value, ",")) {
+ opts->transforms.Push(std::string(transform));
}
- for (auto hash : split_on_comma(args[i])) {
+ }
+ });
+
+ auto& hlsl_rc_bp = options.Add<StringOption>("hlsl-root-constant-binding-point",
+ R"(Binding point for root constant.
+Specify the binding point for generated uniform buffer
+used for num_workgroups in HLSL. If not specified, then
+default to binding 0 of the largest used group plus 1,
+or group 0 if no resource bound)");
+
+ auto& skip_hash = options.Add<StringOption>(
+ "skip-hash", R"(Skips validation if the hash of the output is equal to any
+of the hash codes in the comma separated list of hashes)");
+ TINT_DEFER({
+ if (skip_hash.value.has_value()) {
+ for (auto hash : tint::utils::Split(*skip_hash.value, ",")) {
uint32_t value = 0;
int base = 10;
if (hash.size() > 2 && hash[0] == '0' && (hash[1] == 'x' || hash[1] == 'X')) {
@@ -353,91 +340,82 @@
std::from_chars(hash.data(), hash.data() + hash.size(), value, base);
opts->skip_hash.emplace(value);
}
- } else if (arg == "--print-hash") {
- opts->print_hash = true;
- } else if (arg == "--fxc") {
- ++i;
- if (i >= args.size()) {
- std::cerr << "Missing value for " << arg << std::endl;
+ }
+ });
+
+ auto& overrides = options.Add<StringOption>(
+ "overrides", "Override values as IDENTIFIER=VALUE, comma-separated");
+
+ auto& help = options.Add<BoolOption>("help", "Show usage", ShortName{"h"});
+
+ auto show_usage = [&] {
+ std::cout << R"(Usage: tint [options] <input-file>
+
+Options:
+)";
+ options.ShowHelp(std::cout);
+ };
+
+ auto result = options.Parse(std::cerr, arguments);
+ if (!result) {
+ std::cerr << std::endl;
+ show_usage();
+ return false;
+ }
+ if (help.value.value_or(false)) {
+ show_usage();
+ return false;
+ }
+
+ if (overrides.value.has_value()) {
+ for (const auto& o : tint::utils::Split(*overrides.value, ",")) {
+ auto parts = tint::utils::Split(o, "=");
+ if (parts.Length() != 2) {
+ std::cerr << "override values must be of the form IDENTIFIER=VALUE";
return false;
}
- opts->fxc_path = args[i];
- } else if (arg == "--dxc") {
- ++i;
- if (i >= args.size()) {
- std::cerr << "Missing value for " << arg << std::endl;
+ auto value = tint::utils::ParseNumber<double>(parts[1]);
+ if (!value) {
+ std::cerr << "invalid override value: " << parts[1];
return false;
}
- opts->dxc_path = args[i];
-#if TINT_BUILD_IR
- } else if (arg == "--dump-ir") {
- opts->dump_ir = true;
- } else if (arg == "--use-ir") {
- opts->use_ir = true;
-#endif // TINT_BUILD_IR
-#if TINT_BUILD_SYNTAX_TREE_WRITER
- } else if (arg == "--dump-ast") {
- opts->dump_syntax_tree = true;
-#endif // TINT_BUILD_SYNTAX_TREE_WRITER
- } else if (arg == "--xcrun") {
- ++i;
- if (i >= args.size()) {
- std::cerr << "Missing value for " << arg << std::endl;
- return false;
- }
- opts->xcrun_path = args[i];
- opts->validate = true;
- } else if (arg == "--overrides") {
- ++i;
- if (i >= args.size()) {
- std::cerr << "Missing value for " << arg << std::endl;
- return false;
- }
- for (const auto& o : split_on_comma(args[i])) {
- auto parts = split_on_equal(o);
- opts->overrides.insert({parts[0], std::stod(parts[1])});
- }
- } else if (arg == "--rename-all") {
- ++i;
- opts->rename_all = true;
- } else if (arg == "--hlsl-root-constant-binding-point") {
- ++i;
- if (i >= args.size()) {
- std::cerr << "Missing value for " << arg << std::endl;
- return false;
- }
- auto binding_points = split_on_comma(args[i]);
- if (binding_points.size() != 2) {
- std::cerr << "Invalid binding point for " << arg << ": " << args[i] << std::endl;
- return false;
- }
- auto group = parse_unsigned_number(binding_points[0]);
- if ((!group.has_value()) || (group.value() > std::numeric_limits<uint32_t>::max())) {
- std::cerr << "Invalid group for " << arg << ": " << binding_points[0] << std::endl;
- return false;
- }
- auto binding = parse_unsigned_number(binding_points[1]);
- if ((!binding.has_value()) ||
- (binding.value() > std::numeric_limits<uint32_t>::max())) {
- std::cerr << "Invalid binding for " << arg << ": " << binding_points[1]
- << std::endl;
- return false;
- }
- opts->hlsl_root_constant_binding_point = tint::sem::BindingPoint{
- static_cast<uint32_t>(group.value()), static_cast<uint32_t>(binding.value())};
- } else if (!arg.empty()) {
- if (arg[0] == '-') {
- std::cerr << "Unrecognized option: " << arg << std::endl;
- return false;
- }
- if (!opts->input_filename.empty()) {
- std::cerr << "More than one input file specified: '" << opts->input_filename
- << "' and '" << arg << "'" << std::endl;
- return false;
- }
- opts->input_filename = arg;
+ opts->overrides.Add(std::string(parts[0]), value.Get());
}
}
+
+ if (hlsl_rc_bp.value.has_value()) {
+ auto binding_points = tint::utils::Split(*hlsl_rc_bp.value, ",");
+ if (binding_points.Length() != 2) {
+ std::cerr << "Invalid binding point for " << hlsl_rc_bp.name << ": "
+ << *hlsl_rc_bp.value << std::endl;
+ return false;
+ }
+ auto group = tint::utils::ParseUint32(binding_points[0]);
+ if (!group) {
+ std::cerr << "Invalid group for " << hlsl_rc_bp.name << ": " << binding_points[0]
+ << std::endl;
+ return false;
+ }
+ auto binding = tint::utils::ParseUint32(binding_points[1]);
+ if (!binding) {
+ std::cerr << "Invalid binding for " << hlsl_rc_bp.name << ": " << binding_points[1]
+ << std::endl;
+ return false;
+ }
+ opts->hlsl_root_constant_binding_point =
+ tint::sem::BindingPoint{group.Get(), binding.Get()};
+ }
+
+ auto files = result.Get();
+ if (files.Length() > 1) {
+ std::cerr << "More than one input file specified: "
+ << tint::utils::Join(Transform(files, tint::utils::Quote), ", ") << std::endl;
+ return false;
+ }
+ if (files.Length() == 1) {
+ opts->input_filename = files[0];
+ }
+
return true;
}
@@ -677,7 +655,7 @@
const char* default_xcrun_exe = "xcrun";
#endif
auto xcrun = tint::utils::Command::LookPath(
- options.xcrun_path.empty() ? default_xcrun_exe : options.xcrun_path);
+ options.xcrun_path.empty() ? default_xcrun_exe : std::string(options.xcrun_path));
if (xcrun.Found()) {
res = tint::val::Msl(xcrun.Path(), result.msl);
} else {
@@ -738,8 +716,8 @@
tint::val::Result dxc_res;
bool dxc_found = false;
if (options.validate || must_validate_dxc) {
- auto dxc =
- tint::utils::Command::LookPath(options.dxc_path.empty() ? "dxc" : options.dxc_path);
+ auto dxc = tint::utils::Command::LookPath(
+ options.dxc_path.empty() ? "dxc" : std::string(options.dxc_path));
if (dxc.Found()) {
dxc_found = true;
@@ -757,8 +735,8 @@
} else if (must_validate_dxc) {
// DXC was explicitly requested. Error if it could not be found.
dxc_res.failed = true;
- dxc_res.output =
- "DXC executable '" + options.dxc_path + "' not found. Cannot validate";
+ dxc_res.output = "DXC executable '" + std::string(options.dxc_path) +
+ "' not found. Cannot validate";
}
}
@@ -766,7 +744,7 @@
bool fxc_found = false;
if (options.validate || must_validate_fxc) {
auto fxc = tint::utils::Command::LookPath(
- options.fxc_path.empty() ? tint::val::kFxcDLLName : options.fxc_path);
+ options.fxc_path.empty() ? tint::val::kFxcDLLName : std::string(options.fxc_path));
#ifdef _WIN32
if (fxc.Found()) {
@@ -913,7 +891,14 @@
} // namespace
int main(int argc, const char** argv) {
- std::vector<std::string> args(argv, argv + argc);
+ tint::utils::Vector<std::string_view, 8> arguments;
+ for (int i = 1; i < argc; i++) {
+ std::string_view arg(argv[i]);
+ if (!arg.empty()) {
+ arguments.Push(argv[i]);
+ }
+ }
+
Options options;
tint::SetInternalCompilerErrorReporter(&tint::cmd::TintInternalCompilerErrorReporter);
@@ -928,11 +913,6 @@
};
#endif // TINT_BUILD_WGSL_WRITER
- if (!ParseArgs(args, &options)) {
- std::cerr << "Failed to parse arguments." << std::endl;
- return 1;
- }
-
struct TransformFactory {
const char* name;
/// Build and adds the transform to the transform manager.
@@ -970,22 +950,23 @@
tint::ast::transform::SubstituteOverride::Config cfg;
std::unordered_map<tint::OverrideId, double> values;
- values.reserve(options.overrides.size());
+ values.reserve(options.overrides.Count());
- for (const auto& [name, value] : options.overrides) {
+ for (auto override : options.overrides) {
+ const auto& name = override.key;
+ const auto& value = override.value;
if (name.empty()) {
- std::cerr << "empty override name";
+ std::cerr << "empty override name" << std::endl;
return false;
}
- if (isdigit(name[0])) {
- tint::OverrideId id{
- static_cast<decltype(tint::OverrideId::value)>(atoi(name.c_str()))};
+ if (auto num = tint::utils::ParseNumber<decltype(tint::OverrideId::value)>(name)) {
+ tint::OverrideId id{num.Get()};
values.emplace(id, value);
} else {
auto override_names = inspector.GetNamedOverrideIds();
auto it = override_names.find(name);
if (it == override_names.end()) {
- std::cerr << "unknown override '" << name << "'";
+ std::cerr << "unknown override '" << name << "'" << std::endl;
return false;
}
values.emplace(it->second, value);
@@ -1007,20 +988,8 @@
return names.str();
};
- if (options.show_help) {
- std::string usage = tint::utils::ReplaceAll(kUsage, "${transforms}", transform_names());
-#if TINT_BUILD_IR
- usage +=
- " --dump-ir -- Writes the IR to stdout\n"
- " --dump-ir-graph -- Writes the IR graph to 'tint.dot' as a dot graph\n"
- " --use-ir -- Use the IR for writers and transforms when possible\n";
-#endif // TINT_BUILD_IR
-#if TINT_BUILD_SYNTAX_TREE_WRITER
- usage += " --dump-ast -- Writes the AST to stdout\n";
-#endif // TINT_BUILD_SYNTAX_TREE_WRITER
-
- std::cout << usage << std::endl;
- return 0;
+ if (!ParseArgs(arguments, transform_names(), &options)) {
+ return 1;
}
// Implement output format defaults.
@@ -1138,12 +1107,12 @@
}
std::cerr << "Unknown transform: " << name << std::endl;
- std::cerr << "Available transforms: " << std::endl << transform_names();
+ std::cerr << "Available transforms: " << std::endl << transform_names() << std::endl;
return false;
};
// If overrides are provided, add the SubstituteOverride transform.
- if (!options.overrides.empty()) {
+ if (!options.overrides.IsEmpty()) {
if (!enable_transform("substitute_override")) {
return 1;
}
diff --git a/src/tint/utils/cli.cc b/src/tint/utils/cli.cc
new file mode 100644
index 0000000..2d3fd52
--- /dev/null
+++ b/src/tint/utils/cli.cc
@@ -0,0 +1,182 @@
+// Copyright 2023 The Tint Authors.
+//
+// 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.
+
+#include "src/tint/utils/cli.h"
+
+#include <algorithm>
+#include <sstream>
+#include <utility>
+
+#include "src/tint/utils/hashset.h"
+#include "src/tint/utils/string.h"
+#include "src/tint/utils/transform.h"
+
+namespace tint::utils::cli {
+
+Option::~Option() = default;
+
+void OptionSet::ShowHelp(std::ostream& s_out) {
+ utils::Vector<const Option*, 32> sorted_options;
+ for (auto* opt : options.Objects()) {
+ sorted_options.Push(opt);
+ }
+ sorted_options.Sort([](const Option* a, const Option* b) { return a->Name() < b->Name(); });
+
+ struct CmdInfo {
+ std::string left;
+ std::string right;
+ };
+ utils::Vector<CmdInfo, 64> cmd_infos;
+
+ for (auto* opt : sorted_options) {
+ {
+ std::stringstream left, right;
+ left << "--" << opt->Name();
+ if (auto param = opt->Parameter(); !param.empty()) {
+ left << " <" << param << ">";
+ }
+ right << opt->Description();
+ if (auto def = opt->DefaultValue(); !def.empty()) {
+ right << "\ndefault: " << def;
+ }
+ cmd_infos.Push({left.str(), right.str()});
+ }
+ if (auto alias = opt->Alias(); !alias.empty()) {
+ std::stringstream left, right;
+ left << "--" << alias;
+ right << "alias for --" << opt->Name();
+ cmd_infos.Push({left.str(), right.str()});
+ }
+ if (auto sn = opt->ShortName(); !sn.empty()) {
+ std::stringstream left, right;
+ left << " -" << sn;
+ right << "short name for --" << opt->Name();
+ cmd_infos.Push({left.str(), right.str()});
+ }
+ }
+
+ const size_t kMaxRightOffset = 30;
+
+ // Measure
+ size_t left_width = 0;
+ for (auto& cmd_info : cmd_infos) {
+ for (auto line : utils::Split(cmd_info.left, "\n")) {
+ if (line.length() < kMaxRightOffset) {
+ left_width = std::max(left_width, line.length());
+ }
+ }
+ }
+
+ // Print
+ left_width = std::min(left_width, kMaxRightOffset);
+
+ auto pad = [&](size_t n) {
+ while (n--) {
+ s_out << " ";
+ }
+ };
+
+ for (auto& cmd_info : cmd_infos) {
+ auto left_lines = utils::Split(cmd_info.left, "\n");
+ auto right_lines = utils::Split(cmd_info.right, "\n");
+
+ size_t num_lines = std::max(left_lines.Length(), right_lines.Length());
+ for (size_t i = 0; i < num_lines; i++) {
+ bool has_left = (i < left_lines.Length()) && !left_lines[i].empty();
+ bool has_right = (i < right_lines.Length()) && !right_lines[i].empty();
+ if (has_left) {
+ s_out << left_lines[i];
+ if (has_right) {
+ if (left_lines[i].length() > left_width) {
+ // Left exceeds column width.
+ // Insert a new line and indent to the right
+ s_out << std::endl;
+ pad(left_width);
+ } else {
+ pad(left_width - left_lines[i].length());
+ }
+ }
+ } else if (has_right) {
+ pad(left_width);
+ }
+ if (has_right) {
+ s_out << " " << right_lines[i];
+ }
+ s_out << std::endl;
+ }
+ }
+}
+
+Result<OptionSet::Unconsumed> OptionSet::Parse(std::ostream& s_err,
+ utils::VectorRef<std::string_view> arguments_raw) {
+ // Build a map of name to option, and set defaults
+ utils::Hashmap<std::string, Option*, 32> options_by_name;
+ for (auto* opt : options.Objects()) {
+ opt->SetDefault();
+ for (auto name : {opt->Name(), opt->Alias(), opt->ShortName()}) {
+ if (!name.empty() && !options_by_name.Add(name, opt)) {
+ s_err << "multiple options with name '" << name << "'" << std::endl;
+ return Failure;
+ }
+ }
+ }
+
+ // Canonicalize arguments by splitting '--foo=x' into '--foo' 'x'.
+ std::deque<std::string_view> arguments;
+ for (auto arg : arguments_raw) {
+ if (HasPrefix(arg, "-")) {
+ if (auto eq_idx = arg.find("="); eq_idx != std::string_view::npos) {
+ arguments.push_back(arg.substr(0, eq_idx));
+ arguments.push_back(arg.substr(eq_idx + 1));
+ continue;
+ }
+ }
+ arguments.push_back(arg);
+ }
+
+ utils::Hashset<Option*, 8> options_parsed;
+
+ Unconsumed unconsumed;
+ while (!arguments.empty()) {
+ auto arg = std::move(arguments.front());
+ arguments.pop_front();
+ auto name = TrimLeft(arg, [](char c) { return c == '-'; });
+ if (arg == name || name.length() == 0) {
+ unconsumed.Push(arg);
+ continue;
+ }
+ if (auto opt = options_by_name.Find(name)) {
+ if (auto err = (*opt)->Parse(arguments); !err.empty()) {
+ s_err << err << std::endl;
+ return Failure;
+ }
+ } else {
+ s_err << "unknown flag: " << arg << std::endl;
+ auto names = options_by_name.Keys();
+ auto alternatives =
+ Transform(names, [&](const std::string& s) { return std::string_view(s); });
+ utils::StringStream ss;
+ utils::SuggestAlternativeOptions opts;
+ opts.prefix = "--";
+ opts.list_possible_values = false;
+ SuggestAlternatives(arg, alternatives.Slice(), ss, opts);
+ s_err << ss.str();
+ return Failure;
+ }
+ }
+
+ return unconsumed;
+}
+
+} // namespace tint::utils::cli
diff --git a/src/tint/utils/cli.h b/src/tint/utils/cli.h
new file mode 100644
index 0000000..c1c3030
--- /dev/null
+++ b/src/tint/utils/cli.h
@@ -0,0 +1,410 @@
+// Copyright 2023 The Tint Authors.
+//
+// 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 SRC_TINT_UTILS_CLI_H_
+#define SRC_TINT_UTILS_CLI_H_
+
+#include <deque>
+#include <optional>
+#include <string>
+#include <utility>
+
+#include "src/tint/utils/block_allocator.h"
+#include "src/tint/utils/compiler_macros.h"
+#include "src/tint/utils/parse_num.h"
+#include "src/tint/utils/result.h"
+#include "src/tint/utils/string.h"
+#include "src/tint/utils/vector.h"
+
+namespace tint::utils::cli {
+
+/// Alias is a fluent-constructor helper for Options
+struct Alias {
+ /// The alias to apply to an Option
+ std::string value;
+
+ /// @param option the option to apply the alias to
+ template <typename T>
+ void Apply(T& option) {
+ option.alias = value;
+ }
+};
+
+/// ShortName is a fluent-constructor helper for Options
+struct ShortName {
+ /// The short-name to apply to an Option
+ std::string value;
+
+ /// @param option the option to apply the short name to
+ template <typename T>
+ void Apply(T& option) {
+ option.short_name = value;
+ }
+};
+
+/// Parameter is a fluent-constructor helper for Options
+struct Parameter {
+ /// The parameter name to apply to an Option
+ std::string value;
+
+ /// @param option the option to apply the parameter name to
+ template <typename T>
+ void Apply(T& option) {
+ option.parameter = value;
+ }
+};
+
+/// Default is a fluent-constructor helper for Options
+template <typename T>
+struct Default {
+ /// The default value to apply to an Option
+ T value;
+
+ /// @param option the option to apply the default value to
+ template <typename O>
+ void Apply(O& option) {
+ option.default_value = value;
+ }
+};
+
+/// Deduction guide for Default
+template <typename T>
+Default(T) -> Default<T>;
+
+/// Option is the base class for all command line options
+class Option {
+ public:
+ /// An alias to std::string, used to hold error messages.
+ using Error = std::string;
+
+ /// Destructor
+ virtual ~Option();
+
+ /// @return the name of the option, without any leading hyphens.
+ /// Example: 'help'
+ virtual std::string Name() const = 0;
+
+ /// @return the alias name of the option, without any leading hyphens. (optional)
+ /// Example: 'flag'
+ virtual std::string Alias() const = 0;
+
+ /// @return the shorter name of the option, without any leading hyphens. (optional)
+ /// Example: 'h'
+ virtual std::string ShortName() const = 0;
+
+ /// @return a string describing the parameter that the option expects.
+ /// Empty represents no expected parameter.
+ virtual std::string Parameter() const = 0;
+
+ /// @return a description of the option.
+ /// Example: 'shows this message'
+ virtual std::string Description() const = 0;
+
+ /// @return the default value of the option, or an empty string if there is no default value.
+ virtual std::string DefaultValue() const = 0;
+
+ /// Sets the option value to the default (called before arguments are parsed)
+ virtual void SetDefault() = 0;
+
+ /// Parses the option's arguments from the list of command line arguments, removing the consumed
+ /// arguments before returning. @p arguments will have already had the option's name consumed
+ /// before calling.
+ /// @param arguments the queue of unprocessed arguments. Parse() may take from the front of @p
+ /// arguments.
+ /// @return empty Error if successfully parsed, otherwise an error string.
+ virtual Error Parse(std::deque<std::string_view>& arguments) = 0;
+
+ protected:
+ /// An empty string, used to represent no-error.
+ static constexpr const char* Success = "";
+
+ /// @param expected the expected value(s) for the option
+ /// @return an Error message for a missing argument
+ Error ErrMissingArgument(std::string expected) const {
+ Error err = "missing value for option '--" + Name() + "'";
+ if (!expected.empty()) {
+ err += "Expected: " + expected;
+ }
+ return err;
+ }
+
+ /// @param got the argument value provided
+ /// @param reason the reason the argument is invalid (optional)
+ /// @return an Error message for an invalid argument
+ Error ErrInvalidArgument(std::string_view got, std::string reason) const {
+ Error err = "invalid value '" + std::string(got) + "' for option '--" + Name() + "'";
+ if (!reason.empty()) {
+ err += "\n" + reason;
+ }
+ return err;
+ }
+};
+
+/// OptionSet is a set of Options, which can parse the command line arguments.
+class OptionSet {
+ public:
+ /// Unconsumed is a list of unconsumed command line arguments
+ using Unconsumed = utils::Vector<std::string_view, 8>;
+
+ /// Constructs and returns a new Option to be owned by the OptionSet
+ /// @tparam T the Option type
+ /// @tparam ARGS the constructor argument types
+ /// @param args the constructor arguments
+ /// @return the constructed Option
+ template <typename T, typename... ARGS>
+ T& Add(ARGS&&... args) {
+ return *options.Create<T>(std::forward<ARGS>(args)...);
+ }
+
+ /// Prints to @p out the description of all the command line options.
+ /// @param out the output stream
+ void ShowHelp(std::ostream& out);
+
+ /// Parses all the options in @p options.
+ /// @param err the error stream
+ /// @param arguments the command line arguments, excluding the initial executable name
+ /// @return a Result holding a list of arguments that were not consumed as options
+ Result<Unconsumed> Parse(std::ostream& err, utils::VectorRef<std::string_view> arguments);
+
+ private:
+ /// The list of options to parse
+ utils::BlockAllocator<Option, 1024> options;
+};
+
+/// ValueOption is an option that accepts a single value
+template <typename T>
+class ValueOption : public Option {
+ static constexpr bool is_bool = std::is_same_v<T, bool>;
+ static constexpr bool is_number =
+ !is_bool && (std::is_integral_v<T> || std::is_floating_point_v<T>);
+ static constexpr bool is_string = std::is_same_v<T, std::string>;
+ static_assert(is_bool || is_number || is_string, "unsupported data type");
+
+ public:
+ /// The name of the option, without any leading hyphens.
+ std::string name;
+ /// The alias name of the option, without any leading hyphens.
+ std::string alias;
+ /// The shorter name of the option, without any leading hyphens.
+ std::string short_name;
+ /// A description of the option.
+ std::string description;
+ /// The default value.
+ std::optional<T> default_value;
+ /// The option value. Populated with Parse().
+ std::optional<T> value;
+ /// A string describing the name of the option's value.
+ std::string parameter = "value";
+
+ /// Constructor
+ ValueOption() = default;
+
+ /// Constructor
+ /// @param option_name the option name
+ /// @param option_description the option description
+ /// @param settings a number of fluent-constructor values that configure the option
+ /// @see ShortName, Parameter, Default
+ template <typename... SETTINGS>
+ ValueOption(std::string option_name, std::string option_description, SETTINGS&&... settings)
+ : name(std::move(option_name)), description(std::move(option_description)) {
+ (settings.Apply(*this), ...);
+ }
+
+ std::string Name() const override { return name; }
+
+ std::string Alias() const override { return alias; }
+
+ std::string ShortName() const override { return short_name; }
+
+ std::string Parameter() const override { return parameter; }
+
+ std::string Description() const override { return description; }
+
+ std::string DefaultValue() const override {
+ return default_value.has_value() ? ToString(*default_value) : "";
+ }
+
+ void SetDefault() override { value = default_value; }
+
+ Error Parse(std::deque<std::string_view>& arguments) override {
+ TINT_BEGIN_DISABLE_WARNING(UNREACHABLE_CODE);
+
+ if (arguments.empty()) {
+ if constexpr (is_bool) {
+ // Treat as flag (--blah)
+ value = true;
+ return Success;
+ } else {
+ return ErrMissingArgument(parameter);
+ }
+ }
+
+ auto arg = arguments.front();
+
+ if constexpr (is_number) {
+ auto result = ParseNumber<T>(arg);
+ if (result) {
+ value = result.Get();
+ arguments.pop_front();
+ return Success;
+ }
+ if (result.Failure() == ParseNumberError::kResultOutOfRange) {
+ return ErrInvalidArgument(arg, "value out of range");
+ }
+ return ErrInvalidArgument(arg, "failed to parse value");
+ } else if constexpr (is_string) {
+ value = arg;
+ arguments.pop_front();
+ return Success;
+ } else if constexpr (is_bool) {
+ if (arg == "true") {
+ value = true;
+ arguments.pop_front();
+ return Success;
+ }
+ if (arg == "false") {
+ value = false;
+ arguments.pop_front();
+ return Success;
+ }
+ // Next argument is assumed to be another option, or unconsumed argument.
+ // Treat as flag (--blah)
+ value = true;
+ return Success;
+ }
+
+ TINT_END_DISABLE_WARNING(UNREACHABLE_CODE);
+ }
+};
+
+/// BoolOption is an alias to ValueOption<bool>
+using BoolOption = ValueOption<bool>;
+
+/// StringOption is an alias to ValueOption<std::string>
+using StringOption = ValueOption<std::string>;
+
+/// EnumName is a pair of enum value and name.
+/// @tparam ENUM the enum type
+template <typename ENUM>
+struct EnumName {
+ /// Constructor
+ EnumName() = default;
+
+ /// Constructor
+ /// @param v the enum value
+ /// @param n the name of the enum value
+ EnumName(ENUM v, std::string n) : value(v), name(std::move(n)) {}
+
+ /// the enum value
+ ENUM value;
+ /// the name of the enum value
+ std::string name;
+};
+
+/// Deduction guide for EnumName
+template <typename ENUM>
+EnumName(ENUM, std::string) -> EnumName<ENUM>;
+
+/// EnumOption is an option that accepts an enumerator of values
+template <typename ENUM>
+class EnumOption : public Option {
+ public:
+ /// The name of the option, without any leading hyphens.
+ std::string name;
+ /// The alias name of the option, without any leading hyphens.
+ std::string alias;
+ /// The shorter name of the option, without any leading hyphens.
+ std::string short_name;
+ /// A description of the option.
+ std::string description;
+ /// The enum options as a pair of enum value to name
+ utils::Vector<EnumName<ENUM>, 8> enum_names;
+ /// The default value.
+ std::optional<ENUM> default_value;
+ /// The option value. Populated with Parse().
+ std::optional<ENUM> value;
+
+ /// Constructor
+ EnumOption() = default;
+
+ /// Constructor
+ /// @param option_name the option name
+ /// @param option_description the option description
+ /// @param names The enum options as a pair of enum value to name
+ /// @param settings a number of fluent-constructor values that configure the option
+ /// @see ShortName, Parameter, Default
+ template <typename... SETTINGS>
+ EnumOption(std::string option_name,
+ std::string option_description,
+ utils::VectorRef<EnumName<ENUM>> names,
+ SETTINGS&&... settings)
+ : name(std::move(option_name)),
+ description(std::move(option_description)),
+ enum_names(std::move(names)) {
+ (settings.Apply(*this), ...);
+ }
+
+ std::string Name() const override { return name; }
+
+ std::string ShortName() const override { return short_name; }
+
+ std::string Alias() const override { return alias; }
+
+ std::string Parameter() const override { return PossibleValues("|"); }
+
+ std::string Description() const override { return description; }
+
+ std::string DefaultValue() const override {
+ for (auto& enum_name : enum_names) {
+ if (enum_name.value == default_value) {
+ return enum_name.name;
+ }
+ }
+ return "";
+ }
+
+ void SetDefault() override { value = default_value; }
+
+ Error Parse(std::deque<std::string_view>& arguments) override {
+ if (arguments.empty()) {
+ return ErrMissingArgument("one of: " + PossibleValues(", "));
+ }
+ auto& arg = arguments.front();
+ for (auto& enum_name : enum_names) {
+ if (enum_name.name == arg) {
+ value = enum_name.value;
+ arguments.pop_front();
+ return Success;
+ }
+ }
+ return ErrInvalidArgument(arg, "Must be one of: " + PossibleValues(", "));
+ }
+
+ /// @param delimiter the delimiter between each enum option
+ /// @returns the accepted enum names delimited with @p delimiter
+ std::string PossibleValues(std::string delimiter) const {
+ std::string out;
+ for (auto& enum_name : enum_names) {
+ if (!out.empty()) {
+ out += delimiter;
+ }
+ out += enum_name.name;
+ }
+ return out;
+ }
+};
+
+} // namespace tint::utils::cli
+
+#endif // SRC_TINT_UTILS_CLI_H_
diff --git a/src/tint/utils/cli_test.cc b/src/tint/utils/cli_test.cc
new file mode 100644
index 0000000..cabbf14
--- /dev/null
+++ b/src/tint/utils/cli_test.cc
@@ -0,0 +1,299 @@
+// Copyright 2023 The Tint Authors.
+//
+// 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.
+
+#include "src/tint/utils/cli.h"
+
+#include <sstream>
+
+#include "gmock/gmock.h"
+#include "src/tint/utils/string.h"
+
+#include "src/tint/utils/transform.h" // Used by ToStringList()
+
+namespace tint::utils::cli {
+namespace {
+
+// Workaround for https://github.com/google/googletest/issues/3081
+// Remove when using C++20
+template <size_t N>
+utils::Vector<std::string, N> ToStringList(const utils::Vector<std::string_view, N>& views) {
+ return Transform(views, [](std::string_view view) { return std::string(view); });
+}
+
+using CLITest = testing::Test;
+
+TEST_F(CLITest, ShowHelp_ValueWithParameter) {
+ OptionSet opts;
+ opts.Add<ValueOption<int>>("my_option", "sets the awesome value");
+
+ std::stringstream out;
+ out << std::endl;
+ opts.ShowHelp(out);
+ EXPECT_EQ(out.str(), R"(
+--my_option <value> sets the awesome value
+)");
+}
+
+TEST_F(CLITest, ShowHelp_ValueWithAlias) {
+ OptionSet opts;
+ opts.Add<ValueOption<int>>("my_option", "sets the awesome value", Alias{"alias"});
+
+ std::stringstream out;
+ out << std::endl;
+ opts.ShowHelp(out);
+ EXPECT_EQ(out.str(), R"(
+--my_option <value> sets the awesome value
+--alias alias for --my_option
+)");
+}
+TEST_F(CLITest, ShowHelp_ValueWithShortName) {
+ OptionSet opts;
+ opts.Add<ValueOption<int>>("my_option", "sets the awesome value", ShortName{"a"});
+
+ std::stringstream out;
+ out << std::endl;
+ opts.ShowHelp(out);
+ EXPECT_EQ(out.str(), R"(
+--my_option <value> sets the awesome value
+ -a short name for --my_option
+)");
+}
+
+TEST_F(CLITest, ShowHelp_MultilineDesc) {
+ OptionSet opts;
+ opts.Add<ValueOption<int>>("an-option", R"(this is a
+multi-line description
+for an option
+)");
+
+ std::stringstream out;
+ out << std::endl;
+ opts.ShowHelp(out);
+ EXPECT_EQ(out.str(), R"(
+--an-option <value> this is a
+ multi-line description
+ for an option
+
+)");
+}
+
+TEST_F(CLITest, ShowHelp_LongName) {
+ OptionSet opts;
+ opts.Add<ValueOption<int>>("an-option-with-a-really-really-long-name",
+ "this is an option that has a silly long name", ShortName{"a"});
+
+ std::stringstream out;
+ out << std::endl;
+ opts.ShowHelp(out);
+ EXPECT_EQ(out.str(), R"(
+--an-option-with-a-really-really-long-name <value>
+ this is an option that has a silly long name
+ -a short name for --an-option-with-a-really-really-long-name
+)");
+}
+
+TEST_F(CLITest, ShowHelp_EnumValue) {
+ enum class E { X, Y, Z };
+
+ OptionSet opts;
+ opts.Add<EnumOption<E>>("my_enum_option", "sets the awesome value",
+ utils::Vector{
+ EnumName(E::X, "X"),
+ EnumName(E::Y, "Y"),
+ EnumName(E::Z, "Z"),
+ });
+
+ std::stringstream out;
+ out << std::endl;
+ opts.ShowHelp(out);
+ EXPECT_EQ(out.str(), R"(
+--my_enum_option <X|Y|Z> sets the awesome value
+)");
+}
+
+TEST_F(CLITest, ShowHelp_MixedValues) {
+ enum class E { X, Y, Z };
+
+ OptionSet opts;
+
+ opts.Add<ValueOption<int>>("option-a", "an integer");
+ opts.Add<BoolOption>("option-b", "a boolean");
+ opts.Add<EnumOption<E>>("option-c", "sets the awesome value",
+ utils::Vector{
+ EnumName(E::X, "X"),
+ EnumName(E::Y, "Y"),
+ EnumName(E::Z, "Z"),
+ });
+
+ std::stringstream out;
+ out << std::endl;
+ opts.ShowHelp(out);
+ EXPECT_EQ(out.str(), R"(
+--option-a <value> an integer
+--option-b <value> a boolean
+--option-c <X|Y|Z> sets the awesome value
+)");
+}
+
+TEST_F(CLITest, ParseBool_Flag) {
+ OptionSet opts;
+ auto& opt = opts.Add<BoolOption>("my_option", "a boolean value");
+
+ std::stringstream err;
+ auto res = opts.Parse(err, Split("--my_option unconsumed", " "));
+ ASSERT_TRUE(res) << err.str();
+ EXPECT_TRUE(err.str().empty());
+ EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre("unconsumed"));
+ EXPECT_EQ(opt.value, true);
+}
+
+TEST_F(CLITest, ParseBool_ExplicitTrue) {
+ OptionSet opts;
+ auto& opt = opts.Add<BoolOption>("my_option", "a boolean value");
+
+ std::stringstream err;
+ auto res = opts.Parse(err, Split("--my_option true", " "));
+ ASSERT_TRUE(res) << err.str();
+ EXPECT_TRUE(err.str().empty());
+ EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre());
+ EXPECT_EQ(opt.value, true);
+}
+
+TEST_F(CLITest, ParseBool_ExplicitFalse) {
+ OptionSet opts;
+ auto& opt = opts.Add<BoolOption>("my_option", "a boolean value", Default{true});
+
+ std::stringstream err;
+ auto res = opts.Parse(err, Split("--my_option false", " "));
+ ASSERT_TRUE(res) << err.str();
+ EXPECT_TRUE(err.str().empty());
+ EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre());
+ EXPECT_EQ(opt.value, false);
+}
+
+TEST_F(CLITest, ParseInt) {
+ OptionSet opts;
+ auto& opt = opts.Add<ValueOption<int>>("my_option", "an integer value");
+
+ std::stringstream err;
+ auto res = opts.Parse(err, Split("--my_option 42", " "));
+ ASSERT_TRUE(res) << err.str();
+ EXPECT_TRUE(err.str().empty());
+ EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre());
+ EXPECT_EQ(opt.value, 42);
+}
+
+TEST_F(CLITest, ParseUint64) {
+ OptionSet opts;
+ auto& opt = opts.Add<ValueOption<uint64_t>>("my_option", "a uint64_t value");
+
+ std::stringstream err;
+ auto res = opts.Parse(err, Split("--my_option 1000000", " "));
+ ASSERT_TRUE(res) << err.str();
+ EXPECT_TRUE(err.str().empty());
+ EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre());
+ EXPECT_EQ(opt.value, 1000000);
+}
+
+TEST_F(CLITest, ParseFloat) {
+ OptionSet opts;
+ auto& opt = opts.Add<ValueOption<float>>("my_option", "a float value");
+
+ std::stringstream err;
+ auto res = opts.Parse(err, Split("--my_option 1.25", " "));
+ ASSERT_TRUE(res) << err.str();
+ EXPECT_TRUE(err.str().empty());
+ EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre());
+ EXPECT_EQ(opt.value, 1.25f);
+}
+
+TEST_F(CLITest, ParseString) {
+ OptionSet opts;
+ auto& opt = opts.Add<StringOption>("my_option", "a string value");
+
+ std::stringstream err;
+ auto res = opts.Parse(err, Split("--my_option blah", " "));
+ ASSERT_TRUE(res) << err.str();
+ EXPECT_TRUE(err.str().empty());
+ EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre());
+ EXPECT_EQ(opt.value, "blah");
+}
+
+TEST_F(CLITest, ParseEnum) {
+ enum class E { X, Y, Z };
+
+ OptionSet opts;
+ auto& opt = opts.Add<EnumOption<E>>("my_option", "sets the awesome value",
+ utils::Vector{
+ EnumName(E::X, "X"),
+ EnumName(E::Y, "Y"),
+ EnumName(E::Z, "Z"),
+ });
+ std::stringstream err;
+ auto res = opts.Parse(err, Split("--my_option Y", " "));
+ ASSERT_TRUE(res) << err.str();
+ EXPECT_TRUE(err.str().empty());
+ EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre());
+ EXPECT_EQ(opt.value, E::Y);
+}
+
+TEST_F(CLITest, ParseShortName) {
+ OptionSet opts;
+ auto& opt = opts.Add<ValueOption<int>>("my_option", "an integer value", ShortName{"o"});
+
+ std::stringstream err;
+ auto res = opts.Parse(err, Split("-o 42", " "));
+ ASSERT_TRUE(res) << err.str();
+ EXPECT_TRUE(err.str().empty());
+ EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre());
+ EXPECT_EQ(opt.value, 42);
+}
+
+TEST_F(CLITest, ParseUnconsumed) {
+ OptionSet opts;
+ auto& opt = opts.Add<ValueOption<int32_t>>("my_option", "a int32_t value");
+
+ std::stringstream err;
+ auto res = opts.Parse(err, Split("abc --my_option -123 def", " "));
+ ASSERT_TRUE(res) << err.str();
+ EXPECT_TRUE(err.str().empty());
+ EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre("abc", "def"));
+ EXPECT_EQ(opt.value, -123);
+}
+
+TEST_F(CLITest, ParseUsingEquals) {
+ OptionSet opts;
+ auto& opt = opts.Add<ValueOption<int>>("my_option", "an int value");
+
+ std::stringstream err;
+ auto res = opts.Parse(err, Split("--my_option=123", " "));
+ ASSERT_TRUE(res) << err.str();
+ EXPECT_TRUE(err.str().empty());
+ EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre());
+ EXPECT_EQ(opt.value, 123);
+}
+
+TEST_F(CLITest, SetValueToDefault) {
+ OptionSet opts;
+ auto& opt = opts.Add<BoolOption>("my_option", "a boolean value", Default{true});
+
+ std::stringstream err;
+ auto res = opts.Parse(err, utils::Empty);
+ ASSERT_TRUE(res) << err.str();
+ EXPECT_TRUE(err.str().empty());
+ EXPECT_EQ(opt.value, true);
+}
+
+} // namespace
+} // namespace tint::utils::cli
diff --git a/src/tint/utils/parse_num.h b/src/tint/utils/parse_num.h
index 6d4fcb4..7c4ff7e 100644
--- a/src/tint/utils/parse_num.h
+++ b/src/tint/utils/parse_num.h
@@ -18,6 +18,7 @@
#include <optional>
#include <string>
+#include "src/tint/utils/compiler_macros.h"
#include "src/tint/utils/result.h"
namespace tint::utils {
@@ -78,6 +79,8 @@
/// @returns the string @p str parsed as a uint8_t
Result<uint8_t, ParseNumberError> ParseUint8(std::string_view str);
+TINT_BEGIN_DISABLE_WARNING(UNREACHABLE_CODE);
+
/// @param str the string
/// @returns the string @p str parsed as a the number @p T
template <typename T>
@@ -121,6 +124,8 @@
return ParseNumberError::kUnparsable;
}
+TINT_END_DISABLE_WARNING(UNREACHABLE_CODE);
+
} // namespace tint::utils
#endif // SRC_TINT_UTILS_PARSE_NUM_H_
diff --git a/src/tint/utils/string_test.cc b/src/tint/utils/string_test.cc
index 676c341..63f72a6 100644
--- a/src/tint/utils/string_test.cc
+++ b/src/tint/utils/string_test.cc
@@ -17,9 +17,18 @@
#include "gmock/gmock.h"
#include "src/tint/utils/string_stream.h"
+#include "src/tint/utils/transform.h" // Used by ToStringList()
+
namespace tint::utils {
namespace {
+// Workaround for https://github.com/google/googletest/issues/3081
+// Remove when using C++20
+template <size_t N>
+utils::Vector<std::string, N> ToStringList(const utils::Vector<std::string_view, N>& views) {
+ return Transform(views, [](std::string_view view) { return std::string(view); });
+}
+
TEST(StringTest, ReplaceAll) {
EXPECT_EQ("xybbcc", ReplaceAll("aabbcc", "aa", "xy"));
EXPECT_EQ("aaxycc", ReplaceAll("aabbcc", "bb", "xy"));
@@ -176,16 +185,15 @@
EXPECT_EQ("'meow'", Quote("meow"));
}
-#if 0 // Enable when moved to C++20 (https://github.com/google/googletest/issues/3081)
TEST(StringTest, Split) {
- EXPECT_THAT(Split("", ","), testing::ElementsAre(""));
- EXPECT_THAT(Split("cat", ","), testing::ElementsAre("cat"));
- EXPECT_THAT(Split("cat,", ","), testing::ElementsAre("cat", ""));
- EXPECT_THAT(Split(",cat", ","), testing::ElementsAre("", "cat"));
- EXPECT_THAT(Split("cat,dog,fish", ","), testing::ElementsAre("cat", "dog", "fish"));
- EXPECT_THAT(Split("catdogfish", "dog"), testing::ElementsAre("cat", "fish"));
+ EXPECT_THAT(ToStringList(Split("", ",")), testing::ElementsAre(""));
+ EXPECT_THAT(ToStringList(Split("cat", ",")), testing::ElementsAre("cat"));
+ EXPECT_THAT(ToStringList(Split("cat,", ",")), testing::ElementsAre("cat", ""));
+ EXPECT_THAT(ToStringList(Split(",cat", ",")), testing::ElementsAre("", "cat"));
+ EXPECT_THAT(ToStringList(Split("cat,dog,fish", ",")),
+ testing::ElementsAre("cat", "dog", "fish"));
+ EXPECT_THAT(ToStringList(Split("catdogfish", "dog")), testing::ElementsAre("cat", "fish"));
}
-#endif
TEST(StringTest, Join) {
EXPECT_EQ(Join(utils::Vector<int, 1>{}, ","), "");