mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-16 07:30:42 +01:00
Common/Util: Trinity::StringTo<double> support (PR #25364)
This commit is contained in:
@@ -39,7 +39,6 @@ namespace Trinity::Impl::StringConvertImpl
|
||||
*/
|
||||
};
|
||||
|
||||
// @todo relax this once proper std::from_chars support exists
|
||||
template <typename T>
|
||||
struct For<T, std::enable_if_t<std::is_integral_v<T> && !std::is_same_v<T, bool>>>
|
||||
{
|
||||
@@ -171,6 +170,91 @@ namespace Trinity::Impl::StringConvertImpl
|
||||
return (val ? "1" : "0");
|
||||
}
|
||||
};
|
||||
|
||||
#if TRINITY_COMPILER == TRINITY_COMPILER_MICROSOFT
|
||||
template <typename T>
|
||||
struct For<T, std::enable_if_t<std::is_floating_point_v<T>>>
|
||||
{
|
||||
static Optional<T> FromString(std::string_view str, std::chars_format fmt = std::chars_format())
|
||||
{
|
||||
if (str.empty())
|
||||
return std::nullopt;
|
||||
|
||||
if (fmt == std::chars_format())
|
||||
{
|
||||
if (StringEqualI(str.substr(0, 2), "0x"))
|
||||
{
|
||||
fmt = std::chars_format::hex;
|
||||
str.remove_prefix(2);
|
||||
}
|
||||
else
|
||||
fmt = std::chars_format::general;
|
||||
|
||||
if (str.empty())
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
char const* const start = str.data();
|
||||
char const* const end = (start + str.length());
|
||||
|
||||
T val;
|
||||
std::from_chars_result const res = std::from_chars(start, end, val, fmt);
|
||||
if ((res.ptr == end) && (res.ec == std::errc()))
|
||||
return val;
|
||||
else
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// this allows generic converters for all numeric types (easier templating!)
|
||||
static Optional<T> FromString(std::string_view str, int base)
|
||||
{
|
||||
if (base == 16)
|
||||
return FromString(str, std::chars_format::hex);
|
||||
else if (base == 10)
|
||||
return FromString(str, std::chars_format::general);
|
||||
else
|
||||
return FromString(str, std::chars_format());
|
||||
}
|
||||
|
||||
static std::string ToString(T val)
|
||||
{
|
||||
return std::to_string(val);
|
||||
}
|
||||
};
|
||||
#else
|
||||
// @todo replace this once libc++ supports double args to from_chars
|
||||
template <typename T>
|
||||
struct For<T, std::enable_if_t<std::is_floating_point_v<T>>>
|
||||
{
|
||||
static Optional<T> FromString(std::string_view str, int base = 0)
|
||||
{
|
||||
try {
|
||||
if (str.empty())
|
||||
return std::nullopt;
|
||||
|
||||
if ((base == 10) && StringEqualI(str.substr(0, 2), "0x"))
|
||||
return std::nullopt;
|
||||
|
||||
std::string tmp;
|
||||
if (base == 16)
|
||||
tmp.append("0x");
|
||||
tmp.append(str);
|
||||
|
||||
size_t n;
|
||||
T val = static_cast<T>(std::stold(tmp, &n));
|
||||
if (n != tmp.length())
|
||||
return std::nullopt;
|
||||
return val;
|
||||
}
|
||||
catch (...) { return std::nullopt; }
|
||||
}
|
||||
|
||||
static std::string ToString(T val)
|
||||
{
|
||||
return std::to_string(val);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
}
|
||||
|
||||
namespace Trinity
|
||||
|
||||
@@ -45,9 +45,9 @@ namespace Trinity::Impl::ChatCommands
|
||||
template <typename T, typename = void>
|
||||
struct ArgInfo { static_assert(!std::is_same_v<T,T>, "Invalid command parameter type - see ChatCommandArgs.h for possible types"); };
|
||||
|
||||
// catch-all for integral types
|
||||
// catch-all for number types
|
||||
template <typename T>
|
||||
struct ArgInfo<T, std::enable_if_t<std::is_integral_v<T>>>
|
||||
struct ArgInfo<T, std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T>>>
|
||||
{
|
||||
static Optional<std::string_view> TryConsume(T& val, std::string_view args)
|
||||
{
|
||||
@@ -59,34 +59,14 @@ struct ArgInfo<T, std::enable_if_t<std::is_integral_v<T>>>
|
||||
val = *v;
|
||||
else
|
||||
return std::nullopt;
|
||||
return tail;
|
||||
}
|
||||
};
|
||||
|
||||
// catch-all for floating point types
|
||||
template <typename T>
|
||||
struct ArgInfo<T, std::enable_if_t<std::is_floating_point_v<T>>>
|
||||
{
|
||||
static Optional<std::string_view> TryConsume(T& val, std::string_view args)
|
||||
{
|
||||
auto [token, tail] = tokenize(args);
|
||||
if (token.empty())
|
||||
return std::nullopt;
|
||||
|
||||
try
|
||||
if constexpr (std::is_floating_point_v<T>)
|
||||
{
|
||||
// @todo replace this once libc++ supports double args to from_chars for required minimum
|
||||
size_t processedChars = 0;
|
||||
val = std::stold(std::string(token), &processedChars);
|
||||
if (processedChars != token.length())
|
||||
if (!std::isfinite(val))
|
||||
return std::nullopt;
|
||||
}
|
||||
catch (...) { return std::nullopt; }
|
||||
|
||||
if (std::isfinite(val))
|
||||
return tail;
|
||||
else
|
||||
return std::nullopt;
|
||||
return tail;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ TEST_CASE("String to smaller integer types", "[StringConvert]")
|
||||
REQUIRE(Trinity::StringTo<int16>("-1", 0) == -1);
|
||||
}
|
||||
|
||||
TEST_CASE("Test.String to boolean", "[StringConvert]")
|
||||
TEST_CASE("String to boolean", "[StringConvert]")
|
||||
{
|
||||
REQUIRE(Trinity::StringTo<bool>("true") == true);
|
||||
REQUIRE(Trinity::StringTo<bool>("false") == false);
|
||||
@@ -129,3 +129,26 @@ TEST_CASE("Test.String to boolean", "[StringConvert]")
|
||||
REQUIRE(Trinity::StringTo<bool>("1", 10) == true);
|
||||
REQUIRE(Trinity::StringTo<bool>("0", 10) == false);
|
||||
}
|
||||
|
||||
TEST_CASE("String to double", "[StringConvert]")
|
||||
{
|
||||
using namespace Catch::literals;
|
||||
REQUIRE(Trinity::StringTo<double>("0.5") == 0.5);
|
||||
REQUIRE(Trinity::StringTo<double>("0.1") == 0.1_a);
|
||||
REQUIRE(Trinity::StringTo<double>("1.2.3") == std::nullopt);
|
||||
REQUIRE(Trinity::StringTo<double>("1e+5") == 100000.0);
|
||||
REQUIRE(Trinity::StringTo<double>("1e+3+5") == std::nullopt);
|
||||
REQUIRE(Trinity::StringTo<double>("a1.5") == std::nullopt);
|
||||
REQUIRE(Trinity::StringTo<double>("1.5tail") == std::nullopt);
|
||||
REQUIRE(Trinity::StringTo<double>("0x0") == 0.0);
|
||||
REQUIRE(Trinity::StringTo<double>("0x0", 16) == std::nullopt);
|
||||
REQUIRE(Trinity::StringTo<double>("0", 16) == 0.0);
|
||||
REQUIRE(Trinity::StringTo<double>("0x1.BC70A3D70A3D7p+6") == 0x1.BC70A3D70A3D7p+6);
|
||||
REQUIRE(Trinity::StringTo<double>("0x1.BC70A3D70A3D7p+6", 10) == std::nullopt);
|
||||
REQUIRE(Trinity::StringTo<double>("0x1.BC70A3D70A3D7p+6", 16) == std::nullopt);
|
||||
REQUIRE(Trinity::StringTo<double>("1.BC70A3D70A3D7p+6", 16) == 0x1.BC70A3D70A3D7p+6);
|
||||
REQUIRE(Trinity::StringTo<double>("0x1.2.3") == std::nullopt);
|
||||
REQUIRE(Trinity::StringTo<double>("0x1.AAAp+1-3") == std::nullopt);
|
||||
REQUIRE(Trinity::StringTo<double>("1.2.3", 16) == std::nullopt);
|
||||
REQUIRE(Trinity::StringTo<double>("1.AAAp+1-3", 16) == std::nullopt);
|
||||
}
|
||||
|
||||
@@ -48,13 +48,30 @@ TEST_CASE("Command argument parsing", "[ChatCommand]")
|
||||
TestChatCommand("true", [](ChatHandler*, uint32) { return true; }, false);
|
||||
}
|
||||
|
||||
SECTION("std::vector<uint8>")
|
||||
SECTION("Floating point argument")
|
||||
{
|
||||
TestChatCommand("1 2 3 4 5 6 7 8 9 10", [](ChatHandler*, std::vector<uint8> v)
|
||||
TestChatCommand("0.5", [](ChatHandler*, float f)
|
||||
{
|
||||
REQUIRE(v.size() == 10);
|
||||
for (size_t i = 0; i < 10; ++i)
|
||||
REQUIRE(v[i] == (i + 1));
|
||||
REQUIRE(f == 0.5);
|
||||
return true;
|
||||
});
|
||||
TestChatCommand("true", [](ChatHandler*, float) { return true; }, false);
|
||||
}
|
||||
|
||||
SECTION("std::vector<uint16>")
|
||||
{
|
||||
TestChatCommand("1 2 3 4 5 6 7 8 9 10", [](ChatHandler*, std::vector<uint16> v)
|
||||
{
|
||||
REQUIRE(v == std::vector<uint16>{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
SECTION("std::array<uint16>")
|
||||
{
|
||||
TestChatCommand("1 2 3 4 5 6 7 8 9 10", [](ChatHandler*, std::array<uint16, 10> v)
|
||||
{
|
||||
REQUIRE(v == std::array<uint16, 10>{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user