From: Adam C. Emerson Date: Fri, 6 Mar 2020 04:13:47 +0000 (-0500) Subject: strtol: Add parse/consume for string_view friendly interface X-Git-Tag: v15.2.8~5^2~1^2~31^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=c20af1649fb9c2fc86b23320fa2a2acaa3616918;p=ceph.git strtol: Add parse/consume for string_view friendly interface Also these don't have the stringstream overhead. Signed-off-by: Adam C. Emerson (cherry picked from commit a29695e82ec8a93b000322773949f30694abf3d3) --- diff --git a/src/common/strtol.h b/src/common/strtol.h index a7c0cc220b6f..42f6fac9b03e 100644 --- a/src/common/strtol.h +++ b/src/common/strtol.h @@ -15,10 +15,122 @@ #ifndef CEPH_COMMON_STRTOL_H #define CEPH_COMMON_STRTOL_H +#if __has_include() +#include +#endif // __has_include() +#include +#include +#include #include -extern "C" { -#include +#include +#include +#include + + +namespace ceph { +#if __has_include() +// Wrappers around std::from_chars. +// +// Why do we want this instead of strtol and friends? Because the +// string doesn't have to be NUL-terminated! (Also, for a lot of +// purposes, just putting a string_view in and getting an optional out +// is friendly.) +// +// Returns the found number on success. Returns an empty optional on +// failure OR on trailing characters. +template +auto parse(std::string_view s, int base = 10) + -> std::enable_if_t, std::optional> +{ + T t; + auto r = std::from_chars(s.data(), s.data() + s.size(), t, base); + if ((r.ec != std::errc{}) || (r.ptr != s.data() + s.size())) { + return std::nullopt; + } + return t; +} + +// As above, but succeed on trailing characters and trim the supplied +// string_view to remove the parsed number. Set the supplied +// string_view to empty if it ends with the number. +template +auto consume(std::string_view& s, int base = 10) + -> std::enable_if_t, std::optional> +{ + T t; + auto r = std::from_chars(s.data(), s.data() + s.size(), t, base); + if (r.ec != std::errc{}) + return std::nullopt; + + if (r.ptr == s.data() + s.size()) { + s = std::string_view{}; + } else { + s.remove_prefix(r.ptr - s.data()); + } + return t; +} +// Sadly GCC is missing the floating point versions. +#else // __has_include() +template +auto parse(std::string_view sv, int base = 10) + -> std::enable_if_t, std::optional> +{ + std::string s(sv); + char* end = nullptr; + std::conditional_t, std::intmax_t, std::uintmax_t> v; + errno = 0; + + if (s.size() > 0 && std::isspace(s[0])) + return std::nullopt; + + if constexpr (std::is_signed_v) { + v = std::strtoimax(s.data(), &end, base); + } else { + if (s.size() > 0 && s[0] == '-') + return std::nullopt; + v = std::strtoumax(s.data(), &end, base); + } + if (errno != 0 || + end != s.data() + s.size() || + v > std::numeric_limits::max() || + v < std::numeric_limits::min()) + return std::nullopt; + return static_cast(v); +} + +template +auto consume(std::string_view& sv, int base = 10) + -> std::enable_if_t, std::optional> +{ + std::string s(sv); + char* end = nullptr; + std::conditional_t, std::intmax_t, std::uintmax_t> v; + errno = 0; + + if (s.size() > 0 && std::isspace(s[0])) + return std::nullopt; + + if constexpr (std::is_signed_v) { + v = std::strtoimax(s.data(), &end, base); + } else { + if (s.size() > 0 && s[0] == '-') + return std::nullopt; + v = std::strtoumax(s.data(), &end, base); + } + if (errno != 0 || + end == s.data() || + v > std::numeric_limits::max() || + v < std::numeric_limits::min()) + return std::nullopt; + if (end == s.data() + s.size()) { + sv = std::string_view{}; + } else { + sv.remove_prefix(end - s.data()); + } + return static_cast(v); } +#endif // __has_include() +} // namespace ceph long long strict_strtoll(const char *str, int base, std::string *err); diff --git a/src/test/strtol.cc b/src/test/strtol.cc index 673bf2977f41..901f33cf8ceb 100644 --- a/src/test/strtol.cc +++ b/src/test/strtol.cc @@ -12,11 +12,12 @@ * */ -#include "common/strtol.h" -#include +#include #include #include +#include "common/strtol.h" + #include "gtest/gtest.h" static void test_strict_strtoll(const char *str, long long expected, int base) @@ -398,6 +399,249 @@ TEST(StrictSICast, Error) { } } + +using ceph::parse; +using ceph::consume; +using namespace std::literals; + +template +inline void test_parse() { + auto r = parse("23"sv); + ASSERT_TRUE(r); + EXPECT_EQ(*r, 23); + + r = parse(" 23"sv); + EXPECT_FALSE(r); + + r = parse("-5"sv); + if constexpr (std::is_signed_v) { + ASSERT_TRUE(r); + EXPECT_EQ(*r, -5); + } else { + EXPECT_FALSE(r); + } + + r = parse("meow"sv); + EXPECT_FALSE(r); + + r = parse("9yards"sv); + EXPECT_FALSE(r); +} + +TEST(Parse, Char) { + test_parse(); +} + +TEST(Parse, UChar) { + test_parse(); +} + +TEST(Parse, SChar) { + test_parse(); +} + +TEST(Parse, UInt8) { + test_parse(); +} + +TEST(Parse, Int8) { + test_parse(); +} + +TEST(Parse, UInt16) { + test_parse(); +} + +TEST(Parse, Int16) { + test_parse(); +} + +TEST(Parse, UInt32) { + test_parse(); +} + +TEST(Parse, Int32) { + test_parse(); +} + +TEST(Parse, UInt64) { + test_parse(); +} + +TEST(Parse, Int64) { + test_parse(); +} + +TEST(Parse, UIntMax) { + test_parse(); +} + +TEST(Parse, IntMax) { + test_parse(); +} + +TEST(Parse, UIntPtr) { + test_parse(); +} + +TEST(Parse, IntPtr) { + test_parse(); +} + +TEST(Parse, UShort) { + test_parse(); +} + +TEST(Parse, Short) { + test_parse(); +} + +TEST(Parse, ULong) { + test_parse(); +} + +TEST(Parse, Long) { + test_parse(); +} + +TEST(Parse, ULongLong) { + test_parse(); +} + +TEST(Parse, LongLong) { + test_parse(); +} + +template +inline void test_consume() { + auto pos = "23"sv; + auto spacepos = " 23"sv; + auto neg = "-5"sv; + auto meow = "meow"sv; + auto trail = "9yards"sv; + + auto v = pos; + auto r = consume(v); + ASSERT_TRUE(r); + EXPECT_EQ(*r, 23); + EXPECT_TRUE(v.empty()); + + v = spacepos; + r = consume(v); + EXPECT_FALSE(r); + EXPECT_EQ(v, spacepos); + + v = neg; + r = consume(v); + if constexpr (std::is_signed_v) { + ASSERT_TRUE(r); + EXPECT_EQ(*r, -5); + EXPECT_TRUE(v.empty()); + } else { + EXPECT_FALSE(r); + EXPECT_EQ(v, neg); + } + + v = meow; + r = consume(v); + EXPECT_FALSE(r); + EXPECT_EQ(v, meow); + + v = trail; + r = consume(v); + ASSERT_TRUE(r); + EXPECT_EQ(*r, 9); + auto w = trail; + w.remove_prefix(1); + EXPECT_EQ(v, w); +} + +TEST(Consume, Char) { + test_consume(); +} + +TEST(Consume, UChar) { + test_consume(); +} + +TEST(Consume, SChar) { + test_consume(); +} + +TEST(Consume, UInt8) { + test_consume(); +} + +TEST(Consume, Int8) { + test_consume(); +} + +TEST(Consume, UInt16) { + test_consume(); +} + +TEST(Consume, Int16) { + test_consume(); +} + +TEST(Consume, UInt32) { + test_consume(); +} + +TEST(Consume, Int32) { + test_consume(); +} + +TEST(Consume, UInt64) { + test_consume(); +} + +TEST(Consume, Int64) { + test_consume(); +} + +TEST(Consume, UIntMax) { + test_consume(); +} + +TEST(Consume, IntMax) { + test_consume(); +} + +TEST(Consume, UIntPtr) { + test_consume(); +} + +TEST(Consume, IntPtr) { + test_consume(); +} + +TEST(Consume, UShort) { + test_consume(); +} + +TEST(Consume, Short) { + test_consume(); +} + +TEST(Consume, ULong) { + test_consume(); +} + +TEST(Consume, Long) { + test_consume(); +} + +TEST(Consume, ULongLong) { + test_consume(); +} + +TEST(Consume, LongLong) { + test_consume(); +} + + + /* * Local Variables: * compile-command: "cd .. ; make unittest_strtol && ./unittest_strtol"