diff --git a/src/common/file_system.cpp b/src/common/file_system.cpp index b16db60df..48b71312c 100644 --- a/src/common/file_system.cpp +++ b/src/common/file_system.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index 2dfe33a5b..e6ed65666 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -6,15 +6,26 @@ #include "bitutils.h" #include +#include #include +#include #include +// Older versions of libstdc++ are missing support for from_chars() with floats, and was only recently +// merged in libc++. So, just fall back to stringstream (yuck!) on everywhere except MSVC. +#if !defined(_MSC_VER) +#include +#include +#endif + #ifndef __APPLE__ #include // alloca #else #include #endif +#include "fast_float/fast_float.h" + #ifdef _WIN32 #include "windows_headers.h" #endif @@ -207,6 +218,202 @@ bool StringUtil::ContainsNoCase(std::string_view s1, std::string_view s2) [](char lhs, char rhs) { return (ToLower(lhs) == ToLower(rhs)); }) != s1.end()); } +/// Wrapper around std::from_chars +template + requires std::is_integral_v +std::optional StringUtil::FromChars(const std::string_view str, const int base /*= 10*/) +{ + T value; + + const std::from_chars_result result = std::from_chars(str.data(), str.data() + str.length(), value, base); + if (result.ec != std::errc()) + return std::nullopt; + + return value; +} +template + requires std::is_integral_v +std::optional StringUtil::FromChars(const std::string_view str, const int base, std::string_view* const endptr) +{ + T value; + + const char* ptr = str.data(); + const char* end = ptr + str.length(); + const std::from_chars_result result = std::from_chars(ptr, end, value, base); + if (result.ec != std::errc()) + return std::nullopt; + + if (endptr) + { + const size_t remaining_len = end - result.ptr; + *endptr = (remaining_len > 0) ? std::string_view(result.ptr, remaining_len) : std::string_view(); + } + + return value; +} + +template + requires std::is_integral_v +std::optional StringUtil::FromCharsWithOptionalBase(const std::string_view str, + std::string_view* const endptr /*= nullptr*/) +{ + int base; + std::string_view data; + if (str.starts_with("0x")) + { + base = 16; + data = str.substr(2); + } + else if (str.starts_with("0b")) + { + base = 2; + data = str.substr(2); + } + else if (str.starts_with("0") && str.length() > 1) + { + base = 8; + data = str.substr(1); + } + else + { + base = 10; + data = str; + } + + if (endptr) + return FromChars(data, base, endptr); + else + return FromChars(data, base); +} + +template + requires std::is_floating_point_v +std::optional StringUtil::FromChars(const std::string_view str) +{ + T value; + + const fast_float::from_chars_result result = fast_float::from_chars(str.data(), str.data() + str.length(), value); + if (result.ec != std::errc()) + return std::nullopt; + + return value; +} +template + requires std::is_floating_point_v +std::optional StringUtil::FromChars(const std::string_view str, std::string_view* const endptr) +{ + T value; + + const char* ptr = str.data(); + const char* end = ptr + str.length(); + const fast_float::from_chars_result result = fast_float::from_chars(ptr, end, value); + if (result.ec != std::errc()) + return std::nullopt; + + if (endptr) + { + const size_t remaining_len = end - result.ptr; + *endptr = (remaining_len > 0) ? std::string_view(result.ptr, remaining_len) : std::string_view(); + } + + return value; +} + +/// Wrapper around std::to_chars +template + requires std::is_integral_v +std::string StringUtil::ToChars(const T value, const int base /*= 10*/) +{ + constexpr size_t MAX_SIZE = 32; + char buf[MAX_SIZE]; + std::string ret; + + const std::to_chars_result result = std::to_chars(buf, buf + MAX_SIZE, value, base); + if (result.ec == std::errc()) + ret.append(buf, result.ptr - buf); + + return ret; +} + +template + requires std::is_floating_point_v +std::string StringUtil::ToChars(const T value) +{ + // No to_chars() in older versions of libstdc++/libc++. +#ifdef _MSC_VER + constexpr size_t MAX_SIZE = 64; + char buf[MAX_SIZE]; + std::string ret; + const std::to_chars_result result = std::to_chars(buf, buf + MAX_SIZE, value); + if (result.ec == std::errc()) + ret.append(buf, result.ptr - buf); + return ret; +#else + std::ostringstream ss; + ss.imbue(std::locale::classic()); + ss << value; + return std::move(ss).str(); +#endif +} + +// Instantiating with known types +#define TO_FROM_CHARS_INTEGRAL_TYPES(X) \ + X(s8) \ + X(u8) \ + X(s16) \ + X(u16) \ + X(s32) \ + X(unsigned int) \ + X(s64) \ + X(u64) + +#define X(T) \ + template std::optional StringUtil::FromChars(const std::string_view, const int); \ + template std::optional StringUtil::FromChars(const std::string_view, const int, std::string_view* const); \ + template std::optional StringUtil::FromCharsWithOptionalBase(const std::string_view, std::string_view* const); \ + template std::string StringUtil::ToChars(const T, const int); +TO_FROM_CHARS_INTEGRAL_TYPES(X); +#undef X + +#define TO_FROM_CHARS_FLOATING_POINT_TYPES(X) \ + X(float) \ + X(double) + +#define X(T) \ + template std::optional StringUtil::FromChars(const std::string_view); \ + template std::optional StringUtil::FromChars(const std::string_view, std::string_view* const); \ + template std::string StringUtil::ToChars(T); + +TO_FROM_CHARS_FLOATING_POINT_TYPES(X); +#undef X + +/// Explicit override for booleans +template<> +std::optional StringUtil::FromChars(const std::string_view str, int base) +{ + if (Strncasecmp("true", str.data(), str.length()) == 0 || Strncasecmp("yes", str.data(), str.length()) == 0 || + Strncasecmp("on", str.data(), str.length()) == 0 || Strncasecmp("1", str.data(), str.length()) == 0 || + Strncasecmp("enabled", str.data(), str.length()) == 0) + { + return true; + } + + if (Strncasecmp("false", str.data(), str.length()) == 0 || Strncasecmp("no", str.data(), str.length()) == 0 || + Strncasecmp("off", str.data(), str.length()) == 0 || Strncasecmp("0", str.data(), str.length()) == 0 || + Strncasecmp("disabled", str.data(), str.length()) == 0) + { + return false; + } + + return std::nullopt; +} + +template<> +inline std::string StringUtil::ToChars(bool value, int base) +{ + return std::string(value ? "true" : "false"); +} + std::string StringUtil::StripControlCharacters(std::string_view str) { std::string out; diff --git a/src/common/string_util.h b/src/common/string_util.h index 8723cf776..00b1cf0cb 100644 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -7,26 +7,15 @@ #include #include -#include #include #include #include -#include #include #include #include #include #include -#include "fast_float/fast_float.h" - -// Older versions of libstdc++ are missing support for from_chars() with floats, and was only recently -// merged in libc++. So, just fall back to stringstream (yuck!) on everywhere except MSVC. -#if !defined(_MSC_VER) -#include -#include -#endif - namespace StringUtil { /// Returns the given character in uppercase. @@ -75,155 +64,34 @@ inline constexpr int ConstexprCompare(const char* s1, const char* s2) } /// Wrapper around std::from_chars -template requires std::is_integral_v -inline std::optional FromChars(const std::string_view str, int base = 10) -{ - T value; - - const std::from_chars_result result = std::from_chars(str.data(), str.data() + str.length(), value, base); - if (result.ec != std::errc()) - return std::nullopt; - - return value; -} -template requires std::is_integral_v -inline std::optional FromChars(const std::string_view str, int base, std::string_view* endptr) -{ - T value; - - const char* ptr = str.data(); - const char* end = ptr + str.length(); - const std::from_chars_result result = std::from_chars(ptr, end, value, base); - if (result.ec != std::errc()) - return std::nullopt; - - if (endptr) - { - const size_t remaining_len = end - result.ptr; - *endptr = (remaining_len > 0) ? std::string_view(result.ptr, remaining_len) : std::string_view(); - } - - return value; -} - -template requires std::is_integral_v -inline std::optional FromCharsWithOptionalBase(std::string_view str, std::string_view* endptr = nullptr) -{ - int base = 10; - if (str.starts_with("0x")) - { - base = 16; - str = str.substr(2); - } - else if (str.starts_with("0b")) - { - base = 2; - str = str.substr(2); - } - else if (str.starts_with("0") && str.length() > 1) - { - base = 8; - str = str.substr(1); - } - - if (endptr) - return FromChars(str, base, endptr); - else - return FromChars(str, base); -} - -template requires std::is_floating_point_v -inline std::optional FromChars(const std::string_view str) -{ - T value; - - const fast_float::from_chars_result result = fast_float::from_chars(str.data(), str.data() + str.length(), value); - if (result.ec != std::errc()) - return std::nullopt; +template + requires std::is_integral_v +std::optional FromChars(const std::string_view str, const int base = 10); - return value; -} -template requires std::is_floating_point_v -inline std::optional FromChars(const std::string_view str, std::string_view* endptr) -{ - T value; +template + requires std::is_integral_v +std::optional FromChars(const std::string_view str, const int base, std::string_view* const endptr); - const char* ptr = str.data(); - const char* end = ptr + str.length(); - const fast_float::from_chars_result result = fast_float::from_chars(ptr, end, value); - if (result.ec != std::errc()) - return std::nullopt; +template + requires std::is_integral_v +std::optional FromCharsWithOptionalBase(const std::string_view str, std::string_view* const endptr = nullptr); - if (endptr) - { - const size_t remaining_len = end - result.ptr; - *endptr = (remaining_len > 0) ? std::string_view(result.ptr, remaining_len) : std::string_view(); - } +template + requires std::is_floating_point_v +std::optional FromChars(const std::string_view str); - return value; -} +template + requires std::is_floating_point_v +std::optional FromChars(const std::string_view str, std::string_view* const endptr); /// Wrapper around std::to_chars -template requires std::is_integral_v -inline std::string ToChars(T value, int base = 10) -{ - constexpr size_t MAX_SIZE = 32; - char buf[MAX_SIZE]; - std::string ret; - - const std::to_chars_result result = std::to_chars(buf, buf + MAX_SIZE, value, base); - if (result.ec == std::errc()) - ret.append(buf, result.ptr - buf); - - return ret; -} - -template requires std::is_floating_point_v -inline std::string ToChars(T value) -{ - // No to_chars() in older versions of libstdc++/libc++. -#ifdef _MSC_VER - constexpr size_t MAX_SIZE = 64; - char buf[MAX_SIZE]; - std::string ret; - const std::to_chars_result result = std::to_chars(buf, buf + MAX_SIZE, value); - if (result.ec == std::errc()) - ret.append(buf, result.ptr - buf); - return ret; -#else - std::ostringstream ss; - ss.imbue(std::locale::classic()); - ss << value; - return std::move(ss).str(); -#endif -} - -/// Explicit override for booleans -template<> -inline std::optional FromChars(const std::string_view str, int base) -{ - if (Strncasecmp("true", str.data(), str.length()) == 0 || Strncasecmp("yes", str.data(), str.length()) == 0 || - Strncasecmp("on", str.data(), str.length()) == 0 || Strncasecmp("1", str.data(), str.length()) == 0 || - Strncasecmp("enabled", str.data(), str.length()) == 0) - { - return true; - } - - if (Strncasecmp("false", str.data(), str.length()) == 0 || Strncasecmp("no", str.data(), str.length()) == 0 || - Strncasecmp("off", str.data(), str.length()) == 0 || Strncasecmp("0", str.data(), str.length()) == 0 || - Strncasecmp("disabled", str.data(), str.length()) == 0) - { - return false; - } - - return std::nullopt; -} +template + requires std::is_integral_v +std::string ToChars(const T value, const int base = 10); -template<> -inline std::string ToChars(bool value, int base) -{ - return std::string(value ? "true" : "false"); -} +template + requires std::is_floating_point_v +std::string ToChars(const T value); /// Returns true if the given character is whitespace. ALWAYS_INLINE bool IsWhitespace(char ch)