Skip to content

Commit

Permalink
Common: Add StringUtil::ToChars() wrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
stenzek authored and lightningterror committed Sep 25, 2022
1 parent b359043 commit af646e4
Show file tree
Hide file tree
Showing 10 changed files with 157 additions and 24 deletions.
8 changes: 8 additions & 0 deletions cmake/SearchForStuff.cmake
Expand Up @@ -252,6 +252,14 @@ else()
set(BIN2CPPDEP ${CMAKE_SOURCE_DIR}/linux_various/hex2h.pl)
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/simpleini EXCLUDE_FROM_ALL)
add_subdirectory(3rdparty/imgui EXCLUDE_FROM_ALL)
Expand Down
1 change: 1 addition & 0 deletions common/CMakeLists.txt
Expand Up @@ -288,6 +288,7 @@ target_link_libraries(common PRIVATE

target_link_libraries(common PUBLIC
fmt::fmt
fast_float
)

fixup_file_properties(common)
Expand Down
83 changes: 60 additions & 23 deletions common/StringUtil.h
Expand Up @@ -15,6 +15,7 @@

#pragma once
#include "Pcsx2Types.h"
#include <charconv>
#include <cstdarg>
#include <cstddef>
#include <cstring>
Expand All @@ -24,13 +25,16 @@
#include <string_view>
#include <vector>

#if defined(__has_include) && __has_include(<charconv>)
#include <charconv>
#ifndef _MSC_VER
#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 <locale>
#include <sstream>
#ifdef __APPLE__
#include <Availability.h>
#endif
#else
#include <sstream>
#endif

namespace StringUtil
Expand Down Expand Up @@ -72,23 +76,15 @@ namespace StringUtil
#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>
inline std::optional<T> FromChars(const std::string_view& str, int base = 10)
{
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);
if (result.ec != std::errc())
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;
}
Expand All @@ -98,22 +94,57 @@ namespace StringUtil
{
T value;

#if defined(__has_include) && __has_include(<charconv>) && defined(_MSC_VER)
const std::from_chars_result result = std::from_chars(str.data(), str.data() + str.length(), 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;
}

/// 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
/// 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;
std::ostringstream ss;
ss.imbue(std::locale::classic());
ss << std::setbase(base) << value;
return ss.str();
#endif
}

return value;
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
template <>
inline std::optional<bool> FromChars(const std::string_view& str, int base)
Expand All @@ -135,6 +166,12 @@ namespace StringUtil
return std::nullopt;
}

template <>
inline std::string ToChars(bool value, int base)
{
return std::string(value ? "true" : "false");
}

/// Encode/decode hexadecimal byte buffers
std::optional<std::vector<u8>> DecodeHex(const std::string_view& str);
std::string EncodeHex(const u8* data, int length);
Expand Down
1 change: 1 addition & 0 deletions common/common.vcxproj
Expand Up @@ -33,6 +33,7 @@
<ItemDefinitionGroup>
<ClCompile>
<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\jpgd</AdditionalIncludeDirectories>
<ExceptionHandling>Async</ExceptionHandling>
Expand Down
1 change: 1 addition & 0 deletions pcsx2-qt/pcsx2-qt.vcxproj
Expand Up @@ -43,6 +43,7 @@
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\glad\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\simpleini\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>
<!-- Needed for moc pch -->
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(ProjectDir)\Settings;$(ProjectDir)\GameList;$(ProjectDir)\Tools\InputRecording</AdditionalIncludeDirectories>
Expand Down
1 change: 1 addition & 0 deletions pcsx2/pcsx2.vcxproj
Expand Up @@ -46,6 +46,7 @@
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\libzip;$(SolutionDir)3rdparty\libzip\libzip\lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\d3d12memalloc\include;%(AdditionalIncludeDirectories)</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>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>PrecompiledHeader.h</PrecompiledHeaderFile>
Expand Down
1 change: 1 addition & 0 deletions pcsx2/pcsx2core.vcxproj
Expand Up @@ -49,6 +49,7 @@
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\d3d12memalloc\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\zstd\zstd\lib</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>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>PrecompiledHeader.h</PrecompiledHeaderFile>
Expand Down
2 changes: 1 addition & 1 deletion tests/ctest/common/CMakeLists.txt
@@ -1 +1 @@
add_pcsx2_test(common_test path_tests.cpp)
add_pcsx2_test(common_test path_tests.cpp string_util_tests.cpp)
82 changes: 82 additions & 0 deletions tests/ctest/common/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
1 change: 1 addition & 0 deletions updater/updater.vcxproj
Expand Up @@ -33,6 +33,7 @@
<ItemDefinitionGroup>
<ClCompile>
<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>
<ExceptionHandling>Async</ExceptionHandling>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
Expand Down

0 comments on commit af646e4

Please sign in to comment.