#pragma once /* * __ _ _ __ __ _ _ __ __ _ _ __ ___ ___ * / _` | '__/ _` | '_ \ / _` | '__/ __|/ _ \ Argument Parser for Modern C++ * | (_| | | | (_| | |_) | (_| | | \__ \ __/ http://github.com/p-ranav/argparse * \__,_|_| \__, | .__/ \__,_|_| |___/\___| * |___/|_| * * Licensed under the MIT License . * SPDX-License-Identifier: MIT * Copyright (c) 2019-2022 Pranav Srinivas Kumar * and other contributors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #include #ifndef ARGPARSE_MODULE_USE_STD_MODULE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif #include "src/util/defs.hpp" #include "src/util/error.hpp" #include "src/util/types.hpp" #ifndef ARGPARSE_CUSTOM_STRTOF #define ARGPARSE_CUSTOM_STRTOF strtof #endif #ifndef ARGPARSE_CUSTOM_STRTOD #define ARGPARSE_CUSTOM_STRTOD strtod #endif #ifndef ARGPARSE_CUSTOM_STRTOLD #define ARGPARSE_CUSTOM_STRTOLD strtold #endif // ReSharper disable CppTemplateParameterNeverUsed, CppDFATimeOver // NOLINTBEGIN(readability-identifier-naming, readability-identifier-length, modernize-use-nullptr) namespace argparse { using namespace util::types; using util::error::DracError, util::error::DracErrorCode; namespace details { template struct HasContainerTraits : std::false_type {}; template <> struct HasContainerTraits : std::false_type {}; template <> struct HasContainerTraits : std::false_type {}; template struct HasContainerTraits().begin()), decltype(std::declval().end()), decltype(std::declval().size())>> : std::true_type {}; template inline constexpr bool IsContainer = HasContainerTraits::value; template struct HasStreamableTraits : std::false_type {}; template struct HasStreamableTraits() << std::declval())>> : std::true_type {}; template inline constexpr bool IsStreamable = HasStreamableTraits::value; constexpr usize repr_max_container_size = 5; template concept Formattable = requires(const T& t, std::basic_format_context ctx) { std::formatter, CharT>().format(t, ctx); }; template static auto repr(const T& val) -> String { if constexpr (std::is_same_v) return val ? "true" : "false"; else if constexpr (std::is_convertible_v) return std::format("\"{}\"", String { StringView { val } }); else if constexpr (IsContainer) { String result = "{"; const auto size = val.size(); if (size > 0) { bool first = true; auto transformed_view = val | std::views::transform([](const auto& elem) { return details::repr(elem); }); if (size <= repr_max_container_size) { for (const String& elem_repr : transformed_view) { if (!first) result += " "; result += elem_repr; first = false; } } else { for (const String& elem_repr : transformed_view | std::views::take(repr_max_container_size - 1)) { if (!first) result += " "; result += elem_repr; first = false; } result += "... "; result += details::repr(*std::prev(val.end())); } } result += "}"; return result; } else if constexpr (Formattable) return std::format("{}", val); else if constexpr (IsStreamable) { std::stringstream out; out << val; return out.str(); } else return ""; } constexpr i32 radix_2 = 2; constexpr i32 radix_8 = 8; constexpr i32 radix_10 = 10; constexpr i32 radix_16 = 16; template constexpr fn apply_plus_one_impl(F&& f, Tuple&& t, Extra&& x, std::index_sequence /*unused*/) -> decltype(auto) { return std::invoke(std::forward(f), std::get(std::forward(t))..., std::forward(x)); } template constexpr fn apply_plus_one(F&& f, Tuple&& t, Extra&& x) -> decltype(auto) { return details::apply_plus_one_impl( std::forward(f), std::forward(t), std::forward(x), std::make_index_sequence>> {} ); } constexpr fn pointer_range(const StringView s) noexcept -> std::tuple { return { s.data(), s.data() + s.size() }; } template constexpr fn starts_with(std::basic_string_view prefix, std::basic_string_view s) noexcept -> bool { return s.substr(0, prefix.size()) == prefix; } enum class chars_format : u8 { scientific = 0xf1, fixed = 0xf2, hex = 0xf4, binary = 0xf8, general = fixed | scientific }; struct ConsumeBinaryPrefixResult { bool is_binary; StringView rest; }; constexpr fn consume_binary_prefix(StringView s) -> ConsumeBinaryPrefixResult { if (starts_with(StringView { "0b" }, s) || starts_with(StringView { "0B" }, s)) { s.remove_prefix(2); return { .is_binary = true, .rest = s }; } return { .is_binary = false, .rest = s }; } struct ConsumeHexPrefixResult { bool is_hexadecimal; StringView rest; }; using namespace std::literals; constexpr fn consume_hex_prefix(StringView s) -> ConsumeHexPrefixResult { if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) { s.remove_prefix(2); return { .is_hexadecimal = true, .rest = s }; } return { .is_hexadecimal = false, .rest = s }; } template fn do_from_chars(const StringView s) -> Result { T x { 0 }; auto [first, last] = pointer_range(s); auto [ptr, ec] = std::from_chars(first, last, x, Param); if (ec == std::errc()) { if (ptr == last) return x; return Err(DracError(DracErrorCode::ParseError, std::format("pattern '{}' does not match to the end", String(s)))); } if (ec == std::errc::invalid_argument) return Err(DracError(DracErrorCode::InvalidArgument, std::format("pattern '{}' not found", String(s)))); if (ec == std::errc::result_out_of_range) return Err(DracError(DracErrorCode::ParseError, std::format("'{}' not representable", String(s)))); return Err(DracError(DracErrorCode::InternalError, std::format("Unknown parsing error for '{}'", String(s)))); } template struct parse_number { static fn operator()(const StringView s)->Result { return do_from_chars(s); } }; template struct parse_number { static fn operator()(const StringView s)->Result { if (auto [ok, rest] = consume_binary_prefix(s); ok) return do_from_chars(rest); return Err(DracError(DracErrorCode::InvalidArgument, "pattern not found")); } }; template struct parse_number { static fn operator()(const StringView s)->Result { Result result; if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) { if (auto [ok, rest] = consume_hex_prefix(s); ok) result = do_from_chars(rest); else return Err(DracError(DracErrorCode::InternalError, std::format("Inconsistent hex prefix detection for '{}'", String(s)))); } else result = do_from_chars(s); if (!result) return Err(DracError(result.error().code, std::format("Failed to parse '{}' as hexadecimal: {}", String(s), result.error().message))); return result; } }; template struct parse_number { static fn operator()(const StringView s)->Result { if (auto [ok, rest] = consume_hex_prefix(s); ok) { Result result = do_from_chars(rest); if (!result) return Err(DracError(result.error().code, std::format("Failed to parse '{}' as hexadecimal: {}", String(s), result.error().message))); return result; } if (auto [ok_binary, rest_binary] = consume_binary_prefix(s); ok_binary) { Result result = do_from_chars(rest_binary); if (!result) return Err(DracError(result.error().code, std::format("Failed to parse '{}' as binary: {}", String(s), result.error().message))); return result; } if (starts_with("0"sv, s)) { Result result = do_from_chars(s); if (!result) return Err(DracError(result.error().code, std::format("Failed to parse '{}' as octal: {}", String(s), result.error().message))); return result; } Result result = do_from_chars(s); if (!result) return Err(DracError(result.error().code, std::format("Failed to parse '{}' as decimal integer: {}", String(s), result.error().message))); return result; } }; template inline constexpr std::nullptr_t generic_strtod = nullptr; template <> inline const auto generic_strtod = ARGPARSE_CUSTOM_STRTOF; template <> inline const auto generic_strtod = ARGPARSE_CUSTOM_STRTOD; template <> inline const auto generic_strtod = ARGPARSE_CUSTOM_STRTOLD; template fn do_strtod(const String& s) -> Result { if (isspace(static_cast(s[0])) || s[0] == '+') return Err(DracError(DracErrorCode::InvalidArgument, std::format("pattern '{}' not found", s))); auto [first, last] = pointer_range(s); char* ptr = nullptr; errno = 0; auto x = generic_strtod(first, &ptr); if (errno == 0) { if (ptr == last) return x; return Err(DracError(DracErrorCode::ParseError, std::format("pattern '{}' does not match to the end", s))); } if (errno == ERANGE) return Err(DracError(DracErrorCode::ParseError, std::format("'{}' not representable", s))); return Err(DracError(std::error_code(errno, std::system_category()))); } template struct parse_number { fn operator()(const String& s)->Result { if (auto [is_hex, rest] = consume_hex_prefix(s); is_hex) return Err(DracError(DracErrorCode::InvalidArgument, "chars_format::general does not parse hexfloat")); if (auto [is_bin, rest] = consume_binary_prefix(s); is_bin) return Err(DracError(DracErrorCode::InvalidArgument, "chars_format::general does not parse binfloat")); Result result = do_strtod(s); if (!result) return Err(DracError(result.error().code, std::format("Failed to parse '{}' as number: {}", s, result.error().message))); return result; } }; template struct parse_number { fn operator()(const String& s)->Result { if (auto [is_hex, rest] = consume_hex_prefix(s); !is_hex) return Err(DracError(DracErrorCode::InvalidArgument, "chars_format::hex requires hexfloat format (e.g., 0x1.2p3)")); if (auto [is_bin, rest] = consume_binary_prefix(s); is_bin) return Err(DracError(DracErrorCode::InvalidArgument, "chars_format::hex does not parse binfloat")); Result result = do_strtod(s); if (!result) return Err(DracError(result.error().code, std::format("Failed to parse '{}' as hexadecimal float: {}", s, result.error().message))); return result; } }; template struct parse_number { fn operator()(const String& s)->Result { if (auto [is_hex, rest] = consume_hex_prefix(s); is_hex) return Err(DracError(DracErrorCode::InvalidArgument, "chars_format::binary does not parse hexfloat")); if (auto [is_bin, rest] = consume_binary_prefix(s); !is_bin) return Err(DracError(DracErrorCode::InvalidArgument, "chars_format::binary requires binfloat format (e.g., 0b1.01p2)")); Result result = do_strtod(s); if (!result) return Err(DracError(result.error().code, std::format("Failed to parse '{}' as binary float: {}", s, result.error().message))); return result; } }; template struct parse_number { fn operator()(const String& s)->Result { if (const auto [is_hex, rest] = consume_hex_prefix(s); is_hex) return Err(DracError(DracErrorCode::InvalidArgument, "chars_format::scientific does not parse hexfloat")); if (const auto [is_bin, rest] = consume_binary_prefix(s); is_bin) return Err(DracError(DracErrorCode::InvalidArgument, "chars_format::scientific does not parse binfloat")); if (s.find_first_of("eE") == String::npos) return Err(DracError(DracErrorCode::InvalidArgument, "chars_format::scientific requires exponent part")); Result result = do_strtod(s); if (!result) return Err(DracError(result.error().code, std::format("Failed to parse '{}' as scientific notation: {}", s, result.error().message))); return result; } }; template struct parse_number { fn operator()(const String& s)->Result { if (const auto [is_hex, rest] = consume_hex_prefix(s); is_hex) return Err(DracError(DracErrorCode::InvalidArgument, "chars_format::fixed does not parse hexfloat")); if (const auto [is_bin, rest] = consume_binary_prefix(s); is_bin) return Err(DracError(DracErrorCode::InvalidArgument, "chars_format::fixed does not parse binfloat")); if (s.find_first_of("eE") != String::npos) return Err(DracError(DracErrorCode::InvalidArgument, "chars_format::fixed does not parse exponent part")); Result result = do_strtod(s); if (!result) return Err(DracError(result.error().code, std::format("Failed to parse '{}' as fixed notation: {}", s, result.error().message))); return result; } }; template concept ToStringConvertible = std::convertible_to || std::convertible_to || requires(const T& t) { std::format("{}", t); }; template fn join(StrIt first, StrIt last, const String& separator) -> String { if (first == last) return ""; std::stringstream value; value << *first; ++first; while (first != last) { value << separator << *first; ++first; } return value.str(); } template struct can_invoke_to_string { template // ReSharper disable CppFunctionIsNotImplemented static fn test(int) -> decltype(std::to_string(std::declval()), std::true_type {}); template static fn test(...) -> std::false_type; // ReSharper restore CppFunctionIsNotImplemented static constexpr bool value = decltype(test(0))::value; }; template struct IsChoiceTypeSupported { using CleanType = std::decay_t; static const bool value = std::is_integral_v || std::is_same_v || std::is_same_v || std::is_same_v; }; template fn get_levenshtein_distance(const StringType& s1, const StringType& s2) -> usize { Vec> dp( s1.size() + 1, Vec(s2.size() + 1, 0) ); for (usize i = 0; i <= s1.size(); ++i) { for (usize j = 0; j <= s2.size(); ++j) { if (i == 0) { dp[i][j] = j; } else if (j == 0) { dp[i][j] = i; } else if (s1[i - 1] == s2[j - 1]) { dp[i][j] = dp[i - 1][j - 1]; } else { dp[i][j] = 1 + std::min({ dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1] }); } } } return dp[s1.size()][s2.size()]; } template fn get_most_similar_string(const Map& map, const String& input) -> String { String most_similar {}; usize min_distance = (std::numeric_limits::max)(); for (const auto& entry : map) if (const usize distance = get_levenshtein_distance(entry.first, input); distance < min_distance) { min_distance = distance; most_similar = entry.first; } return most_similar; } template class Ref> struct is_specialization : std::false_type {}; template