#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace argparser { namespace errors { class runtime_error : public std::runtime_error { public: template explicit runtime_error(Ts... args) : std::runtime_error(args...) {} }; class logic_error : public std::logic_error { public: template 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 min, std::optional 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 min, std::optional 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 possible_types) : type_parsing_error(type_name, input, error_pos, make_message(possible_types)) {} private: static std::string make_message(std::vector 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(static_cast>(a) | static_cast>(b)); } constexpr parser_allow_undelimited operator&(parser_allow_undelimited a, parser_allow_undelimited b) { return static_cast(static_cast>(a) & static_cast>(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(static_cast>(a) | static_cast>(b)); } constexpr parse_opt operator&(parse_opt a, parse_opt b) { return static_cast(static_cast>(a) & static_cast>(b)); } template constexpr bool enum_flag_contains(T a, T b) { return static_cast>(a & b) != 0; } template using string_map = std::map>; }// namespace internal class parse_result { internal::string_map opts{}; internal::string_map args{}; std::vector 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 remaining) { remaining_args = std::move(remaining); } [[nodiscard]] std::vector 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; template 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 using type_handle_impl = std::shared_ptr>; 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 static T parse_single_value(std::string input, const type_handle_impl &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; namespace internal { template using parser_func = std::function; } template class basic_type : public type_impl { public: basic_type(std::string name, internal::parser_func parser) : type_impl(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 parser; }; namespace internal { template class automatic_parser { public: static parser_func 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 class automatic_parser { 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 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 parser_func make_enum_parser(const std::string &name, internal::string_map 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 struct UniqueTypes; template typename X, typename... Ts> struct UniqueTypes> { using type = X; }; template typename X, typename... Ts, typename T, typename... Us> struct UniqueTypes, T, Us...> { using type = typename UniqueTypes< typename std::conditional< std::disjunction...>::value, X, X>::type, Us...>::type; }; template typename X, typename... Ts> struct Distinct { using type = typename UniqueTypes, Ts...>::type; }; }// namespace internal::distinct_types_impl namespace internal { template using distinct_types_variant = typename internal::distinct_types_impl::Distinct::type; template using distinct_types_tuple = typename internal::distinct_types_impl::Distinct::type; template using if_single_type = std::enable_if_t>::value == 1, T>; template using if_not_single_type = std::enable_if_t<(std::tuple_size>::value > 1), T>; template using single_type_conditional = std::conditional<(std::tuple_size>::value == 1), T, F>; template using single_type = if_single_type>::type, Ts...>; }// namespace internal template class list_type : public type_impl> { public: list_type(std::string name, type_handle_impl element_type) : type_impl>(std::move(name)), element_type(element_type) {} std::vector parse(const char *begin, const char *end, const char *&parse_end, internal::parser_allow_undelimited allow_undelimited) override { auto cur_pos = begin; std::vector 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 element_type; }; class option { public: explicit option(std::string name) : name(std::move(name)) {} virtual ~option() = default; void parse(std::optional 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 arg, std::any &val) = 0; }; using option_handle = std::shared_ptr