#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);
*
*/
-#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)
}
}
+
+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"