]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
strtol: Add parse/consume for string_view friendly interface 37809/head
authorAdam C. Emerson <aemerson@redhat.com>
Fri, 6 Mar 2020 04:13:47 +0000 (23:13 -0500)
committerNathan Cutler <ncutler@suse.com>
Tue, 27 Oct 2020 09:51:10 +0000 (10:51 +0100)
Also these don't have the stringstream overhead.

Signed-off-by: Adam C. Emerson <aemerson@redhat.com>
(cherry picked from commit a29695e82ec8a93b000322773949f30694abf3d3)

src/common/strtol.h
src/test/strtol.cc

index a7c0cc220b6f9c6f02c5c1dde5d612d3ff59d9b4..42f6fac9b03e1bb43cd75db817dcf960ed4274ce 100644 (file)
 #ifndef CEPH_COMMON_STRTOL_H
 #define CEPH_COMMON_STRTOL_H
 
+#if __has_include(<charconv>)
+#include <charconv>
+#endif // __has_include(<charconv>)
+#include <cinttypes>
+#include <cstdlib>
+#include <optional>
 #include <string>
-extern "C" {
-#include <stdint.h>
+#include <string_view>
+#include <system_error>
+#include <type_traits>
+
+
+namespace ceph {
+#if __has_include(<charconv>)
+// 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<typename T>
+auto parse(std::string_view s, int base = 10)
+  -> std::enable_if_t<std::is_integral_v<T>, std::optional<T>>
+{
+  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<typename T>
+auto consume(std::string_view& s, int base = 10)
+  -> std::enable_if_t<std::is_integral_v<T>, std::optional<T>>
+{
+  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(<charconv>)
+template<typename T>
+auto parse(std::string_view sv, int base = 10)
+  -> std::enable_if_t<std::is_integral_v<T>, std::optional<T>>
+{
+  std::string s(sv);
+  char* end = nullptr;
+  std::conditional_t<std::is_signed_v<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<T>) {
+    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<T>::max() ||
+      v < std::numeric_limits<T>::min())
+    return std::nullopt;
+  return static_cast<T>(v);
+}
+
+template<typename T>
+auto consume(std::string_view& sv, int base = 10)
+  -> std::enable_if_t<std::is_integral_v<T>, std::optional<T>>
+{
+  std::string s(sv);
+  char* end = nullptr;
+  std::conditional_t<std::is_signed_v<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<T>) {
+    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<T>::max() ||
+      v < std::numeric_limits<T>::min())
+    return std::nullopt;
+  if (end == s.data() + s.size()) {
+    sv = std::string_view{};
+  } else {
+    sv.remove_prefix(end - s.data());
+  }
+  return static_cast<T>(v);
 }
+#endif // __has_include(<charconv>)
+} // namespace ceph
 
 long long strict_strtoll(const char *str, int base, std::string *err);
 
index 673bf2977f416826a6b9cf46a29836646c2dd99c..901f33cf8ceb7cb5674f56cfdf28ab29a8f4ed42 100644 (file)
  *
  */
 
-#include "common/strtol.h"
-#include <math.h>
+#include <cmath>
 #include <string>
 #include <map>
 
+#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<typename T>
+inline void test_parse() {
+  auto r = parse<T>("23"sv);
+  ASSERT_TRUE(r);
+  EXPECT_EQ(*r, 23);
+
+  r = parse<T>("    23"sv);
+  EXPECT_FALSE(r);
+
+  r = parse<T>("-5"sv);
+  if constexpr (std::is_signed_v<T>) {
+    ASSERT_TRUE(r);
+    EXPECT_EQ(*r, -5);
+  } else {
+    EXPECT_FALSE(r);
+  }
+
+  r = parse<T>("meow"sv);
+  EXPECT_FALSE(r);
+
+  r = parse<T>("9yards"sv);
+  EXPECT_FALSE(r);
+}
+
+TEST(Parse, Char) {
+  test_parse<char>();
+}
+
+TEST(Parse, UChar) {
+  test_parse<unsigned char>();
+}
+
+TEST(Parse, SChar) {
+  test_parse<signed char>();
+}
+
+TEST(Parse, UInt8) {
+  test_parse<std::uint8_t>();
+}
+
+TEST(Parse, Int8) {
+  test_parse<std::int8_t>();
+}
+
+TEST(Parse, UInt16) {
+  test_parse<std::uint16_t>();
+}
+
+TEST(Parse, Int16) {
+  test_parse<std::int16_t>();
+}
+
+TEST(Parse, UInt32) {
+  test_parse<std::uint32_t>();
+}
+
+TEST(Parse, Int32) {
+  test_parse<std::int32_t>();
+}
+
+TEST(Parse, UInt64) {
+  test_parse<std::uint64_t>();
+}
+
+TEST(Parse, Int64) {
+  test_parse<std::int64_t>();
+}
+
+TEST(Parse, UIntMax) {
+  test_parse<std::uintmax_t>();
+}
+
+TEST(Parse, IntMax) {
+  test_parse<std::intmax_t>();
+}
+
+TEST(Parse, UIntPtr) {
+  test_parse<std::uintptr_t>();
+}
+
+TEST(Parse, IntPtr) {
+  test_parse<std::intptr_t>();
+}
+
+TEST(Parse, UShort) {
+  test_parse<unsigned short>();
+}
+
+TEST(Parse, Short) {
+  test_parse<short>();
+}
+
+TEST(Parse, ULong) {
+  test_parse<unsigned long>();
+}
+
+TEST(Parse, Long) {
+  test_parse<long>();
+}
+
+TEST(Parse, ULongLong) {
+  test_parse<unsigned long long>();
+}
+
+TEST(Parse, LongLong) {
+  test_parse<long long>();
+}
+
+template<typename T>
+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<T>(v);
+  ASSERT_TRUE(r);
+  EXPECT_EQ(*r, 23);
+  EXPECT_TRUE(v.empty());
+
+  v = spacepos;
+  r = consume<T>(v);
+  EXPECT_FALSE(r);
+  EXPECT_EQ(v, spacepos);
+
+  v = neg;
+  r = consume<T>(v);
+  if constexpr (std::is_signed_v<T>) {
+    ASSERT_TRUE(r);
+    EXPECT_EQ(*r, -5);
+    EXPECT_TRUE(v.empty());
+  } else {
+    EXPECT_FALSE(r);
+    EXPECT_EQ(v, neg);
+  }
+
+  v = meow;
+  r = consume<T>(v);
+  EXPECT_FALSE(r);
+  EXPECT_EQ(v, meow);
+
+  v = trail;
+  r = consume<T>(v);
+  ASSERT_TRUE(r);
+  EXPECT_EQ(*r, 9);
+  auto w = trail;
+  w.remove_prefix(1);
+  EXPECT_EQ(v, w);
+}
+
+TEST(Consume, Char) {
+  test_consume<char>();
+}
+
+TEST(Consume, UChar) {
+  test_consume<unsigned char>();
+}
+
+TEST(Consume, SChar) {
+  test_consume<signed char>();
+}
+
+TEST(Consume, UInt8) {
+  test_consume<std::uint8_t>();
+}
+
+TEST(Consume, Int8) {
+  test_consume<std::int8_t>();
+}
+
+TEST(Consume, UInt16) {
+  test_consume<std::uint16_t>();
+}
+
+TEST(Consume, Int16) {
+  test_consume<std::int16_t>();
+}
+
+TEST(Consume, UInt32) {
+  test_consume<std::uint32_t>();
+}
+
+TEST(Consume, Int32) {
+  test_consume<std::int32_t>();
+}
+
+TEST(Consume, UInt64) {
+  test_consume<std::uint64_t>();
+}
+
+TEST(Consume, Int64) {
+  test_consume<std::int64_t>();
+}
+
+TEST(Consume, UIntMax) {
+  test_consume<std::uintmax_t>();
+}
+
+TEST(Consume, IntMax) {
+  test_consume<std::intmax_t>();
+}
+
+TEST(Consume, UIntPtr) {
+  test_consume<std::uintptr_t>();
+}
+
+TEST(Consume, IntPtr) {
+  test_consume<std::intptr_t>();
+}
+
+TEST(Consume, UShort) {
+  test_consume<unsigned short>();
+}
+
+TEST(Consume, Short) {
+  test_consume<short>();
+}
+
+TEST(Consume, ULong) {
+  test_consume<unsigned long>();
+}
+
+TEST(Consume, Long) {
+  test_consume<long>();
+}
+
+TEST(Consume, ULongLong) {
+  test_consume<unsigned long long>();
+}
+
+TEST(Consume, LongLong) {
+  test_consume<long long>();
+}
+
+
+
 /*
  * Local Variables:
  * compile-command: "cd .. ; make unittest_strtol && ./unittest_strtol"