summaryrefslogtreecommitdiff
path: root/Source/Core/Common/StringUtil.h
diff options
context:
space:
mode:
authorJordan Woyak <jordan.woyak@gmail.com>2019-12-30 17:28:35 -0600
committerJordan Woyak <jordan.woyak@gmail.com>2020-01-04 07:19:15 -0600
commit0e8d4cb6ac679fd52a320aae9d1a478e47728116 (patch)
tree8cc5d2713252caa6835005b104830b7c4a80fb1d /Source/Core/Common/StringUtil.h
parent7a6a4510f6eed48652200f8c83e79723ae3581f2 (diff)
StringUtil: Make TryParse of floats handle comma and dot decimal separators.
Diffstat (limited to 'Source/Core/Common/StringUtil.h')
-rw-r--r--Source/Core/Common/StringUtil.h63
1 files changed, 52 insertions, 11 deletions
diff --git a/Source/Core/Common/StringUtil.h b/Source/Core/Common/StringUtil.h
index b651910767..c47fd1f620 100644
--- a/Source/Core/Common/StringUtil.h
+++ b/Source/Core/Common/StringUtil.h
@@ -6,7 +6,9 @@
#include <cstdarg>
#include <cstddef>
+#include <cstdlib>
#include <iomanip>
+#include <locale>
#include <sstream>
#include <string>
#include <type_traits>
@@ -47,20 +49,60 @@ std::string ArrayToString(const u8* data, u32 size, int line_len = 20, bool spac
std::string_view StripSpaces(std::string_view s);
std::string_view StripQuotes(std::string_view s);
+std::string ReplaceAll(std::string result, std::string_view src, std::string_view dest);
+
bool TryParse(const std::string& str, bool* output);
-bool TryParse(const std::string& str, u16* output);
-bool TryParse(const std::string& str, u32* output);
-bool TryParse(const std::string& str, u64* output);
-template <typename N>
-static bool TryParse(const std::string& str, N* const output)
+template <typename T, std::enable_if_t<std::is_integral_v<T> || std::is_enum_v<T>>* = nullptr>
+bool TryParse(const std::string& str, T* output)
{
- std::istringstream iss(str);
- // is this right? not doing this breaks reading floats on locales that use different decimal
- // separators
- iss.imbue(std::locale("C"));
+ char* end_ptr = nullptr;
+
+ // Set errno to a clean slate.
+ errno = 0;
+
+ // Read a u64 for unsigned types and s64 otherwise.
+ using ReadType = std::conditional_t<std::is_unsigned_v<T>, u64, s64>;
+ ReadType value;
- N tmp;
+ if constexpr (std::is_unsigned_v<T>)
+ value = std::strtoull(str.c_str(), &end_ptr, 0);
+ else
+ value = std::strtoll(str.c_str(), &end_ptr, 0);
+
+ // Fail if the end of the string wasn't reached.
+ if (end_ptr == nullptr || *end_ptr != '\0')
+ return false;
+
+ // Fail if the value was out of 64-bit range.
+ if (errno == ERANGE)
+ return false;
+
+ using LimitsType = typename std::conditional_t<std::is_enum_v<T>, std::underlying_type<T>,
+ std::common_type<T>>::type;
+ // Fail if outside numeric limits.
+ if (value < std::numeric_limits<LimitsType>::min() ||
+ value > std::numeric_limits<LimitsType>::max())
+ {
+ return false;
+ }
+
+ *output = static_cast<T>(value);
+ return true;
+}
+
+template <typename T, std::enable_if_t<std::is_floating_point_v<T>>* = nullptr>
+bool TryParse(std::string str, T* const output)
+{
+ // Replace commas with dots.
+ std::istringstream iss(ReplaceAll(std::move(str), ",", "."));
+
+ // Use "classic" locale to force a "dot" decimal separator.
+ iss.imbue(std::locale::classic());
+
+ T tmp;
+
+ // Succeed if a value was read and the entire string was used.
if (iss >> tmp && iss.eof())
{
*output = tmp;
@@ -118,7 +160,6 @@ bool SplitPath(std::string_view full_path, std::string* path, std::string* filen
void BuildCompleteFilename(std::string& complete_filename, std::string_view path,
std::string_view filename);
-std::string ReplaceAll(std::string result, std::string_view src, std::string_view dest);
bool StringBeginsWith(std::string_view str, std::string_view begin);
bool StringEndsWith(std::string_view str, std::string_view end);