aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTreeston <treeston.mmoc@gmail.com>2020-09-02 22:04:45 +0200
committerGitHub <noreply@github.com>2020-09-02 22:04:45 +0200
commitf45aa5cac1579e87cbc599ffb58e10e662066792 (patch)
tree250812f5a1608c21f3e04cbc460e8be0cece147c
parenteaf8fa75a1c76131ecbf2585db6d6236b7334b8e (diff)
Common/Util: Trinity::StringTo<double> support (PR #25364)
-rw-r--r--src/common/Utilities/StringConvert.h86
-rw-r--r--src/server/game/Chat/ChatCommands/ChatCommandArgs.h30
-rw-r--r--tests/common/StringConvert.cpp25
-rw-r--r--tests/game/ChatCommand.cpp27
4 files changed, 136 insertions, 32 deletions
diff --git a/src/common/Utilities/StringConvert.h b/src/common/Utilities/StringConvert.h
index f264c0222fa..4ec3324fb33 100644
--- a/src/common/Utilities/StringConvert.h
+++ b/src/common/Utilities/StringConvert.h
@@ -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
diff --git a/src/server/game/Chat/ChatCommands/ChatCommandArgs.h b/src/server/game/Chat/ChatCommands/ChatCommandArgs.h
index 583edcd40e2..9f10013989c 100644
--- a/src/server/game/Chat/ChatCommands/ChatCommandArgs.h
+++ b/src/server/game/Chat/ChatCommands/ChatCommandArgs.h
@@ -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;
}
};
diff --git a/tests/common/StringConvert.cpp b/tests/common/StringConvert.cpp
index d25e28f3d19..c1a6f5da7f8 100644
--- a/tests/common/StringConvert.cpp
+++ b/tests/common/StringConvert.cpp
@@ -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);
+}
diff --git a/tests/game/ChatCommand.cpp b/tests/game/ChatCommand.cpp
index f484b5806c5..2f73b579f84 100644
--- a/tests/game/ChatCommand.cpp
+++ b/tests/game/ChatCommand.cpp
@@ -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;
});
}