mirror of https://github.com/PCSX2/pcsx2.git
Common: Add StringUtil::ToChars() wrapper
This commit is contained in:
parent
b3590430c9
commit
af646e4496
|
@ -252,6 +252,14 @@ else()
|
||||||
set(BIN2CPPDEP ${CMAKE_SOURCE_DIR}/linux_various/hex2h.pl)
|
set(BIN2CPPDEP ${CMAKE_SOURCE_DIR}/linux_various/hex2h.pl)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# rapidyaml includes fast_float as a submodule, saves us pulling it in directly.
|
||||||
|
# Normally, we'd just pull in the cmake project, and link to it, but... it seems to enable
|
||||||
|
# permissive mode, which breaks other parts of PCSX2. So, we'll just create a target here
|
||||||
|
# for now.
|
||||||
|
#add_subdirectory(3rdparty/rapidyaml/rapidyaml/ext/c4core/src/c4/ext/fast_float EXCLUDE_FROM_ALL)
|
||||||
|
add_library(fast_float INTERFACE)
|
||||||
|
target_include_directories(fast_float INTERFACE 3rdparty/rapidyaml/rapidyaml/ext/c4core/src/c4/ext/fast_float/include)
|
||||||
|
|
||||||
add_subdirectory(3rdparty/jpgd EXCLUDE_FROM_ALL)
|
add_subdirectory(3rdparty/jpgd EXCLUDE_FROM_ALL)
|
||||||
add_subdirectory(3rdparty/simpleini EXCLUDE_FROM_ALL)
|
add_subdirectory(3rdparty/simpleini EXCLUDE_FROM_ALL)
|
||||||
add_subdirectory(3rdparty/imgui EXCLUDE_FROM_ALL)
|
add_subdirectory(3rdparty/imgui EXCLUDE_FROM_ALL)
|
||||||
|
|
|
@ -288,6 +288,7 @@ target_link_libraries(common PRIVATE
|
||||||
|
|
||||||
target_link_libraries(common PUBLIC
|
target_link_libraries(common PUBLIC
|
||||||
fmt::fmt
|
fmt::fmt
|
||||||
|
fast_float
|
||||||
)
|
)
|
||||||
|
|
||||||
fixup_file_properties(common)
|
fixup_file_properties(common)
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "Pcsx2Types.h"
|
#include "Pcsx2Types.h"
|
||||||
|
#include <charconv>
|
||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
@ -24,13 +25,16 @@
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#if defined(__has_include) && __has_include(<charconv>)
|
#include "fast_float/fast_float.h"
|
||||||
#include <charconv>
|
|
||||||
#ifndef _MSC_VER
|
// 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 <locale>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#include <Availability.h>
|
||||||
#endif
|
#endif
|
||||||
#else
|
|
||||||
#include <sstream>
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace StringUtil
|
namespace StringUtil
|
||||||
|
@ -72,23 +76,15 @@ namespace StringUtil
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrapper arond std::from_chars
|
/// Wrapper around std::from_chars
|
||||||
template <typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true>
|
template <typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true>
|
||||||
inline std::optional<T> FromChars(const std::string_view& str, int base = 10)
|
inline std::optional<T> FromChars(const std::string_view& str, int base = 10)
|
||||||
{
|
{
|
||||||
T value;
|
T value;
|
||||||
|
|
||||||
#if defined(__has_include) && __has_include(<charconv>)
|
|
||||||
const std::from_chars_result result = std::from_chars(str.data(), str.data() + str.length(), value, base);
|
const std::from_chars_result result = std::from_chars(str.data(), str.data() + str.length(), value, base);
|
||||||
if (result.ec != std::errc())
|
if (result.ec != std::errc())
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
#else
|
|
||||||
std::string temp(str);
|
|
||||||
std::istringstream ss(temp);
|
|
||||||
ss >> std::setbase(base) >> value;
|
|
||||||
if (ss.fail())
|
|
||||||
return std::nullopt;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
@ -98,22 +94,57 @@ namespace StringUtil
|
||||||
{
|
{
|
||||||
T value;
|
T value;
|
||||||
|
|
||||||
#if defined(__has_include) && __has_include(<charconv>) && defined(_MSC_VER)
|
const fast_float::from_chars_result result = fast_float::from_chars(str.data(), str.data() + str.length(), value);
|
||||||
const std::from_chars_result result = std::from_chars(str.data(), str.data() + str.length(), value);
|
|
||||||
if (result.ec != std::errc())
|
if (result.ec != std::errc())
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
#else
|
|
||||||
/// libstdc++ does not support from_chars with floats yet
|
|
||||||
std::string temp(str);
|
|
||||||
std::istringstream ss(temp);
|
|
||||||
ss >> value;
|
|
||||||
if (ss.fail())
|
|
||||||
return std::nullopt;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wrapper around std::to_chars
|
||||||
|
template <typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true>
|
||||||
|
inline std::string ToChars(T value, int base = 10)
|
||||||
|
{
|
||||||
|
// to_chars() requires macOS 10.15+.
|
||||||
|
#if !defined(__APPLE__) || MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_15
|
||||||
|
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;
|
||||||
|
#else
|
||||||
|
std::ostringstream ss;
|
||||||
|
ss.imbue(std::locale::classic());
|
||||||
|
ss << std::setbase(base) << value;
|
||||||
|
return ss.str();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, std::enable_if_t<std::is_floating_point<T>::value, bool> = true>
|
||||||
|
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 ss.str();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Explicit override for booleans
|
/// Explicit override for booleans
|
||||||
template <>
|
template <>
|
||||||
inline std::optional<bool> FromChars(const std::string_view& str, int base)
|
inline std::optional<bool> FromChars(const std::string_view& str, int base)
|
||||||
|
@ -135,6 +166,12 @@ namespace StringUtil
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline std::string ToChars(bool value, int base)
|
||||||
|
{
|
||||||
|
return std::string(value ? "true" : "false");
|
||||||
|
}
|
||||||
|
|
||||||
/// Encode/decode hexadecimal byte buffers
|
/// Encode/decode hexadecimal byte buffers
|
||||||
std::optional<std::vector<u8>> DecodeHex(const std::string_view& str);
|
std::optional<std::vector<u8>> DecodeHex(const std::string_view& str);
|
||||||
std::string EncodeHex(const u8* data, int length);
|
std::string EncodeHex(const u8* data, int length);
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
<ItemDefinitionGroup>
|
<ItemDefinitionGroup>
|
||||||
<ClCompile>
|
<ClCompile>
|
||||||
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\d3d12memalloc\include;$(SolutionDir)3rdparty\glad\include;$(SolutionDir)3rdparty\glslang\glslang;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\d3d12memalloc\include;$(SolutionDir)3rdparty\glad\include;$(SolutionDir)3rdparty\glslang\glslang;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
|
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\rapidyaml\rapidyaml\ext\c4core\src\c4\ext\fast_float\include</AdditionalIncludeDirectories>
|
||||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\libpng</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\libpng</AdditionalIncludeDirectories>
|
||||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\jpgd</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\jpgd</AdditionalIncludeDirectories>
|
||||||
<ExceptionHandling>Async</ExceptionHandling>
|
<ExceptionHandling>Async</ExceptionHandling>
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\glad\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\glad\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\simpleini\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\simpleini\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\lzma\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\lzma\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
|
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\rapidyaml\rapidyaml\ext\c4core\src\c4\ext\fast_float\include;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
|
||||||
<AdditionalIncludeDirectories>$(ProjectDir);$(SolutionDir)pcsx2;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>$(ProjectDir);$(SolutionDir)pcsx2;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
<!-- Needed for moc pch -->
|
<!-- Needed for moc pch -->
|
||||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(ProjectDir)\Settings;$(ProjectDir)\GameList;$(ProjectDir)\Tools\InputRecording</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(ProjectDir)\Settings;$(ProjectDir)\GameList;$(ProjectDir)\Tools\InputRecording</AdditionalIncludeDirectories>
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\libzip;$(SolutionDir)3rdparty\libzip\libzip\lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\libzip;$(SolutionDir)3rdparty\libzip\libzip\lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\d3d12memalloc\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\d3d12memalloc\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\zstd\zstd\lib</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\zstd\zstd\lib</AdditionalIncludeDirectories>
|
||||||
|
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\rapidyaml\rapidyaml\ext\c4core\src\c4\ext\fast_float\include</AdditionalIncludeDirectories>
|
||||||
<ExceptionHandling>Async</ExceptionHandling>
|
<ExceptionHandling>Async</ExceptionHandling>
|
||||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||||
<PrecompiledHeaderFile>PrecompiledHeader.h</PrecompiledHeaderFile>
|
<PrecompiledHeaderFile>PrecompiledHeader.h</PrecompiledHeaderFile>
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\d3d12memalloc\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\d3d12memalloc\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\zstd\zstd\lib</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\zstd\zstd\lib</AdditionalIncludeDirectories>
|
||||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\cpuinfo\include</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\cpuinfo\include</AdditionalIncludeDirectories>
|
||||||
|
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\rapidyaml\rapidyaml\ext\c4core\src\c4\ext\fast_float\include</AdditionalIncludeDirectories>
|
||||||
<ExceptionHandling>Async</ExceptionHandling>
|
<ExceptionHandling>Async</ExceptionHandling>
|
||||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||||
<PrecompiledHeaderFile>PrecompiledHeader.h</PrecompiledHeaderFile>
|
<PrecompiledHeaderFile>PrecompiledHeader.h</PrecompiledHeaderFile>
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
add_pcsx2_test(common_test path_tests.cpp)
|
add_pcsx2_test(common_test path_tests.cpp string_util_tests.cpp)
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
/* PCSX2 - PS2 Emulator for PCs
|
||||||
|
* Copyright (C) 2002-2022 PCSX2 Dev Team
|
||||||
|
*
|
||||||
|
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||||
|
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||||
|
* ation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE. See the GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with PCSX2.
|
||||||
|
* If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "common/Pcsx2Defs.h"
|
||||||
|
#include "common/StringUtil.h"
|
||||||
|
#include <locale>
|
||||||
|
#include <clocale>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
TEST(StringUtil, ToChars)
|
||||||
|
{
|
||||||
|
ASSERT_EQ(StringUtil::ToChars(false), "false");
|
||||||
|
ASSERT_EQ(StringUtil::ToChars(true), "true");
|
||||||
|
ASSERT_EQ(StringUtil::ToChars(0), "0");
|
||||||
|
ASSERT_EQ(StringUtil::ToChars(-1337), "-1337");
|
||||||
|
ASSERT_EQ(StringUtil::ToChars(1337), "1337");
|
||||||
|
ASSERT_EQ(StringUtil::ToChars(1337u), "1337");
|
||||||
|
ASSERT_EQ(StringUtil::ToChars(13.37f), "13.37");
|
||||||
|
ASSERT_EQ(StringUtil::ToChars(255, 16), "ff");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(StringUtil, FromChars)
|
||||||
|
{
|
||||||
|
ASSERT_EQ(StringUtil::FromChars<bool>("false").value_or(true), false);
|
||||||
|
ASSERT_EQ(StringUtil::FromChars<bool>("true").value_or(false), true);
|
||||||
|
ASSERT_EQ(StringUtil::FromChars<int>("0").value_or(-1), 0);
|
||||||
|
ASSERT_EQ(StringUtil::FromChars<int>("-1337").value_or(0), -1337);
|
||||||
|
ASSERT_EQ(StringUtil::FromChars<int>("1337").value_or(0), 1337);
|
||||||
|
ASSERT_EQ(StringUtil::FromChars<u32>("1337").value_or(0), 1337);
|
||||||
|
ASSERT_TRUE(std::abs(StringUtil::FromChars<float>("13.37").value_or(0.0f) - 13.37) < 0.01f);
|
||||||
|
ASSERT_EQ(StringUtil::FromChars<int>("ff", 16).value_or(0), 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
// NOTE: These tests are disabled, because they require the da_DK locale to actually be present.
|
||||||
|
// Which probably isn't going to be the case on the CI.
|
||||||
|
|
||||||
|
TEST(StringUtil, ToCharsIsLocaleIndependent)
|
||||||
|
{
|
||||||
|
const auto old_locale = std::locale();
|
||||||
|
std::locale::global(std::locale::classic());
|
||||||
|
|
||||||
|
std::string classic_result(StringUtil::ToChars(13.37f));
|
||||||
|
|
||||||
|
std::locale::global(std::locale("da_DK"));
|
||||||
|
|
||||||
|
std::string dk_result(StringUtil::ToChars(13.37f));
|
||||||
|
|
||||||
|
std::locale::global(old_locale);
|
||||||
|
|
||||||
|
ASSERT_EQ(classic_result, dk_result);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(StringUtil, FromCharsIsLocaleIndependent)
|
||||||
|
{
|
||||||
|
const auto old_locale = std::locale();
|
||||||
|
std::locale::global(std::locale::classic());
|
||||||
|
|
||||||
|
const float classic_result = StringUtil::FromChars<float>("13.37").value_or(0.0f);
|
||||||
|
|
||||||
|
std::locale::global(std::locale("da_DK"));
|
||||||
|
|
||||||
|
const float dk_result = StringUtil::FromChars<float>("13.37").value_or(0.0f);
|
||||||
|
|
||||||
|
std::locale::global(old_locale);
|
||||||
|
|
||||||
|
ASSERT_EQ(classic_result, dk_result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -33,6 +33,7 @@
|
||||||
<ItemDefinitionGroup>
|
<ItemDefinitionGroup>
|
||||||
<ClCompile>
|
<ClCompile>
|
||||||
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\lzma\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\lzma\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
|
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\rapidyaml\rapidyaml\ext\c4core\src\c4\ext\fast_float\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
<AdditionalIncludeDirectories>$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
<ExceptionHandling>Async</ExceptionHandling>
|
<ExceptionHandling>Async</ExceptionHandling>
|
||||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||||
|
|
Loading…
Reference in New Issue