+#include "harness.h"
+#include <charconv>
+#include <cstdlib>
+#include <functional>
+#include <initializer_list>
+#include <iostream>
+#include <map>
+#include <optional>
+#include <ostream>
+#include <string_view>
+#include <system_error>
+#include <type_traits>
+#include <vector>
+
+using namespace std::literals;
+
+enum class OptionValueKind
+{
+ None,
+ Required,
+};
+
+class OptionsParser;
+
+struct Option final
+{
+ char short_name = '\0';
+ std::string_view long_name = "", description = "";
+ bool required = false;
+ bool all_other_args_not_required = false;
+ OptionValueKind value_kind = OptionValueKind::None;
+ std::function<void(OptionsParser &parser,
+ std::optional<std::string_view> value)>
+ parse_value;
+ bool has_short_name() const
+ {
+ return short_name != '\0';
+ }
+ bool has_long_name() const
+ {
+ return !long_name.empty();
+ }
+ friend std::ostream &operator<<(std::ostream &os, const Option &option)
+ {
+ if (option.has_long_name())
+ {
+ os << "--" << option.long_name;
+ }
+ else if (option.has_short_name())
+ {
+ os << "-" << option.short_name;
+ }
+ else
+ {
+ os << "--<unnamed>";
+ }
+ return os;
+ }
+};
+
+class Options final
+{
+ friend class OptionsParser;
+
+ private:
+ std::vector<Option> options;
+ std::map<char, std::size_t> short_options;
+ std::map<std::string_view, std::size_t> long_options;
+
+ public:
+ Options(std::initializer_list<Option> options_)
+ : options(options_), short_options(), long_options()
+ {
+ for (std::size_t i = 0; i < options.size(); i++)
+ {
+ auto &option = options[i];
+ if (option.has_short_name())
+ short_options[option.short_name] = i;
+ if (option.has_long_name())
+ long_options[option.long_name] = i;
+ }
+ }
+
+ public:
+ std::vector<std::string_view> parse(const char *const *argv) const;
+};
+
+class OptionsParser final
+{
+ private:
+ const Options &options;
+ const char *const *argv;
+ std::string_view argv0;
+ std::vector<bool> seen_options;
+ bool check_required = true;
+ std::optional<std::size_t> current_option_index;
+
+ private:
+ const Option ¤t_option()
+ {
+ static const Option default_option{};
+ if (current_option_index)
+ {
+ return options.options[*current_option_index];
+ }
+ else
+ {
+ return default_option;
+ }
+ }
+
+ public:
+ OptionsParser(const Options &options, const char *const *argv)
+ : options(options), argv(argv + 1),
+ argv0(argv[0] ? argv[0] : "<none>"),
+ seen_options(options.options.size(), false), current_option_index()
+ {
+ if (!argv[0])
+ {
+ static const char *null[] = {nullptr};
+ this->argv = null;
+ help_and_exit("missing program name");
+ }
+ }
+
+ private:
+ void parse_option(std::size_t option_index, std::string_view prefix,
+ std::string_view name,
+ std::optional<std::string_view> value)
+ {
+ auto &option = options.options[option_index];
+ switch (option.value_kind)
+ {
+ case OptionValueKind::None:
+ if (value)
+ {
+ help_and_exit("value not allowed for ", prefix, name);
+ }
+ break;
+ case OptionValueKind::Required:
+ if (!value)
+ {
+ if (*argv)
+ {
+ value = *argv++;
+ }
+ else
+ {
+ help_and_exit("missing value for ", prefix, name);
+ }
+ }
+ break;
+ }
+ seen_options[option_index] = true;
+ if (option.all_other_args_not_required)
+ check_required = false;
+ current_option_index = option_index;
+ option.parse_value(*this, value);
+ current_option_index = std::nullopt;
+ }
+
+ public:
+ std::vector<std::string_view> parse()
+ {
+ std::vector<std::string_view> retval;
+ constexpr auto npos = std::string_view::npos;
+ while (*argv)
+ {
+ std::string_view arg = *argv++;
+ static constexpr std::string_view long_prefix = "--"sv;
+ static constexpr std::string_view short_prefix = "-"sv;
+ if (arg.rfind(long_prefix, 0) == 0) // it starts with `--`
+ {
+ arg.remove_prefix(long_prefix.size());
+ auto eq = arg.find('=');
+ if (eq == 0)
+ {
+ eq = npos;
+ }
+ if (arg.empty()) // just `--`
+ {
+ while (*argv)
+ {
+ retval.push_back(*argv++);
+ }
+ break;
+ }
+ auto name = arg.substr(0, eq);
+ std::optional<std::string_view> value;
+ if (eq != npos)
+ {
+ value = arg.substr(eq + 1);
+ }
+ auto iter = options.long_options.find(name);
+ if (iter == options.long_options.end())
+ {
+ help_and_exit("unknown option: ", long_prefix, name);
+ }
+ auto option_index = iter->second;
+ parse_option(option_index, long_prefix, name, value);
+ continue;
+ }
+ else if (arg.rfind(short_prefix, 0) == 0) // it starts with `-`
+ {
+ arg.remove_prefix(short_prefix.size());
+ if (arg.empty()) // just `-`
+ {
+ retval.push_back(short_prefix);
+ continue;
+ }
+ while (!arg.empty())
+ {
+ auto name = arg.substr(0, 1);
+ arg.remove_prefix(1);
+ auto iter = options.short_options.find(name[0]);
+ if (iter == options.short_options.end())
+ {
+ help_and_exit("unknown option: ", short_prefix, name);
+ }
+ auto option_index = iter->second;
+ std::optional<std::string_view> value;
+ switch (options.options[option_index].value_kind)
+ {
+ case OptionValueKind::None:
+ break;
+ case OptionValueKind::Required:
+ auto eq = arg.rfind('=', 0);
+ if (eq != npos)
+ {
+ value = arg.substr(eq + 1);
+ }
+ else if (!arg.empty())
+ {
+ value = arg;
+ }
+ arg = "";
+ break;
+ }
+ parse_option(option_index, short_prefix, name, value);
+ }
+ continue;
+ }
+ else
+ {
+ retval.push_back(arg);
+ }
+ }
+ if (check_required)
+ {
+ for (std::size_t i = 0; i < options.options.size(); i++)
+ {
+ auto &option = options.options[i];
+ if (option.required && !seen_options[i])
+ {
+ help_and_exit("missing required option ", option);
+ }
+ }
+ }
+ return retval;
+ }
+ template <typename... Options>
+ [[noreturn]] void help_and_exit(const Options &...error_msg) const
+ {
+ auto &os = sizeof...(error_msg) == 0 ? std::cout : std::cerr;
+ if (sizeof...(error_msg) != 0)
+ {
+ ((os << "Error: ") << ... << error_msg) << std::endl;
+ }
+ os << "Usage: " << argv0;
+ for (auto &option : options.options)
+ {
+ if (!option.required)
+ {
+ os << "[";
+ }
+ auto sep = "";
+ if (option.has_short_name())
+ {
+ os << "-" << option.short_name;
+ sep = "|";
+ }
+ if (option.has_long_name())
+ {
+ os << sep << "--" << option.long_name;
+ }
+ switch (option.value_kind)
+ {
+ case OptionValueKind::None:
+ break;
+ // TODO: case OptionValueKind::Optional:
+ case OptionValueKind::Required:
+ os << " <value>";
+ break;
+ }
+ if (!option.required)
+ {
+ os << "]";
+ }
+ }
+ os << std::endl << "Options:" << std::endl;
+ for (auto &option : options.options)
+ {
+ auto sep = "";
+ if (option.has_short_name())
+ {
+ os << "-" << option.short_name;
+ sep = "|";
+ }
+ if (option.has_long_name())
+ {
+ os << sep << "--" << option.long_name;
+ }
+ os << " " << option.description << std::endl;
+ }
+ std::exit(sizeof...(error_msg) == 0 ? 0 : 1);
+ }
+ template <typename Int>
+ std::enable_if_t<std::is_integral_v<Int>, void> parse_int(
+ std::optional<std::string_view> value, Int &i_value)
+ {
+ i_value = Int();
+ if (!value)
+ {
+ help_and_exit("missing value for ", current_option());
+ }
+ auto str = *value;
+ int base = 10;
+ if (0 == str.rfind("0x", 0) || 0 == str.rfind("0X", 0))
+ {
+ base = 16;
+ str.remove_prefix(2);
+ }
+ else if (0 == str.rfind("0o", 0) || 0 == str.rfind("0O", 0))
+ {
+ base = 8;
+ str.remove_prefix(2);
+ }
+ else if (0 == str.rfind("0b", 0) || 0 == str.rfind("0B", 0))
+ {
+ base = 2;
+ str.remove_prefix(2);
+ }
+ std::from_chars_result result = std::from_chars(
+ str.data(), str.data() + str.size(), i_value, base);
+ if (result.ptr != str.data() + str.size())
+ {
+ result.ec = std::errc::invalid_argument;
+ }
+ if (result.ec == std::errc::result_out_of_range)
+ {
+ help_and_exit("value out of range: ", current_option(), "=",
+ *value);
+ }
+ else if (result.ec != std::errc())
+ {
+ help_and_exit("invalid value for: ", current_option());
+ }
+ }
+ template <typename Int>
+ std::enable_if_t<std::is_integral_v<Int>, void> parse_int(
+ std::optional<std::string_view> value, std::optional<Int> &i_value,
+ bool required = true)
+ {
+ if (!required && !value)
+ {
+ i_value = std::nullopt;
+ return;
+ }
+ i_value.emplace();
+ this->parse_int(value, i_value.value());
+ }
+};
+
+inline std::vector<std::string_view> Options::parse(
+ const char *const *argv) const
+{
+ return OptionsParser(*this, argv).parse();
+}
+
+int main(int, char **argv)
+{
+ Config config{};
+ Options options{
+ Option{
+ .short_name = 'h',
+ .long_name = "help",
+ .description = "Display usage and exit.",
+ .all_other_args_not_required = true,
+ .parse_value = [](OptionsParser &parser,
+ auto) { parser.help_and_exit(); },
+ },
+ Option{.short_name = 'j',
+ .long_name = "thread-count",
+ .description = "Number of threads to run on",
+ .value_kind = OptionValueKind::Required,
+ .parse_value =
+ [&](OptionsParser &parser, auto value) {
+ parser.parse_int(value, config.thread_count);
+ }},
+ Option{.short_name = 'n',
+ .long_name = "iter-count",
+ .description = "Number of iterations to run per thread",
+ .value_kind = OptionValueKind::Required,
+ .parse_value =
+ [&](OptionsParser &parser, auto value) {
+ parser.parse_int(value, config.iteration_count);
+ }},
+ };
+ OptionsParser parser(options, argv);
+ auto args = parser.parse();
+ if (!args.empty())
+ {
+ parser.help_and_exit("unexpected argument");
+ }
+ // TODO: invoke benchmarks
+ return 0;
+}