- add compile checker in cmake to detect charconv support status. - and expose macro for yycc to enable different part of charconv repectively to prevent duplicated defines.
447 lines
15 KiB
C++
447 lines
15 KiB
C++
#pragma once
|
|
#include "../../macro/stl_detector.hpp"
|
|
|
|
//#if defined(YYCC_STL_CLANGSTL)
|
|
|
|
/**
|
|
* @private
|
|
* @file This is the polyfill for LLVM libcxx charconv header
|
|
* which including \c std::from_chars and \c std::to_chars utilities.
|
|
* These 2 functions are only \b fully implemented in the latest Clang (20+)
|
|
* and partially implemented in the latest Apple Clang (Xcode 16).
|
|
* This should be removed once both Clang official and Apple Clang libcxx \b fully provide them.
|
|
* This polyfill is generated by AI.
|
|
*/
|
|
|
|
#include <charconv>
|
|
#include <system_error>
|
|
#include <cctype>
|
|
#include <climits>
|
|
#include <cstdio>
|
|
#include <cerrno>
|
|
#include <cinttypes>
|
|
#include <type_traits>
|
|
#include <concepts>
|
|
#include <stdexcept>
|
|
#include <optional>
|
|
|
|
namespace std {
|
|
|
|
#if !defined(YYCC_CHARCONV_HAS_CHARS_FORMAT)
|
|
|
|
enum class chars_format : unsigned int {
|
|
scientific = 1,
|
|
fixed = 2,
|
|
hex = 4,
|
|
general = fixed | scientific // This should be 6 (fixed|scientific)
|
|
};
|
|
|
|
#endif
|
|
|
|
#if !defined(YYCC_CHARCONV_HAS_FROM_CHARS_RESULT)
|
|
|
|
struct from_chars_result {
|
|
const char* ptr;
|
|
std::errc ec;
|
|
|
|
friend bool operator==(const from_chars_result&, const from_chars_result&) = default;
|
|
constexpr explicit operator bool() const noexcept { return ec == std::errc{}; }
|
|
};
|
|
|
|
#endif
|
|
|
|
#if !defined(YYCC_CHARCONV_HAS_TO_CHARS_RESULT)
|
|
|
|
struct to_chars_result {
|
|
char* ptr;
|
|
std::errc ec;
|
|
|
|
friend bool operator==(const to_chars_result&, const to_chars_result&) = default;
|
|
constexpr explicit operator bool() const noexcept { return ec == std::errc{}; }
|
|
};
|
|
|
|
#endif
|
|
|
|
#if !defined(YYCC_CHARCONV_HAS_TO_CHARS_INT)
|
|
|
|
/// @private
|
|
enum class __integral_type {
|
|
u8,
|
|
u16,
|
|
u32,
|
|
u64,
|
|
i8,
|
|
i16,
|
|
i32,
|
|
i64,
|
|
};
|
|
|
|
/// @private
|
|
enum class __integral_base_type {
|
|
base8,
|
|
base10,
|
|
base16,
|
|
};
|
|
|
|
/// @private
|
|
template<typename T>
|
|
constexpr __integral_type __classify_int_type() {
|
|
if constexpr (std::is_same_v<T, std::uint8_t>) {
|
|
return __integral_type::u8;
|
|
} else if constexpr (std::is_same_v<T, std::uint16_t>) {
|
|
return __integral_type::u16;
|
|
} else if constexpr (std::is_same_v<T, std::uint32_t>) {
|
|
return __integral_type::u32;
|
|
} else if constexpr (std::is_same_v<T, std::uint64_t>) {
|
|
return __integral_type::u64;
|
|
} else if constexpr (std::is_same_v<T, std::int8_t>) {
|
|
return __integral_type::i8;
|
|
} else if constexpr (std::is_same_v<T, std::int16_t>) {
|
|
return __integral_type::i16;
|
|
} else if constexpr (std::is_same_v<T, std::int32_t>) {
|
|
return __integral_type::i32;
|
|
} else if constexpr (std::is_same_v<T, std::int64_t>) {
|
|
return __integral_type::i64;
|
|
} else {
|
|
static_cast(false, "Unsupported integral type");
|
|
}
|
|
}
|
|
|
|
/// @private
|
|
std::optional<__integral_base_type> __classify_int_base(int base) {
|
|
if (base == 8) {
|
|
return __integral_base_type::base8;
|
|
} else if (base == 10) {
|
|
return __integral_base_type::base10;
|
|
} else if (base == 16) {
|
|
return __integral_base_type::base16;
|
|
} else {
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
/// @private
|
|
/// @brief Helper to get printf format specifier based on type and base
|
|
template<__integral_type EIntTy>
|
|
const char* __get_int_format(__integral_base_type base) {
|
|
if (base == __integral_base_type::base8) {
|
|
if constexpr (EIntTy == __integral_type::u8) {
|
|
return "%" PRIo8;
|
|
} else if constexpr (EIntTy == __integral_type::u16) {
|
|
return "%" PRIo16;
|
|
} else if constexpr (EIntTy == __integral_type::u32) {
|
|
return "%" PRIo32;
|
|
} else if constexpr (EIntTy == __integral_type::u64) {
|
|
return "%" PRIo64;
|
|
} else {
|
|
static_assert(false, "Unsupported integral type and base");
|
|
}
|
|
} else if (base == __integral_base_type::base10) {
|
|
if constexpr (EIntTy == __integral_type::u8) {
|
|
return "%" PRIu8;
|
|
} else if constexpr (EIntTy == __integral_type::u16) {
|
|
return "%" PRIu16;
|
|
} else if constexpr (EIntTy == __integral_type::u32) {
|
|
return "%" PRIu32;
|
|
} else if constexpr (EIntTy == __integral_type::u64) {
|
|
return "%" PRIu64;
|
|
} else if constexpr (EIntTy == __integral_type::i8) {
|
|
return "%" PRId8;
|
|
} else if constexpr (EIntTy == __integral_type::i16) {
|
|
return "%" PRId16;
|
|
} else if constexpr (EIntTy == __integral_type::i32) {
|
|
return "%" PRId32;
|
|
} else if constexpr (EIntTy == __integral_type::i64) {
|
|
return "%" PRId64;
|
|
}
|
|
} else if (base == __integral_base_type::base16) {
|
|
if constexpr (EIntTy == __integral_type::u8) {
|
|
return "%" PRIx8;
|
|
} else if constexpr (EIntTy == __integral_type::u16) {
|
|
return "%" PRIx16;
|
|
} else if constexpr (EIntTy == __integral_type::u32) {
|
|
return "%" PRIx32;
|
|
} else if constexpr (EIntTy == __integral_type::u64) {
|
|
return "%" PRIx64;
|
|
} else {
|
|
static_assert(false, "Unsupported integral type and base");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Integer to_chars
|
|
template<typename T>
|
|
requires std::integral<T> && (!std::same_as<T, bool>)
|
|
to_chars_result to_chars(char* first, char* last, T value, int base = 10) {
|
|
if (first >= last) {
|
|
return {first, std::errc::value_too_large};
|
|
}
|
|
|
|
constexpr auto integral_type = __classify_int_type<T>();
|
|
const auto opt_integral_base_type = __classify_int_base(base);
|
|
if (!opt_integral_base_type.has_value()) {
|
|
return {first, std::errc::invalid_argument};
|
|
}
|
|
const auto integral_base_type = std::move(opt_integral_base_type.value());
|
|
|
|
// Use snprintf with appropriate format
|
|
const auto max_buffer_size = static_cast<size_t>(last - first);
|
|
const char* format_string = __get_int_format<integral_type>(integral_base_type);
|
|
int written = std::snprintf(first, max_buffer_size, format_string, value);
|
|
if (written < 0 || static_cast<size_t>(written) >= max_buffer_size) {
|
|
return {last, std::errc::value_too_large};
|
|
}
|
|
return {first + written, std::errc{}};
|
|
}
|
|
|
|
#endif
|
|
|
|
#if !defined(YYCC_CHARCONV_HAS_TO_CHARS_FLOAT)
|
|
|
|
/// @private
|
|
enum class __float_type {
|
|
f32,
|
|
f64,
|
|
};
|
|
|
|
/// @private
|
|
template<typename T>
|
|
constexpr __float_type __classify_float_type() {
|
|
if constexpr (std::is_same_v<T, float>) {
|
|
return __float_type::f32;
|
|
} else if constexpr (std::is_same_v<T, double>) {
|
|
return __float_type::f64;
|
|
} else {
|
|
static_assert(false, "Unsupported floating point type");
|
|
}
|
|
}
|
|
|
|
/// @private
|
|
enum class __float_fmt_type { general, scientific, fixed, hex };
|
|
|
|
/// @private
|
|
std::optional<__float_fmt_type> __classify_float_fmt(chars_format fmt) {
|
|
if (fmt == chars_format::general) {
|
|
return __float_fmt_type::general;
|
|
} else if (fmt == chars_format::scientific) {
|
|
return __float_fmt_type::scientific;
|
|
} else if (fmt == chars_format::fixed) {
|
|
return __float_fmt_type::fixed;
|
|
} else if (fmt == chars_format::hex) {
|
|
return __float_fmt_type::hex;
|
|
} else {
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
/// @private
|
|
template<__float_type TFpTy>
|
|
std::optional<const char*> __get_float_format(chars_format fmt) {
|
|
// Precision is passed by extra argument via ".*" format.
|
|
if (fmt == chars_format::general) {
|
|
if constexpr (TFpTy == __float_type::f32) {
|
|
return "%.*g";
|
|
} else if constexpr (TFpTy == __float_type::f64) {
|
|
return "%.*lg";
|
|
}
|
|
} else if (fmt == chars_format::scientific) {
|
|
if constexpr (TFpTy == __float_type::f32) {
|
|
return "%.*e";
|
|
} else if constexpr (TFpTy == __float_type::f64) {
|
|
return "%.*le";
|
|
}
|
|
} else if (fmt == chars_format::fixed) {
|
|
if constexpr (TFpTy == __float_type::f32) {
|
|
return "%.*f";
|
|
} else if constexpr (TFpTy == __float_type::f64) {
|
|
return "%.*lf";
|
|
}
|
|
} else if (fmt == chars_format::hex) {
|
|
if constexpr (TFpTy == __float_type::f32) {
|
|
return "%.*a";
|
|
} else if constexpr (TFpTy == __float_type::f64) {
|
|
return "%.*la";
|
|
}
|
|
} else {
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
// Float to_chars
|
|
template<typename T>
|
|
requires std::floating_point<T>
|
|
to_chars_result to_chars(char* first, char* last, T value, chars_format fmt, int precision = 0) {
|
|
if (first >= last) {
|
|
return {first, std::errc::value_too_large};
|
|
}
|
|
|
|
constexpr auto float_type = __classify_float_type<T>();
|
|
const auto opt_float_fmt_type = __classify_float_fmt(fmt);
|
|
if (!opt_float_fmt_type.has_value()) {
|
|
return {first, std::errc::invalid_argument};
|
|
}
|
|
const auto float_fmt_type = std::move(opt_float_fmt_type.value());
|
|
|
|
const auto max_buffer_size = static_cast<size_t>(last - first);
|
|
const char* format_string = __get_float_format<float_type>(float_fmt_type);
|
|
int written = std::snprintf(first, max_buffer_size, format_string, precision, value);
|
|
if (written < 0 || static_cast<size_t>(written) >= max_buffer_size) {
|
|
return {last, std::errc::value_too_large};
|
|
}
|
|
return {first + written, std::errc{}};
|
|
}
|
|
|
|
#endif
|
|
|
|
#if !defined(YYCC_CHARCONV_HAS_FROM_CHARS_INT)
|
|
|
|
/// @private
|
|
enum class __strtoi_cluster { tol, toll, toul, toull };
|
|
|
|
/// @private
|
|
template<typename T>
|
|
constexpr __strtoi_cluster __classify_strtoi_cluster() {
|
|
if constexpr (std::is_signed_v<T>) {
|
|
if constexpr (sizeof(T) <= sizeof(long)) {
|
|
return __strtoi_cluster::tol;
|
|
} else if constexpr (sizeof(T) <= sizeof(long long)) {
|
|
return __strtoi_cluster::toll;
|
|
} else {
|
|
static_assert(false, "Unsupported signed integral type");
|
|
}
|
|
} else {
|
|
if constexpr (sizeof(T) <= sizeof(unsigned long)) {
|
|
return __strtoi_cluster::toul;
|
|
} else if constexpr (sizeof(T) <= sizeof(unsigned long long)) {
|
|
return __strtoi_cluster::toull;
|
|
} else {
|
|
static_assert(false, "Unsupported unsigned integral type");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// @private
|
|
template<__strtoi_cluster TFc>
|
|
auto __execute_strtoi(const char* str, char** str_end, int base) {
|
|
if constexpr (TFc == __strtoi_cluster::tol) {
|
|
return std::strtol(str, str_end, base);
|
|
} else if constexpr (TFc == __strtoi_cluster::toll) {
|
|
return std::strtoll(str, str_end, base);
|
|
} else if constexpr (TFc == __strtoi_cluster::toul) {
|
|
return std::strtoul(str, str_end, base);
|
|
} else if constexpr (TFc == __strtoi_cluster::toull) {
|
|
return std::strtoull(str, str_end, base);
|
|
}
|
|
}
|
|
|
|
// Integer from_chars
|
|
template<typename T>
|
|
requires std::integral<T> && (!std::same_as<T, bool>)
|
|
from_chars_result from_chars(const char* first, const char* last, T& value, int base = 10) {
|
|
if (first >= last) {
|
|
return {first, std::errc::invalid_argument};
|
|
}
|
|
|
|
// strtoi function cluster strongly order that given string must be NULL-terminated.
|
|
// So we must do a heavy copy in there because first-last pair is not NULL-terminated guaranteed.
|
|
std::string buffer(first, static_cast<size_t>(last - first));
|
|
|
|
constexpr auto strtoi_cluster = __classify_strtoi_cluster<T>();
|
|
|
|
errno = 0;
|
|
char* end_ptr = const_cast<char*>(first);
|
|
auto result = __execute_strtoi<strtoi_cluster>(buffer.data(), &end_ptr, base);
|
|
if (errno == ERANGE) {
|
|
return {end_ptr, std::errc::result_out_of_range};
|
|
}
|
|
|
|
using strtoi_cluster_rvtype = decltype(result);
|
|
// Check if result fits in T
|
|
if (result < static_cast<strtoi_cluster_rvtype>(std::numeric_limits<T>::min())
|
|
|| result > static_cast<strtoi_cluster_rvtype>(std::numeric_limits<T>::max())) {
|
|
return {end_ptr, std::errc::result_out_of_range};
|
|
}
|
|
|
|
if (end_ptr == buffer.data()) {
|
|
return {first, std::errc::invalid_argument};
|
|
}
|
|
|
|
// Ensure we don't go past 'last'
|
|
if ((end_ptr - buffer.data()) > (last - first)) {
|
|
return {const_cast<char*>(last), std::errc::invalid_argument};
|
|
}
|
|
|
|
value = static_cast<T>(result);
|
|
return {first + (end_ptr - buffer.data()), std::errc{}};
|
|
}
|
|
|
|
#endif
|
|
|
|
#if !defined(YYCC_CHARCONV_HAS_FROM_CHARS_FLOAT)
|
|
|
|
/// @private
|
|
enum class __strtof_cluster { tof, tod };
|
|
|
|
/// @private
|
|
template<typename T>
|
|
constexpr __strtof_cluster __classify_strtof_cluster() {
|
|
if constexpr (std::is_same_v<T, float>) {
|
|
return __strtof_cluster::tof;
|
|
} else if constexpr (std::is_same_v<T, double>) {
|
|
return __strtof_cluster::tod;
|
|
} else {
|
|
static_assert(false, "Unsupported floating point type");
|
|
}
|
|
}
|
|
|
|
/// @private
|
|
template<__strtof_cluster TFc>
|
|
auto __execute_strtof(const char* str, char** str_end) {
|
|
if constexpr (TFc == __strtof_cluster::tof) {
|
|
return std::strtof(str, str_end);
|
|
} else if constexpr (TFc == __strtof_cluster::tod) {
|
|
return std::strtod(str, str_end);
|
|
}
|
|
}
|
|
|
|
// Float from_chars
|
|
template<typename T>
|
|
requires std::floating_point<T>
|
|
from_chars_result from_chars(const char* first, const char* last, T& value, chars_format fmt = chars_format::general) {
|
|
// We ignore "fmt" by design.
|
|
if (first >= last) {
|
|
return {first, std::errc::invalid_argument};
|
|
}
|
|
|
|
// strtof function cluster strongly order that given string must be NULL-terminated.
|
|
// So we must do a heavy copy in there because first-last pair is not NULL-terminated guaranteed.
|
|
std::string buffer(first, static_cast<size_t>(last - first));
|
|
|
|
constexpr auto strtof_cluster = __classify_strtof_cluster<T>();
|
|
|
|
errno = 0;
|
|
char* end_ptr = const_cast<char*>(first);
|
|
auto result = __execute_strtof<strtof_cluster>(buffer.data(), &end_ptr);
|
|
if (errno == ERANGE) {
|
|
return {end_ptr, std::errc::result_out_of_range};
|
|
}
|
|
|
|
if (end_ptr == buffer.data()) {
|
|
return {first, std::errc::invalid_argument};
|
|
}
|
|
|
|
// Ensure we don't go past 'last'
|
|
if ((end_ptr - buffer.data()) > (last - first)) {
|
|
return {const_cast<char*>(last), std::errc::invalid_argument};
|
|
}
|
|
|
|
value = result;
|
|
return {first + (end_ptr - buffer.data()), std::errc{}};
|
|
}
|
|
|
|
#endif
|
|
|
|
} // namespace std
|
|
|
|
//#endif
|