mirror of
https://git.lynn.is/Gwen/argparser.git
synced 2024-05-23 16:59:33 +02:00
1585 lines
65 KiB
C++
1585 lines
65 KiB
C++
#include <any>
|
|
#include <cassert>
|
|
#include <charconv>
|
|
#include <deque>
|
|
#include <format>
|
|
#include <functional>
|
|
#include <iomanip>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <span>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <tuple>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
#include <variant>
|
|
#include <vector>
|
|
|
|
namespace argparser {
|
|
namespace errors {
|
|
class runtime_error : public std::runtime_error {
|
|
public:
|
|
template<typename... Ts>
|
|
explicit runtime_error(Ts... args) : std::runtime_error(args...) {}
|
|
};
|
|
|
|
class logic_error : public std::logic_error {
|
|
public:
|
|
template<typename... Ts>
|
|
explicit logic_error(Ts... args) : std::logic_error(args...) {}
|
|
};
|
|
|
|
class missing_option_error : public runtime_error {
|
|
public:
|
|
explicit missing_option_error(std::string option_name) : runtime_error(std::format("missing required option {}", option_name)) {}
|
|
};
|
|
|
|
class missing_argument_error : public runtime_error {
|
|
public:
|
|
explicit missing_argument_error(std::string arg_name) : runtime_error(std::format("missing argument {}", arg_name)) {}
|
|
};
|
|
|
|
class unknown_option_error : public runtime_error {
|
|
public:
|
|
explicit unknown_option_error(std::string option_name) : runtime_error(std::format("unknown option {}", option_name)) {}
|
|
};
|
|
|
|
class wrong_option_count_error : public runtime_error {
|
|
public:
|
|
wrong_option_count_error(const std::string &option_name, std::optional<unsigned int> min, std::optional<unsigned int> max, unsigned int actual)
|
|
: runtime_error(make_message(option_name, min, max, actual)) {}
|
|
|
|
private:
|
|
static std::string make_message(std::string option_name, std::optional<unsigned int> min, std::optional<unsigned int> max, unsigned int actual) {
|
|
if (min != std::nullopt && max != std::nullopt) {
|
|
return std::format("option {} was provided {} times, but is required at least {} times and at most {} times", option_name, actual, min.value(), max.value());
|
|
} else if (min != std::nullopt) {
|
|
return std::format("option {} was provided {} times, but is required at least {} times", option_name, actual, min.value());
|
|
} else {
|
|
return std::format("option {} was provided {} times, but is required at most {} times", option_name, actual, max.value());
|
|
}
|
|
}
|
|
};
|
|
|
|
class missing_option_value_error : public runtime_error {
|
|
public:
|
|
explicit missing_option_value_error(std::string option_name) : runtime_error(std::format("missing value for option {}", option_name)) {}
|
|
};
|
|
|
|
class not_enough_arguments_error : public runtime_error {
|
|
public:
|
|
explicit not_enough_arguments_error(size_t min_required) : runtime_error(std::format("not enough arguments, need at least {}", min_required)) {}
|
|
};
|
|
|
|
class too_many_arguments_error : public runtime_error {
|
|
public:
|
|
explicit too_many_arguments_error() : runtime_error("too many arguments") {}
|
|
};
|
|
|
|
class invalid_option_value_error : public runtime_error {
|
|
public:
|
|
explicit invalid_option_value_error(std::string option_name) : runtime_error(std::format("invalid value for option {}", option_name)) {}
|
|
};
|
|
|
|
class unexpected_option_value_error : public runtime_error {
|
|
public:
|
|
explicit unexpected_option_value_error(std::string option_name) : runtime_error(std::format("unexpected value for option {}", option_name)) {}
|
|
};
|
|
|
|
class type_parsing_error : public runtime_error {
|
|
public:
|
|
type_parsing_error(std::string type_name, std::string input, size_t error_pos, std::string message)
|
|
: runtime_error(std::format("error parsing type {} at position {}: {}", type_name, error_pos, message)),
|
|
type_name(type_name), input(input), error_pos(error_pos) {}
|
|
|
|
const std::string type_name;
|
|
const std::string input;
|
|
const int error_pos;
|
|
};
|
|
|
|
class ambiguous_parse_error : public type_parsing_error {
|
|
public:
|
|
explicit ambiguous_parse_error(std::string type_name, std::string input, size_t error_pos, std::vector<std::string> possible_types)
|
|
: type_parsing_error(type_name, input, error_pos, make_message(possible_types)) {}
|
|
|
|
private:
|
|
static std::string make_message(std::vector<std::string> possible_types) {
|
|
std::string message = "ambiguity between ";
|
|
for (auto it = possible_types.begin(); it != possible_types.end(); it++) {
|
|
if (std::next(it) == possible_types.end()) {
|
|
message += " and " + *it;
|
|
} else {
|
|
message += ", " + *it;
|
|
}
|
|
}
|
|
return message;
|
|
}
|
|
};
|
|
|
|
class invalid_option_name_error : public logic_error {
|
|
public:
|
|
explicit invalid_option_name_error(std::string option_name) : logic_error(std::format("invalid option name {}", option_name)) {}
|
|
};
|
|
|
|
class duplicate_option_name : public logic_error {
|
|
public:
|
|
explicit duplicate_option_name(std::string option_name) : logic_error(std::format("option with name {} already exists", option_name)) {}
|
|
};
|
|
|
|
class duplicate_argument_name : public logic_error {
|
|
public:
|
|
explicit duplicate_argument_name(std::string arg_name) : logic_error(std::format("argument with name {} already exists", arg_name)) {}
|
|
};
|
|
|
|
class empty_enum_map_error : public logic_error {
|
|
public:
|
|
explicit empty_enum_map_error(std::string type_name) : logic_error(std::format("no enum values for type {}", type_name)) {}
|
|
};
|
|
}// namespace errors
|
|
|
|
namespace internal {
|
|
constexpr std::string_view whitespace = " \f\n\r\t\v";
|
|
enum class parser_allow_undelimited {
|
|
None = 0,
|
|
Comma = 1,
|
|
Parenthesis = 2,
|
|
Brackets = 4,
|
|
Any = 7,
|
|
};
|
|
|
|
constexpr parser_allow_undelimited operator|(parser_allow_undelimited a, parser_allow_undelimited b) {
|
|
return static_cast<parser_allow_undelimited>(static_cast<std::underlying_type_t<parser_allow_undelimited>>(a) | static_cast<std::underlying_type_t<parser_allow_undelimited>>(b));
|
|
}
|
|
|
|
constexpr parser_allow_undelimited operator&(parser_allow_undelimited a, parser_allow_undelimited b) {
|
|
return static_cast<parser_allow_undelimited>(static_cast<std::underlying_type_t<parser_allow_undelimited>>(a) & static_cast<std::underlying_type_t<parser_allow_undelimited>>(b));
|
|
}
|
|
enum class parse_opt {
|
|
None = 0,
|
|
BareString = 1,
|
|
SingleQuotedString = 2,
|
|
DoubleQuotedString = 4,
|
|
AnyString = 7,
|
|
TrimString = 8,
|
|
TrimBareString = 16,
|
|
};
|
|
|
|
constexpr parse_opt operator|(parse_opt a, parse_opt b) {
|
|
return static_cast<parse_opt>(static_cast<std::underlying_type_t<parse_opt>>(a) | static_cast<std::underlying_type_t<parse_opt>>(b));
|
|
}
|
|
|
|
constexpr parse_opt operator&(parse_opt a, parse_opt b) {
|
|
return static_cast<parse_opt>(static_cast<std::underlying_type_t<parse_opt>>(a) & static_cast<std::underlying_type_t<parse_opt>>(b));
|
|
}
|
|
|
|
template<typename T>
|
|
constexpr bool enum_flag_contains(T a, T b) {
|
|
return static_cast<std::underlying_type_t<T>>(a & b) != 0;
|
|
}
|
|
|
|
template<typename T>
|
|
using string_map = std::map<std::string, T, std::less<>>;
|
|
}// namespace internal
|
|
|
|
class parse_result {
|
|
internal::string_map<std::any> opts{};
|
|
internal::string_map<std::any> args{};
|
|
std::vector<std::string> remaining_args{};
|
|
|
|
public:
|
|
void set_opt(const std::string &name, std::any value) {
|
|
opts[name] = std::move(value);
|
|
}
|
|
|
|
[[nodiscard]] std::any get_opt(const std::string &name) const {
|
|
if (opts.find(name) != opts.end()) {
|
|
return opts.at(name);
|
|
} else {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] bool has_opt(const std::string &name) const {
|
|
return opts.find(name) != opts.end();
|
|
}
|
|
|
|
void set_arg(const std::string &name, std::any value) {
|
|
args[name] = std::move(value);
|
|
}
|
|
|
|
[[nodiscard]] std::any get_arg(const std::string &name) const {
|
|
if (args.find(name) != args.end()) {
|
|
return args.at(name);
|
|
} else {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] bool has_arg(const std::string &name) const {
|
|
auto argit = args.find(name);
|
|
if (argit == args.end())
|
|
return false;
|
|
return argit != args.end() && argit->second.has_value();
|
|
}
|
|
|
|
void set_remaining(std::vector<std::string> remaining) {
|
|
remaining_args = std::move(remaining);
|
|
}
|
|
|
|
[[nodiscard]] std::vector<std::string> remaining() const {
|
|
return remaining_args;
|
|
}
|
|
};
|
|
|
|
class type {
|
|
public:
|
|
virtual ~type() = default;
|
|
|
|
[[nodiscard]] std::string get_name() const {
|
|
return name;
|
|
}
|
|
|
|
protected:
|
|
explicit type(std::string name) : name(std::move(name)) {}
|
|
|
|
std::string name;
|
|
};
|
|
|
|
using type_handle = std::shared_ptr<type>;
|
|
|
|
template<typename T>
|
|
class type_impl : public type {
|
|
public:
|
|
virtual T parse(const char *begin, const char *end, const char *&parse_end, internal::parser_allow_undelimited allow_undelimited = internal::parser_allow_undelimited::None) = 0;
|
|
|
|
protected:
|
|
explicit type_impl(std::string name) : type(std::move(name)) {}
|
|
};
|
|
|
|
template<typename T>
|
|
using type_handle_impl = std::shared_ptr<type_impl<T>>;
|
|
|
|
class arg {
|
|
public:
|
|
explicit arg(std::string name) : name(std::move(name)) {}
|
|
|
|
virtual ~arg() = default;
|
|
|
|
void parse(const std::string &input, parse_result &pr) const {
|
|
do_parse(input, pr);
|
|
}
|
|
|
|
[[nodiscard]] bool has_parsed_enough(parse_result &pr) const {
|
|
return this->get_has_parsed_enough(pr);
|
|
}
|
|
|
|
[[nodiscard]] bool can_parse_more(parse_result &pr) const {
|
|
return this->get_can_parse_more(pr);
|
|
}
|
|
|
|
[[nodiscard]] std::string get_name() const {
|
|
return name;
|
|
}
|
|
|
|
protected:
|
|
const std::string name;
|
|
|
|
template<typename T>
|
|
static T parse_single_value(std::string input, const type_handle_impl<T> &type) {
|
|
const char *parse_end;
|
|
auto begin = &*input.begin();
|
|
auto end = &*input.end();
|
|
auto val = type->parse(begin, end, parse_end, internal::parser_allow_undelimited::Any);
|
|
if (parse_end != end) {
|
|
throw errors::type_parsing_error(type->get_name(), std::string(begin, end), parse_end - begin, "unexpected input");
|
|
}
|
|
return val;
|
|
}
|
|
|
|
private:
|
|
virtual void do_parse(std::string input, parse_result &pr) const = 0;
|
|
[[nodiscard]] virtual bool get_has_parsed_enough(parse_result &pr) const = 0;
|
|
[[nodiscard]] virtual bool get_can_parse_more(parse_result &pr) const = 0;
|
|
};
|
|
|
|
using arg_handle = std::shared_ptr<arg>;
|
|
|
|
namespace internal {
|
|
template<typename T>
|
|
using parser_func = std::function<T(const char *begin, const char *end, const char *&parse_end, internal::parser_allow_undelimited allow_undelimited)>;
|
|
}
|
|
|
|
template<typename T>
|
|
class basic_type : public type_impl<T> {
|
|
public:
|
|
basic_type(std::string name, internal::parser_func<T> parser) : type_impl<T>(std::move(name)), parser(std::move(parser)) {}
|
|
|
|
T parse(const char *begin, const char *end, const char *&parse_end, internal::parser_allow_undelimited allow_undelimited) override {
|
|
const char *n_parse_end;
|
|
auto val = parser(begin, end, n_parse_end, allow_undelimited);
|
|
parse_end = n_parse_end;
|
|
return val;
|
|
}
|
|
|
|
private:
|
|
internal::parser_func<T> parser;
|
|
};
|
|
|
|
namespace internal {
|
|
template<typename T, parse_opt parse_opt>
|
|
class automatic_parser {
|
|
public:
|
|
static parser_func<T> make_parser(const std::string &name) {
|
|
return [name](const char *begin, const char *end, const char *&parse_end, internal::parser_allow_undelimited) {
|
|
T val;
|
|
auto pos = begin;
|
|
while (pos < end && isspace(*pos)) pos++;
|
|
auto res = std::from_chars(pos, end, val);
|
|
if (res.ec == std::errc{}) {
|
|
parse_end = res.ptr;
|
|
return val;
|
|
} else {
|
|
throw errors::type_parsing_error(name, std::string(begin, end), pos - begin, "invalid number");
|
|
}
|
|
};
|
|
}
|
|
};
|
|
|
|
constexpr parse_opt string_parse_opt_with_default(parse_opt opt) {
|
|
if ((opt & parse_opt::AnyString) == parse_opt::None) {
|
|
return opt | parse_opt::AnyString;
|
|
} else {
|
|
return opt;
|
|
}
|
|
}
|
|
|
|
constexpr bool string_parser_enable_bare(parse_opt opt) {
|
|
return enum_flag_contains(string_parse_opt_with_default(opt), parse_opt::BareString);
|
|
}
|
|
|
|
constexpr bool string_parser_enable_single_quoted(parse_opt opt) {
|
|
return enum_flag_contains(string_parse_opt_with_default(opt), parse_opt::SingleQuotedString);
|
|
}
|
|
|
|
constexpr bool string_parser_enable_double_quoted(parse_opt opt) {
|
|
return enum_flag_contains(string_parse_opt_with_default(opt), parse_opt::DoubleQuotedString);
|
|
}
|
|
|
|
template<parse_opt parse_opt>
|
|
class automatic_parser<std::string, parse_opt> {
|
|
static std::string trim_string(const std::string &str) {
|
|
auto start = str.find_first_not_of(whitespace);
|
|
auto end = str.find_last_not_of(whitespace);
|
|
return str.substr(start == std::string::npos ? 0 : start, end == std::string::npos ? 0 : end);
|
|
}
|
|
|
|
public:
|
|
static parser_func<std::string> make_parser(const std::string &name) {
|
|
return [name](const char *begin, const char *end, const char *&parse_end, internal::parser_allow_undelimited allow_undelimited) {
|
|
auto str = std::string_view(begin, end);
|
|
if (str.length() == 0) {
|
|
if (parse_opt == parse_opt::None || enum_flag_contains(parse_opt, parse_opt::BareString)) {
|
|
parse_end = begin;
|
|
return std::string("");
|
|
} else {
|
|
throw errors::type_parsing_error(name, std::string(""), 0, "unexpected empty input");
|
|
}
|
|
}
|
|
if (str[0] == '\'' && string_parser_enable_single_quoted(parse_opt)) {
|
|
auto ss = std::stringstream(std::string(str));
|
|
std::string val;
|
|
ss >> std::quoted(val, '\'', '\\');
|
|
auto len = ss.tellg();
|
|
parse_end = begin + len;
|
|
if (enum_flag_contains(parse_opt, parse_opt::TrimString)) {
|
|
return trim_string(val);
|
|
} else {
|
|
return val;
|
|
}
|
|
}
|
|
if (str[0] == '"' && string_parser_enable_double_quoted(parse_opt)) {
|
|
auto ss = std::stringstream(std::string(str));
|
|
std::string val;
|
|
ss >> std::quoted(val, '"', '\\');
|
|
auto len = ss.tellg();
|
|
parse_end = begin + len;
|
|
if (enum_flag_contains(parse_opt, parse_opt::TrimString)) {
|
|
return trim_string(val);
|
|
} else {
|
|
return val;
|
|
}
|
|
}
|
|
if (string_parser_enable_bare(parse_opt)) {
|
|
std::string illegal_characters = "\"'";
|
|
if (!internal::enum_flag_contains(allow_undelimited, internal::parser_allow_undelimited::Comma)) {
|
|
illegal_characters += ",";
|
|
}
|
|
if (!internal::enum_flag_contains(allow_undelimited, internal::parser_allow_undelimited::Parenthesis)) {
|
|
illegal_characters += "()";
|
|
}
|
|
if (!internal::enum_flag_contains(allow_undelimited, internal::parser_allow_undelimited::Brackets)) {
|
|
illegal_characters += "[]";
|
|
}
|
|
auto pos = str.find_first_of(illegal_characters);
|
|
if (pos == std::string_view::npos) {
|
|
parse_end = end;
|
|
} else {
|
|
parse_end = begin + pos;
|
|
}
|
|
std::string val{begin, parse_end};
|
|
if (enum_flag_contains(parse_opt, parse_opt::TrimString) || enum_flag_contains(parse_opt, parse_opt::TrimBareString)) {
|
|
return trim_string(val);
|
|
} else {
|
|
return val;
|
|
}
|
|
}
|
|
throw errors::type_parsing_error(name, std::string(begin, end), 0, "failed to parse string");
|
|
};
|
|
}
|
|
};
|
|
|
|
template<typename T>
|
|
parser_func<T> make_enum_parser(const std::string &name, internal::string_map<T> values) {
|
|
if (values.size() == 0) {
|
|
throw errors::empty_enum_map_error(name);
|
|
}
|
|
return [name, values](const char *begin, const char *end, const char *&parse_end, internal::parser_allow_undelimited) {
|
|
auto input = std::basic_string_view(begin, end);
|
|
auto start = input.find_first_not_of(whitespace);
|
|
if (start == std::string::npos) {
|
|
input = "";
|
|
} else {
|
|
input = std::basic_string_view(begin + start, end);
|
|
}
|
|
T value;
|
|
size_t value_len = 0;
|
|
for (const auto &[str, val]: values) {
|
|
if (str.length() <= input.length()) {
|
|
if (input.starts_with(str)) {
|
|
if (str.length() > value_len) {
|
|
value = val;
|
|
value_len = str.length();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (value_len == 0) {
|
|
std::string expected_values = values.begin()->first;
|
|
for (auto it = std::next(values.begin()); it != values.end(); it++) {
|
|
if (std::next(it) != values.end()) {
|
|
expected_values += ", " + it->first;
|
|
} else {
|
|
expected_values += " or " + it->first;
|
|
}
|
|
}
|
|
throw errors::type_parsing_error(name, std::string(begin, end), 0, std::format("expected {}", expected_values));
|
|
} else {
|
|
parse_end = begin + value_len;
|
|
return value;
|
|
}
|
|
};
|
|
}
|
|
}// namespace internal
|
|
|
|
namespace internal::distinct_types_impl {
|
|
template<typename TList, typename... Ts>
|
|
struct UniqueTypes;
|
|
|
|
template<template<typename...> typename X, typename... Ts>
|
|
struct UniqueTypes<X<Ts...>> {
|
|
using type = X<Ts...>;
|
|
};
|
|
|
|
template<template<typename...> typename X, typename... Ts, typename T, typename... Us>
|
|
struct UniqueTypes<X<Ts...>, T, Us...> {
|
|
using type = typename UniqueTypes<
|
|
typename std::conditional<
|
|
std::disjunction<std::is_same<T, Ts>...>::value,
|
|
X<Ts...>,
|
|
X<Ts..., T>>::type,
|
|
Us...>::type;
|
|
};
|
|
|
|
template<template<typename...> typename X, typename... Ts>
|
|
struct Distinct {
|
|
using type = typename UniqueTypes<X<>, Ts...>::type;
|
|
};
|
|
}// namespace internal::distinct_types_impl
|
|
|
|
namespace internal {
|
|
template<typename... Ts>
|
|
using distinct_types_variant = typename internal::distinct_types_impl::Distinct<std::variant, Ts...>::type;
|
|
template<typename... Ts>
|
|
using distinct_types_tuple = typename internal::distinct_types_impl::Distinct<std::tuple, Ts...>::type;
|
|
template<typename T, typename... Ts>
|
|
using if_single_type = std::enable_if_t<std::tuple_size<distinct_types_tuple<Ts...>>::value == 1, T>;
|
|
template<typename T, typename... Ts>
|
|
using if_not_single_type = std::enable_if_t<(std::tuple_size<distinct_types_tuple<Ts...>>::value > 1), T>;
|
|
template<typename T, typename F, typename... Ts>
|
|
using single_type_conditional = std::conditional<(std::tuple_size<distinct_types_tuple<Ts...>>::value == 1), T, F>;
|
|
template<typename... Ts>
|
|
using single_type = if_single_type<typename std::tuple_element<0, std::tuple<Ts...>>::type, Ts...>;
|
|
}// namespace internal
|
|
|
|
template<typename T>
|
|
class list_type : public type_impl<std::vector<T>> {
|
|
public:
|
|
list_type(std::string name, type_handle_impl<T> element_type) : type_impl<std::vector<T>>(std::move(name)), element_type(element_type) {}
|
|
|
|
std::vector<T> parse(const char *begin, const char *end, const char *&parse_end, internal::parser_allow_undelimited allow_undelimited) override {
|
|
auto cur_pos = begin;
|
|
std::vector<T> values{};
|
|
bool is_delimited;
|
|
if (*cur_pos == '[') {
|
|
is_delimited = true;
|
|
cur_pos++;
|
|
} else {
|
|
if (!internal::enum_flag_contains(allow_undelimited, internal::parser_allow_undelimited::Comma)) {
|
|
throw errors::type_parsing_error(this->name, std::string(begin, end), cur_pos - begin, "expected '['");
|
|
} else {
|
|
is_delimited = false;
|
|
}
|
|
}
|
|
if (cur_pos >= end) {
|
|
throw errors::type_parsing_error(this->name, std::string(begin, end), end - begin, "unexpected end of input");
|
|
}
|
|
internal::parser_allow_undelimited sub_parse_allow_undelimited = internal::parser_allow_undelimited::Parenthesis;
|
|
if (!is_delimited) {
|
|
sub_parse_allow_undelimited &allow_undelimited;
|
|
}
|
|
while (true) {
|
|
const char *this_parse_end;
|
|
auto val = element_type->parse(cur_pos, end, this_parse_end, sub_parse_allow_undelimited);
|
|
values.push_back(val);
|
|
cur_pos = this_parse_end;
|
|
while (std::isspace(*cur_pos) && cur_pos <= end)
|
|
cur_pos++;
|
|
if (cur_pos >= end) {
|
|
if (!is_delimited) {
|
|
break;
|
|
} else {
|
|
throw errors::type_parsing_error(this->name, std::string(begin, end), end - begin, "unexpected end of input");
|
|
}
|
|
}
|
|
if (*cur_pos == ',') {
|
|
cur_pos++;
|
|
auto s = std::string_view(cur_pos, end);
|
|
auto close_bracket_pos = s.find_first_of(']');
|
|
if (s.find_first_not_of(internal::whitespace) == close_bracket_pos) {
|
|
break;
|
|
}
|
|
} else if (!is_delimited || *cur_pos == ']') {
|
|
break;
|
|
} else {
|
|
throw errors::type_parsing_error(this->name, std::string(begin, end), end - begin, "expected , or ]");
|
|
}
|
|
}
|
|
if (is_delimited) {
|
|
if (*cur_pos != ']') {
|
|
throw errors::type_parsing_error(this->name, std::string(begin, end), end - begin, "unexpected end of input");
|
|
} else {
|
|
cur_pos++;
|
|
}
|
|
}
|
|
parse_end = cur_pos;
|
|
return values;
|
|
}
|
|
|
|
private:
|
|
type_handle_impl<T> element_type;
|
|
};
|
|
|
|
class option {
|
|
public:
|
|
explicit option(std::string name) : name(std::move(name)) {}
|
|
|
|
virtual ~option() = default;
|
|
|
|
void parse(std::optional<std::string> arg, std::any &val) {
|
|
return this->do_parse(std::move(arg), val);
|
|
}
|
|
|
|
[[nodiscard]] std::string get_name() {
|
|
return name;
|
|
}
|
|
|
|
[[nodiscard]] virtual bool consumes_value() const = 0;
|
|
virtual void validate(const parse_result &res) const = 0;
|
|
|
|
protected:
|
|
const std::string name;
|
|
|
|
private:
|
|
virtual void do_parse(std::optional<std::string> arg, std::any &val) = 0;
|
|
};
|
|
|
|
using option_handle = std::shared_ptr<option>;
|
|
|
|
template<typename T>
|
|
class optional_arg : public arg, public std::enable_shared_from_this<optional_arg<T>> {
|
|
public:
|
|
optional_arg(std::string name, type_handle_impl<T> type) : arg(std::move(name)), type(type) {}
|
|
|
|
std::shared_ptr<optional_arg<T>> default_value(T val) {
|
|
default_value_ = val;
|
|
return this->shared_from_this();
|
|
}
|
|
|
|
[[nodiscard]] std::optional<T> get(const parse_result &p) const {
|
|
auto v = p.get_arg(name);
|
|
if (!v.has_value()) {
|
|
if (default_value_.has_value()) {
|
|
return default_value_.value();
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
return std::make_optional(std::any_cast<T>(v));
|
|
}
|
|
|
|
[[nodiscard]] bool has(const parse_result &p) const {
|
|
return p.has_arg(name);
|
|
}
|
|
|
|
private:
|
|
type_handle_impl<T> type;
|
|
std::optional<T> default_value_ = std::nullopt;
|
|
|
|
void do_parse(std::string input, parse_result &pr) const override {
|
|
assert(!pr.get_arg(name).has_value());// an optional arg can only be parsed once
|
|
auto val = this->parse_single_value(input, type);
|
|
pr.set_arg(name, std::make_any<T>(val));
|
|
}
|
|
|
|
[[nodiscard]] bool get_can_parse_more(parse_result &pr) const override {
|
|
return !pr.get_arg(name).has_value();
|
|
}
|
|
|
|
[[nodiscard]] bool get_has_parsed_enough(parse_result &) const override {
|
|
return true;
|
|
}
|
|
};
|
|
|
|
template<typename T>
|
|
using optional_arg_handle = std::shared_ptr<optional_arg<T>>;
|
|
|
|
template<typename T>
|
|
class repeatable_arg : public arg, public std::enable_shared_from_this<repeatable_arg<T>> {
|
|
public:
|
|
repeatable_arg(std::string name, type_handle_impl<T> type) : arg(std::move(name)), type(type) {}
|
|
|
|
std::shared_ptr<repeatable_arg<T>> min(size_t min) {
|
|
min_ = min;
|
|
return this->shared_from_this();
|
|
}
|
|
|
|
std::shared_ptr<repeatable_arg<T>> max(size_t max) {
|
|
max_ = max;
|
|
return this->shared_from_this();
|
|
}
|
|
|
|
[[nodiscard]] std::vector<T> get(const parse_result &pr) const {
|
|
auto v = pr.get_arg(name);
|
|
if (!v.has_value()) {
|
|
return std::vector<T>{};
|
|
}
|
|
return std::any_cast<std::vector<T>>(v);
|
|
}
|
|
|
|
[[nodiscard]] bool has(const parse_result &pr) const {
|
|
return pr.has_arg(name);
|
|
}
|
|
|
|
private:
|
|
type_handle_impl<T> type;
|
|
std::optional<size_t> max_;
|
|
std::optional<size_t> min_;
|
|
|
|
void do_parse(std::string input, parse_result &pr) const override {
|
|
auto current_val = pr.get_arg(name);
|
|
std::vector<T> values;
|
|
if (current_val.has_value()) {
|
|
values = std::any_cast<std::vector<T>>(current_val);
|
|
}
|
|
assert(!max_.has_value() || values.size() < max_.value());
|
|
auto val = this->parse_single_value(input, type);
|
|
values.push_back(val);
|
|
pr.set_arg(name, std::make_any<std::vector<T>>(std::move(values)));
|
|
}
|
|
|
|
[[nodiscard]] bool get_can_parse_more(parse_result &pr) const override {
|
|
if (max_.has_value()) {
|
|
auto val = pr.get_arg(name);
|
|
if (!val.has_value()) {
|
|
return true;
|
|
}
|
|
return std::any_cast<std::vector<T>>(val).size() < max_.value();
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] bool get_has_parsed_enough(parse_result &pr) const override {
|
|
if (min_.has_value() && min_.value() > 0) {
|
|
auto val = pr.get_arg(name);
|
|
if (!val.has_value()) {
|
|
return false;
|
|
}
|
|
auto val_ = std::any_cast<std::vector<T>>(val);
|
|
return val_.size() >= min_.value();
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
};
|
|
|
|
template<typename T>
|
|
using repeatable_arg_handle = std::shared_ptr<repeatable_arg<T>>;
|
|
|
|
class repeatable_flag_impl : public option, public std::enable_shared_from_this<repeatable_flag_impl> {
|
|
public:
|
|
repeatable_flag_impl(std::string name) : option(std::move(name)) {}
|
|
|
|
std::shared_ptr<repeatable_flag_impl> min(unsigned int min) {
|
|
min_ = min;
|
|
return this->shared_from_this();
|
|
}
|
|
|
|
std::shared_ptr<repeatable_flag_impl> max(unsigned int max) {
|
|
max_ = max;
|
|
return this->shared_from_this();
|
|
}
|
|
|
|
[[nodiscard]] bool consumes_value() const override {
|
|
return false;
|
|
}
|
|
|
|
void validate(const parse_result &pr) const override {
|
|
unsigned int count = get(pr);
|
|
if ((min_.has_value() && count < min_.value()) ||
|
|
(max_.has_value() && count > max_.value())) {
|
|
throw errors::wrong_option_count_error(this->name, min_, max_, count);
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] unsigned int get(const parse_result &pr) const {
|
|
auto v = pr.get_opt(name);
|
|
if (!v.has_value()) {
|
|
return 0;
|
|
}
|
|
return std::any_cast<unsigned int>(v);
|
|
}
|
|
|
|
[[nodiscard]] bool has(const parse_result &pr) const {
|
|
return pr.has_opt(name);
|
|
}
|
|
|
|
private:
|
|
void do_parse(std::optional<std::string> arg, std::any &val) override {
|
|
assert(!arg.has_value());
|
|
unsigned int count = 0;
|
|
if (val.has_value()) {
|
|
count = std::any_cast<unsigned int>(val);
|
|
}
|
|
val = std::make_any<unsigned int>(count + 1);
|
|
}
|
|
|
|
std::optional<unsigned int> min_ = std::nullopt;
|
|
std::optional<unsigned int> max_ = std::nullopt;
|
|
};
|
|
|
|
using repeatable_flag_handle_impl = std::shared_ptr<repeatable_flag_impl>;
|
|
|
|
template<typename T>
|
|
class repeatable_option_impl : public option, public std::enable_shared_from_this<repeatable_option_impl<T>> {
|
|
public:
|
|
repeatable_option_impl(std::string name, type_handle_impl<T> type) : option(std::move(name)), type(std::move(type)) {}
|
|
|
|
std::shared_ptr<repeatable_option_impl<T>> default_value(std::vector<T> val) {
|
|
this->default_value_ = std::make_optional(val);
|
|
return this->shared_from_this();
|
|
}
|
|
|
|
std::shared_ptr<repeatable_option_impl<T>> min(unsigned int min) {
|
|
this->min_ = min;
|
|
return this->shared_from_this();
|
|
}
|
|
|
|
std::shared_ptr<repeatable_option_impl<T>> max(unsigned int max) {
|
|
this->max_ = max;
|
|
return this->shared_from_this();
|
|
}
|
|
|
|
void validate(const parse_result &pr) const override {
|
|
unsigned int count = 0;
|
|
if (pr.has_opt(this->name)) {
|
|
count = get(pr).size();
|
|
}
|
|
if ((min_.has_value() && count < min_.value()) ||
|
|
(max_.has_value() && count > max_.value())) {
|
|
throw errors::wrong_option_count_error(this->name, min_, max_, count);
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] bool consumes_value() const override {
|
|
return true;
|
|
}
|
|
|
|
[[nodiscard]] std::vector<T> get(const parse_result &pr) const {
|
|
auto v = pr.get_opt(name);
|
|
if (!v.has_value()) {
|
|
if (default_value_.has_value()) {
|
|
return default_value_.value();
|
|
}
|
|
throw std::runtime_error(std::format("option {} was not provided", name));
|
|
}
|
|
return std::any_cast<std::vector<T>>(v);
|
|
}
|
|
|
|
[[nodiscard]] bool has(const parse_result &pr) const {
|
|
return pr.has_opt(name);
|
|
}
|
|
|
|
private:
|
|
void do_parse(std::optional<std::string> arg, std::any &val) override {
|
|
assert(arg.has_value());
|
|
const char *parse_end;
|
|
auto begin = &*arg->begin();
|
|
auto end = &*arg->end();
|
|
auto single_val = this->type->parse(begin, end, parse_end, internal::parser_allow_undelimited::Any);
|
|
if (parse_end != end) {
|
|
throw errors::type_parsing_error(this->type->get_name(), std::string(begin, end), parse_end - begin, "unexpected input");
|
|
}
|
|
std::vector<T> val_vec{};
|
|
if (val.has_value()) {
|
|
val_vec = std::any_cast<std::vector<T>>(val);
|
|
}
|
|
val_vec.push_back(single_val);
|
|
val = std::make_any<std::vector<T>>(val_vec);
|
|
}
|
|
|
|
type_handle_impl<T> type;
|
|
std::optional<std::vector<T>> default_value_ = std::nullopt;
|
|
std::optional<unsigned int> min_ = std::nullopt;
|
|
std::optional<unsigned int> max_ = std::nullopt;
|
|
};
|
|
|
|
template<typename T>
|
|
using repeatable_option_handle_impl = std::shared_ptr<repeatable_option_impl<T>>;
|
|
|
|
template<typename T>
|
|
class single_arg : public arg, public std::enable_shared_from_this<single_arg<T>> {
|
|
public:
|
|
single_arg(std::string name, type_handle_impl<T> type) : arg(std::move(name)), type(type) {}
|
|
|
|
[[nodiscard]] T get(const parse_result &pr) const {
|
|
auto v = pr.get_arg(name);
|
|
if (!v.has_value()) {
|
|
throw errors::missing_argument_error(name);
|
|
}
|
|
return std::any_cast<T>(v);
|
|
}
|
|
|
|
[[nodiscard]] bool has(const parse_result &pr) const {
|
|
return pr.has_arg(name);
|
|
}
|
|
|
|
private:
|
|
type_handle_impl<T> type;
|
|
|
|
void do_parse(std::string input, parse_result &pr) const override {
|
|
assert(!pr.get_arg(name).has_value());// a single arg can only be parsed once
|
|
auto val = this->parse_single_value(input, type);
|
|
pr.set_arg(name, std::make_any<T>(val));
|
|
}
|
|
|
|
bool get_can_parse_more(parse_result &pr) const override {
|
|
return !pr.get_arg(name).has_value();
|
|
}
|
|
|
|
bool get_has_parsed_enough(parse_result &pr) const override {
|
|
return pr.get_arg(name).has_value();
|
|
}
|
|
};
|
|
|
|
template<typename T>
|
|
using single_arg_handle = std::shared_ptr<single_arg<T>>;
|
|
|
|
class flag_impl : public option, public std::enable_shared_from_this<flag_impl> {
|
|
public:
|
|
explicit flag_impl(std::string name) : option(std::move(name)) {}
|
|
|
|
[[nodiscard]] bool is_inverted() const {
|
|
return is_inverted_;
|
|
}
|
|
|
|
std::shared_ptr<flag_impl> invert() {
|
|
this->is_inverted_ = true;
|
|
return this->shared_from_this();
|
|
}
|
|
|
|
[[nodiscard]] bool consumes_value() const override {
|
|
return false;
|
|
}
|
|
|
|
void validate(const parse_result &) const override {
|
|
}
|
|
|
|
[[nodiscard]] bool get(const parse_result &pr) const {
|
|
bool val = pr.has_opt(name);
|
|
if (is_inverted_) {
|
|
val = !val;
|
|
}
|
|
return val;
|
|
}
|
|
|
|
[[nodiscard]] bool has(const parse_result &pr) const {
|
|
return pr.has_opt(name);
|
|
}
|
|
|
|
private:
|
|
void do_parse(std::optional<std::string> arg, std::any &val) override {
|
|
assert(!arg.has_value());
|
|
if (!val.has_value()) {
|
|
val = std::make_any<bool>(true);
|
|
}
|
|
}
|
|
|
|
bool is_inverted_{};
|
|
};
|
|
|
|
using flag_handle_impl = std::shared_ptr<flag_impl>;
|
|
|
|
template<typename T>
|
|
class option_impl : public option, public std::enable_shared_from_this<option_impl<T>> {
|
|
public:
|
|
option_impl(std::string name, type_handle_impl<T> type) : option(std::move(name)), type(std::move(type)) {}
|
|
|
|
std::shared_ptr<option_impl<T>> required() {
|
|
required_ = true;
|
|
return this->shared_from_this();
|
|
}
|
|
|
|
std::shared_ptr<option_impl<T>> default_value(T val) {
|
|
this->default_value_ = val;
|
|
this->has_default_value = true;
|
|
return this->shared_from_this();
|
|
}
|
|
|
|
void validate(const parse_result &res) const override {
|
|
if (this->required_) {
|
|
if (!res.has_opt(this->name)) {
|
|
throw errors::missing_option_error(this->name);
|
|
}
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] bool consumes_value() const override {
|
|
return true;
|
|
}
|
|
|
|
[[nodiscard]] T get(const parse_result &pr) const {
|
|
auto v = pr.get_opt(name);
|
|
if (!v.has_value()) {
|
|
if (has_default_value) {
|
|
return default_value_;
|
|
}
|
|
throw std::runtime_error(std::format("option {} was not provided", name));
|
|
}
|
|
return std::any_cast<T>(v);
|
|
}
|
|
|
|
[[nodiscard]] bool has(const parse_result &pr) const {
|
|
return pr.has_opt(name);
|
|
}
|
|
|
|
private:
|
|
void do_parse(std::optional<std::string> arg, std::any &val) override {
|
|
assert(arg.has_value());
|
|
const char *parse_end;
|
|
auto begin = &*arg->begin();
|
|
auto end = &*arg->end();
|
|
val = this->type->parse(begin, end, parse_end, internal::parser_allow_undelimited::Any);
|
|
if (parse_end != end) {
|
|
throw errors::type_parsing_error(this->type->get_name(), std::string(begin, end), parse_end - begin, "unexpected input");
|
|
}
|
|
}
|
|
|
|
type_handle_impl<T> type;
|
|
bool required_ = false;
|
|
bool has_default_value = false;
|
|
T default_value_;
|
|
};
|
|
|
|
template<typename T>
|
|
using option_handle_impl = std::shared_ptr<option_impl<T>>;
|
|
|
|
// adapted from https://blog.tartanllama.xyz/exploding-tuples-fold-expressions/
|
|
namespace internal::tuple_foreach_impl {
|
|
template<std::size_t... Idx>
|
|
auto make_index_dispatcher(std::index_sequence<Idx...>) {
|
|
return [](auto &&f) { (f(std::integral_constant<std::size_t, Idx>{}), ...); };
|
|
}
|
|
|
|
template<std::size_t N>
|
|
auto make_index_dispatcher() {
|
|
return make_index_dispatcher(std::make_index_sequence<N>{});
|
|
}
|
|
}// namespace internal::tuple_foreach_impl
|
|
|
|
namespace internal {
|
|
template<typename Tuple, typename Func>
|
|
void tuple_foreach(Tuple &&t, Func &&f) {
|
|
constexpr auto n = std::tuple_size<std::decay_t<Tuple>>::value;
|
|
auto dispatcher = internal::tuple_foreach_impl::make_index_dispatcher<n>();
|
|
dispatcher([&f, &t](auto idx) { f(std::get<idx>(std::forward<Tuple>(t)), idx); });
|
|
}
|
|
}// namespace internal
|
|
|
|
template<typename R, typename... Ts>
|
|
class base_tuple_type : public type_impl<R> {
|
|
public:
|
|
base_tuple_type(std::string name, std::tuple<type_handle_impl<Ts>...> types) : type_impl<R>(std::move(name)), types(types) {}
|
|
|
|
protected:
|
|
void parse_into_tuple(const char *begin, const char *end, const char *&parse_end, internal::parser_allow_undelimited allow_undelimited, std::tuple<Ts...> &result) {
|
|
auto cur_pos = begin;
|
|
size_t tuple_size = std::tuple_size_v<std::tuple<Ts...>>;
|
|
bool requires_delimiter = tuple_size > 1 && !internal::enum_flag_contains(allow_undelimited, internal::parser_allow_undelimited::Comma);
|
|
bool is_delimited;
|
|
if (std::string_view(begin, end).starts_with(this->name)) {
|
|
cur_pos += this->name.size();
|
|
requires_delimiter = true;
|
|
}
|
|
if (*cur_pos == '(') {
|
|
is_delimited = true;
|
|
cur_pos++;
|
|
} else if (requires_delimiter) {
|
|
throw errors::type_parsing_error(this->name, std::string(begin, end), cur_pos - begin, "expected '('");
|
|
} else {
|
|
is_delimited = false;
|
|
}
|
|
internal::parser_allow_undelimited sub_parse_allow_undelimited = internal::parser_allow_undelimited::Brackets;
|
|
if (tuple_size == 1) {
|
|
sub_parse_allow_undelimited = sub_parse_allow_undelimited | internal::parser_allow_undelimited::Comma;
|
|
}
|
|
if (!is_delimited) {
|
|
sub_parse_allow_undelimited = sub_parse_allow_undelimited & allow_undelimited;
|
|
}
|
|
internal::tuple_foreach(types, [&cur_pos, &end, &result, this, &begin, &sub_parse_allow_undelimited]<typename T>(type_handle_impl<T> &type, auto idx) {
|
|
const char *this_parse_end;
|
|
std::get<idx>(result) = type->parse(cur_pos, end, this_parse_end, sub_parse_allow_undelimited);
|
|
cur_pos = this_parse_end;
|
|
while (std::isspace(*cur_pos) && cur_pos < end)
|
|
cur_pos++;
|
|
if (idx < std::tuple_size_v<std::tuple<Ts...>> - 1) {
|
|
if (*cur_pos == ',') {
|
|
cur_pos += 1;
|
|
} else {
|
|
throw errors::type_parsing_error(this->name, std::string(begin, end), cur_pos - begin, "expected ','");
|
|
}
|
|
}
|
|
});
|
|
if (*cur_pos == ')') {
|
|
if (is_delimited) {
|
|
cur_pos++;
|
|
}
|
|
} else {
|
|
if (is_delimited) {
|
|
throw errors::type_parsing_error(this->name, std::string(begin, end), cur_pos - begin, "expected ')'");
|
|
}
|
|
}
|
|
parse_end = cur_pos;
|
|
}
|
|
|
|
private:
|
|
std::tuple<type_handle_impl<Ts>...>
|
|
types;
|
|
};
|
|
|
|
template<typename... Ts>
|
|
class tuple_type : public base_tuple_type<std::tuple<Ts...>, Ts...> {
|
|
public:
|
|
tuple_type(std::string name, std::tuple<type_handle_impl<Ts>...> types) : base_tuple_type<std::tuple<Ts...>, Ts...>(std::move(name), types) {}
|
|
|
|
std::tuple<Ts...> parse(const char *begin, const char *end, const char *&parse_end, internal::parser_allow_undelimited allow_undelimited) override {
|
|
std::tuple<Ts...> result{};
|
|
this->parse_into_tuple(begin, end, parse_end, allow_undelimited, result);
|
|
return result;
|
|
}
|
|
};
|
|
|
|
template<typename R, typename... Ts>
|
|
class custom_tuple_type : public base_tuple_type<R, Ts...> {
|
|
public:
|
|
custom_tuple_type(std::string name, std::tuple<type_handle_impl<Ts>...> types) : base_tuple_type<R, Ts...>(std::move(name), types) {}
|
|
|
|
R parse(const char *begin, const char *end, const char *&parse_end, internal::parser_allow_undelimited allow_undelimited) override {
|
|
std::tuple<Ts...> result{};
|
|
this->parse_into_tuple(begin, end, parse_end, allow_undelimited, result);
|
|
return std::apply(construct_return_type, result);
|
|
}
|
|
|
|
private:
|
|
static R construct_return_type(Ts... args) {
|
|
return R(args...);
|
|
}
|
|
};
|
|
|
|
template<typename R, typename... Ts>
|
|
class custom_tuple_type_with_constructor : public base_tuple_type<R, Ts...> {
|
|
public:
|
|
custom_tuple_type_with_constructor(std::string name, std::tuple<type_handle_impl<Ts>...> types, std::function<R(Ts...)> constructor)
|
|
: base_tuple_type<R, Ts...>(std::move(name), types), constructor(constructor) {}
|
|
|
|
R parse(const char *begin, const char *end, const char *&parse_end, internal::parser_allow_undelimited allow_undelimited) override {
|
|
std::tuple<Ts...> result{};
|
|
this->parse_into_tuple(begin, end, parse_end, allow_undelimited, result);
|
|
return std::apply(constructor, result);
|
|
}
|
|
|
|
private:
|
|
std::function<R(Ts...)> constructor;
|
|
};
|
|
|
|
namespace internal {
|
|
template<typename... Ts>
|
|
std::string construct_string_type_name_list_from_tuple(std::tuple<Ts...> types) {
|
|
std::string expected_types;
|
|
expected_types = std::get<0>(types)->get_name();
|
|
if (std::tuple_size_v < decltype(types) >> 1) {
|
|
internal::tuple_foreach(types, [&expected_types]<typename T>(type_handle_impl<T> &type, auto idx) {
|
|
if (idx == std::tuple_size_v<decltype(types)> - 1 || idx == 0) {
|
|
return;
|
|
}
|
|
expected_types += ", " + type->get_name();
|
|
});
|
|
expected_types += " or " + std::get<std::tuple_size_v<decltype(types)> - 1>(types)->get_name();
|
|
}
|
|
return expected_types;
|
|
}
|
|
}// namespace internal
|
|
template<typename... Ts>
|
|
class union_type;
|
|
|
|
template<typename... Ts>
|
|
requires(std::tuple_size<internal::distinct_types_tuple<Ts...>>::value > 1)
|
|
class union_type<Ts...> : public type_impl<internal::distinct_types_variant<Ts...>> {
|
|
using variant_type = internal::distinct_types_variant<Ts...>;
|
|
|
|
public:
|
|
union_type(std::string name, std::tuple<type_handle_impl<Ts>...> types) : type_impl<variant_type>(std::move(name)), types(std::move(types)) {}
|
|
|
|
variant_type parse(const char *begin, const char *end, const char *&parse_end, internal::parser_allow_undelimited allow_undelimited) override {
|
|
std::vector<variant_type> results{};
|
|
std::vector<std::string> result_types{};
|
|
size_t length = 0;
|
|
internal::tuple_foreach(types, [&begin, &end, &length, &results, &result_types, &allow_undelimited]<typename T>(type_handle_impl<T> &type, auto) {
|
|
const char *this_parse_end;
|
|
try {
|
|
auto val = type->parse(begin, end, this_parse_end, allow_undelimited);
|
|
size_t this_length = this_parse_end - begin;
|
|
if (this_length >= length) {
|
|
variant_type variant_wrapped_val = val;
|
|
if (this_length == length) {
|
|
results.push_back(variant_wrapped_val);
|
|
result_types.push_back(type->get_name());
|
|
} else {
|
|
length = this_length;
|
|
results = {variant_wrapped_val};
|
|
result_types = {type->get_name()};
|
|
}
|
|
}
|
|
} catch (errors::type_parsing_error &e) {
|
|
// ignore errors
|
|
}
|
|
});
|
|
if (length == 0) {
|
|
std::string expected_types = internal::construct_string_type_name_list_from_tuple(types);
|
|
throw errors::type_parsing_error(this->name, std::string(begin, end), 0, std::format("expected {}", expected_types));
|
|
}
|
|
if (results.size() > 1) {
|
|
throw errors::ambiguous_parse_error(this->name, std::string(begin, end), 0, result_types);
|
|
}
|
|
parse_end = begin + length;
|
|
return results[0];
|
|
}
|
|
|
|
private:
|
|
std::tuple<type_handle_impl<Ts>...> types;
|
|
};
|
|
|
|
template<typename T, typename... Ts>
|
|
requires(std::tuple_size<internal::distinct_types_tuple<T, Ts...>>::value == 1)
|
|
class union_type<T, Ts...> : public type_impl<T> {
|
|
public:
|
|
union_type(std::string name, std::tuple<type_handle_impl<T>, type_handle_impl<Ts>...> types) : type_impl<T>(std::move(name)), types(std::move(types)) {}
|
|
|
|
T parse(const char *begin, const char *end, const char *&parse_end, internal::parser_allow_undelimited) override {
|
|
std::vector<T> results{};
|
|
std::vector<std::string> result_types{};
|
|
size_t length = 0;
|
|
internal::tuple_foreach(types, [&begin, &end, &length, &results, &result_types](type_handle_impl<T> &type, auto) {
|
|
const char *this_parse_end;
|
|
try {
|
|
auto val = type->parse(begin, end, this_parse_end);
|
|
size_t this_length = this_parse_end - begin;
|
|
if (this_length >= length) {
|
|
if (this_length == length) {
|
|
results.push_back(val);
|
|
result_types.push_back(type->get_name());
|
|
} else {
|
|
length = this_length;
|
|
results = {val};
|
|
result_types = {type->get_name()};
|
|
}
|
|
}
|
|
} catch (errors::type_parsing_error &e) {
|
|
// ignore errors
|
|
}
|
|
});
|
|
if (length == 0) {
|
|
std::string expected_types = internal::construct_string_type_name_list_from_tuple(types);
|
|
throw errors::type_parsing_error(this->name, std::string(begin, end), 0, std::format("expected {}", expected_types));
|
|
}
|
|
if (results.size() > 1 && !std::equal(results.begin() + 1, results.end(), results.begin())) {
|
|
throw errors::ambiguous_parse_error(this->name, std::string(begin, end), 0, result_types);
|
|
}
|
|
parse_end = begin + length;
|
|
return results[0];
|
|
}
|
|
|
|
private:
|
|
std::tuple<type_handle_impl<T>, type_handle_impl<Ts>...> types;
|
|
};
|
|
|
|
// TODO: help output
|
|
class parser {
|
|
void validate_opt_or_flag_name(const std::string &name) {
|
|
if (!name.starts_with("--")) {
|
|
if (!name.starts_with("-") || name.length() != 2) {
|
|
throw errors::invalid_option_name_error(name);
|
|
}
|
|
}
|
|
if (options.find(name) != options.end()) {
|
|
throw errors::duplicate_option_name(name);
|
|
}
|
|
}
|
|
|
|
bool allow_remaining_args_{};
|
|
|
|
public:
|
|
internal::string_map<option_handle> options;
|
|
std::vector<arg_handle> arguments;
|
|
|
|
template<typename T>
|
|
[[nodiscard]] option_handle_impl<T> option(const std::string &name, const type_handle_impl<T> &type) {
|
|
validate_opt_or_flag_name(name);
|
|
auto o = std::make_shared<option_impl<T>>(name, type);
|
|
options[name] = o;
|
|
return o;
|
|
}
|
|
|
|
template<typename T>
|
|
[[nodiscard]] option_handle_impl<T> option(const std::string &name, const std::string &alt_name, const type_handle_impl<T> &type) {
|
|
validate_opt_or_flag_name(name);
|
|
validate_opt_or_flag_name(alt_name);
|
|
auto o = std::make_shared<option_impl<T>>(name, type);
|
|
options[name] = o;
|
|
options[alt_name] = o;
|
|
return o;
|
|
}
|
|
|
|
template<typename T>
|
|
[[nodiscard]] repeatable_option_handle_impl<T> repeatable_option(const std::string &name, const type_handle_impl<T> &type) {
|
|
validate_opt_or_flag_name(name);
|
|
auto o = std::make_shared<repeatable_option_impl<T>>(name, type);
|
|
options[name] = o;
|
|
return o;
|
|
}
|
|
|
|
template<typename T>
|
|
[[nodiscard]] repeatable_option_handle_impl<T> repeatable_option(const std::string &name, const std::string &alt_name, const type_handle_impl<T> &type) {
|
|
validate_opt_or_flag_name(name);
|
|
validate_opt_or_flag_name(alt_name);
|
|
auto o = std::make_shared<repeatable_option_impl<T>>(name, type);
|
|
options[name] = o;
|
|
options[alt_name] = o;
|
|
return o;
|
|
}
|
|
|
|
[[nodiscard]] flag_handle_impl flag(const std::string &name) {
|
|
validate_opt_or_flag_name(name);
|
|
auto f = std::make_shared<flag_impl>(name);
|
|
options[name] = f;
|
|
return f;
|
|
}
|
|
|
|
[[nodiscard]] flag_handle_impl flag(const std::string &name, const std::string &alt_name) {
|
|
validate_opt_or_flag_name(name);
|
|
validate_opt_or_flag_name(alt_name);
|
|
auto f = std::make_shared<flag_impl>(name);
|
|
options[name] = f;
|
|
options[alt_name] = f;
|
|
return f;
|
|
}
|
|
|
|
[[nodiscard]] repeatable_flag_handle_impl repeatable_flag(const std::string &name) {
|
|
validate_opt_or_flag_name(name);
|
|
auto f = std::make_shared<repeatable_flag_impl>(name);
|
|
options[name] = f;
|
|
return f;
|
|
}
|
|
|
|
[[nodiscard]] repeatable_flag_handle_impl repeatable_flag(const std::string &name, const std::string &alt_name) {
|
|
validate_opt_or_flag_name(name);
|
|
validate_opt_or_flag_name(alt_name);
|
|
auto f = std::make_shared<repeatable_flag_impl>(name);
|
|
options[name] = f;
|
|
options[alt_name] = f;
|
|
return f;
|
|
}
|
|
|
|
template<typename T>
|
|
[[nodiscard]] single_arg_handle<T> arg(const std::string &name, type_handle_impl<T> type) {
|
|
auto a = std::make_shared<single_arg<T>>(name, type);
|
|
arguments.push_back(a);
|
|
return a;
|
|
}
|
|
|
|
template<typename T>
|
|
[[nodiscard]] optional_arg_handle<T> optional_arg(const std::string name, type_handle_impl<T> type) {
|
|
auto a = std::make_shared<argparser::optional_arg<T>>(name, type);
|
|
arguments.push_back(a);
|
|
return a;
|
|
}
|
|
|
|
template<typename T>
|
|
[[nodiscard]] repeatable_arg_handle<T> repeatable_arg(const std::string name, type_handle_impl<T> type) {
|
|
auto a = std::make_shared<argparser::repeatable_arg<T>>(name, type);
|
|
arguments.push_back(a);
|
|
return a;
|
|
}
|
|
|
|
[[nodiscard]] type_handle_impl<std::string> enum_type(const std::string &name, const std::vector<std::string> &values) {
|
|
internal::string_map<std::string> actual_values{};
|
|
for (const auto &item: values) {
|
|
actual_values[item] = item;
|
|
}
|
|
auto parser_fn = internal::make_enum_parser(name, actual_values);
|
|
auto t = std::make_shared<argparser::basic_type<std::string>>(name, parser_fn);
|
|
known_types[name] = t;
|
|
return t;
|
|
}
|
|
|
|
template<typename T>
|
|
[[nodiscard]] type_handle_impl<T> enum_type(const std::string &name, const internal::string_map<T> &values) {
|
|
auto parser_fn = internal::make_enum_parser(name, values);
|
|
auto t = std::make_shared<argparser::basic_type<T>>(name, parser_fn);
|
|
known_types[name] = t;
|
|
return t;
|
|
}
|
|
|
|
template<typename... Ts>
|
|
[[nodiscard]] type_handle_impl<std::tuple<Ts...>>
|
|
tuple_type(const std::string &name, const type_handle_impl<Ts> &...types) {
|
|
auto t = std::make_shared<argparser::tuple_type<Ts...>>(name, std::forward_as_tuple(types...));
|
|
known_types[name] = t;
|
|
return t;
|
|
}
|
|
|
|
template<typename R, typename... Ts>
|
|
[[nodiscard]] type_handle_impl<R> tuple_type(const std::string &name, const type_handle_impl<Ts> &...types) {
|
|
auto t = std::make_shared<custom_tuple_type<R, Ts...>>(name, std::forward_as_tuple(types...));
|
|
known_types[name] = t;
|
|
return t;
|
|
}
|
|
|
|
template<typename R, typename... Ts>
|
|
[[nodiscard]] type_handle_impl<R> tuple_type(const std::string &name, typename std::type_identity<std::function<R(Ts...)>>::type constructor, const type_handle_impl<Ts> &...types) {
|
|
auto t = std::make_shared<custom_tuple_type_with_constructor<R, Ts...>>(name, std::forward_as_tuple(types...), constructor);
|
|
known_types[name] = t;
|
|
return t;
|
|
}
|
|
|
|
template<typename R, typename... Ts>
|
|
[[nodiscard]] type_handle_impl<R> tuple_type(const std::string &name, const std::tuple<type_handle_impl<Ts>...> &types, typename std::type_identity<std::function<R(Ts...)>>::type constructor) {
|
|
auto t = std::make_shared<custom_tuple_type_with_constructor<R, Ts...>>(name, std::move(types), constructor);
|
|
known_types[name] = t;
|
|
return t;
|
|
}
|
|
|
|
template<typename... Ts>
|
|
[[nodiscard]] internal::if_not_single_type<type_handle_impl<internal::distinct_types_variant<Ts...>>, Ts...>
|
|
union_type(const std::string &name, const type_handle_impl<Ts> &...types) {
|
|
auto t = std::make_shared<argparser::union_type<Ts...>>(name, std::forward_as_tuple(types...));
|
|
known_types[name] = t;
|
|
return t;
|
|
}
|
|
|
|
template<typename... Ts>
|
|
[[nodiscard]] type_handle_impl<internal::single_type<Ts...>>
|
|
union_type(const std::string &name, const type_handle_impl<Ts> &...types) {
|
|
auto t = std::make_shared<argparser::union_type<Ts...>>(name, std::forward_as_tuple(types...));
|
|
known_types[name] = t;
|
|
return t;
|
|
}
|
|
|
|
template<typename T>
|
|
[[nodiscard]] type_handle_impl<std::vector<T>> list_type(const std::string &name, const type_handle_impl<T> &type) {
|
|
auto t = std::make_shared<argparser::list_type<T>>(name, type);
|
|
known_types[name] = t;
|
|
return t;
|
|
}
|
|
|
|
internal::string_map<type_handle> known_types;
|
|
|
|
template<typename T, internal::parse_opt parse_opt = internal::parse_opt::None>
|
|
[[nodiscard]] type_handle_impl<T> basic_type(const std::string &name) {
|
|
auto parse_fn = internal::automatic_parser<T, parse_opt>::make_parser(name);
|
|
auto t = std::make_shared<argparser::basic_type<T>>(name, parse_fn);
|
|
known_types[name] = t;
|
|
return t;
|
|
}
|
|
|
|
void enable_remaining_args() {
|
|
allow_remaining_args_ = true;
|
|
}
|
|
|
|
[[nodiscard]] parse_result parse(const std::vector<std::string> ¶ms) {
|
|
std::deque<std::string> params_queue(params.size());
|
|
std::copy(params.begin(), params.end(), params_queue.begin());
|
|
auto pr = parse_result{};
|
|
auto arg_iter = arguments.begin();
|
|
while (!params_queue.empty()) {
|
|
auto current = params_queue.front();
|
|
bool looks_like_option = false;
|
|
option_handle opt{};
|
|
std::string opt_name;
|
|
if (allow_remaining_args_ && current == "--") {
|
|
params_queue.pop_front();
|
|
pr.set_remaining(std::vector<std::string>(std::make_move_iterator(params_queue.begin()), std::make_move_iterator(params_queue.end())));
|
|
params_queue.clear();
|
|
break;
|
|
}
|
|
if (current.starts_with("--")) {
|
|
looks_like_option = true;
|
|
if (current.contains('=')) {
|
|
opt_name = current.substr(0, current.find_first_of('='));
|
|
auto optit = options.find(opt_name);
|
|
if (optit != options.end()) {
|
|
opt = optit->second;
|
|
if (!opt->consumes_value()) {
|
|
throw errors::unexpected_option_value_error(opt_name);
|
|
}
|
|
auto remain = current.substr(opt_name.length() + 1);
|
|
params_queue.pop_front();
|
|
params_queue.push_front(remain);
|
|
}
|
|
} else {
|
|
opt_name = current;
|
|
auto optit = options.find(opt_name);
|
|
if (optit != options.end()) {
|
|
opt = optit->second;
|
|
params_queue.pop_front();
|
|
}
|
|
}
|
|
} else if (current.starts_with("-") && current.length() >= 2) {
|
|
looks_like_option = true;
|
|
if (current.length() == 2) {
|
|
opt_name = current;
|
|
auto optit = options.find(opt_name);
|
|
if (optit != options.end()) {
|
|
opt = optit->second;
|
|
params_queue.pop_front();
|
|
}
|
|
} else {
|
|
opt_name = current.substr(0, 2);
|
|
auto optit = options.find(opt_name);
|
|
if (optit != options.end()) {
|
|
opt = optit->second;
|
|
auto remain = current.substr(2);
|
|
params_queue.pop_front();
|
|
if (opt->consumes_value()) {
|
|
params_queue.push_front(remain);
|
|
} else {
|
|
params_queue.push_front("-" + remain);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (opt == nullptr) {
|
|
if (arg_iter == arguments.end()) {
|
|
if (allow_remaining_args_) {
|
|
pr.set_remaining(std::vector<std::string>(std::make_move_iterator(params_queue.begin()), std::make_move_iterator(params_queue.end())));
|
|
params_queue.clear();
|
|
} else {
|
|
if (looks_like_option) {
|
|
throw errors::unknown_option_error(opt_name);
|
|
} else {
|
|
throw errors::too_many_arguments_error();
|
|
}
|
|
}
|
|
} else {
|
|
auto arg = arg_iter->get();
|
|
try {
|
|
arg->parse(current, pr);
|
|
params_queue.pop_front();
|
|
} catch (errors::runtime_error &) {
|
|
if (!arg->has_parsed_enough(pr)) {
|
|
if (looks_like_option) {
|
|
throw errors::unknown_option_error(opt_name);
|
|
} else {
|
|
throw;
|
|
}
|
|
}
|
|
if (std::next(arg_iter) == arguments.end()) {
|
|
throw;
|
|
}
|
|
arg_iter = std::next(arg_iter);
|
|
continue;// retry parsing the value with the next arg
|
|
}
|
|
if (!arg->can_parse_more(pr)) {
|
|
arg_iter = std::next(arg_iter);
|
|
}
|
|
}
|
|
} else {
|
|
std::optional<std::string> option_val = std::nullopt;
|
|
auto consumes_value = opt->consumes_value();
|
|
if (consumes_value) {
|
|
if (params_queue.empty()) {
|
|
throw errors::missing_option_value_error(opt_name);
|
|
}
|
|
option_val = params_queue.front();
|
|
params_queue.pop_front();
|
|
}
|
|
std::any val = {};
|
|
auto canonical_name = opt->get_name();
|
|
if (pr.has_opt(canonical_name)) {
|
|
val = pr.get_opt(canonical_name);
|
|
}
|
|
try {
|
|
opt->parse(option_val, val);
|
|
} catch (errors::type_parsing_error &) {
|
|
if (option_val.has_value() && options.find(option_val.value()) != options.end()) {
|
|
throw errors::missing_option_value_error(opt_name);
|
|
} else {
|
|
throw;
|
|
}
|
|
}
|
|
pr.set_opt(canonical_name, val);
|
|
}
|
|
}
|
|
if (arg_iter != arguments.end() && !arg_iter->get()->has_parsed_enough(pr)) {
|
|
throw errors::missing_argument_error(arg_iter->get()->get_name());
|
|
}
|
|
for (const auto &[name, option]: options) {
|
|
option->validate(pr);
|
|
}
|
|
return pr;
|
|
}
|
|
};
|
|
}// namespace argparser
|