This is an automated email from the ASF dual-hosted git repository.
twice pushed a commit to branch unstable
in repository https://gitbox.apache.org/repos/asf/incubator-kvrocks.git
The following commit(s) were added to refs/heads/unstable by this push:
new e4ae784 Add `ParseInt` in `StatusOr` style (#787)
e4ae784 is described below
commit e4ae784a271b4d815eb3a015e27c6f6e296b3036
Author: Twice <[email protected]>
AuthorDate: Sat Aug 27 22:29:15 2022 +0800
Add `ParseInt` in `StatusOr` style (#787)
- add `TryParseInt<T>`, `ParseInt<T>` and unit tests
- use `union` in `StatusOr` since it is more readable and no strict
aliasing issue
---
src/parse_util.h | 141 ++++++++++++++++++++++++++++++++++++++++++++
src/status.h | 83 ++++++++++++--------------
tests/cppunit/main.cc | 3 +-
tests/cppunit/parse_util.cc | 57 ++++++++++++++++++
4 files changed, 239 insertions(+), 45 deletions(-)
diff --git a/src/parse_util.h b/src/parse_util.h
new file mode 100644
index 0000000..0718540
--- /dev/null
+++ b/src/parse_util.h
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#pragma once
+
+#include <cstdlib>
+#include <limits>
+#include <string>
+#include <tuple>
+#include <status.h>
+
+namespace details {
+
+template <typename>
+struct ParseIntFunc;
+
+template <>
+struct ParseIntFunc<short> { // NOLINT
+ constexpr static const auto value = std::strtol;
+};
+
+template <>
+struct ParseIntFunc<int> {
+ constexpr static const auto value = std::strtol;
+};
+
+template <>
+struct ParseIntFunc<long> { // NOLINT
+ constexpr static const auto value = std::strtol;
+};
+
+template <>
+struct ParseIntFunc<long long> { // NOLINT
+ constexpr static const auto value = std::strtoll;
+};
+
+template <>
+struct ParseIntFunc<unsigned short> { // NOLINT
+ constexpr static const auto value = std::strtoul;
+};
+
+template <>
+struct ParseIntFunc<unsigned> {
+ constexpr static const auto value = std::strtoul;
+};
+
+template <>
+struct ParseIntFunc<unsigned long> { // NOLINT
+ constexpr static const auto value = std::strtoul;
+};
+
+template <>
+struct ParseIntFunc<unsigned long long> { // NOLINT
+ constexpr static const auto value = std::strtoull;
+};
+
+} // namespace details
+
+template <typename T>
+using ParseResultAndPos = std::tuple<T, const char *>;
+
+// TryParseInt parses a string to a integer,
+// if non-integer characters is encountered, it stop parsing and
+// return the result integer and the current string position.
+// e.g. TryParseInt("100MB") -> {100, "MB"}
+// if no integer can be parsed or out of type range, an error will be returned
+// base can be in {0, 2, ..., 36}, refer to strto* in standard c for more
details
+template <typename T = long long> // NOLINT
+StatusOr<ParseResultAndPos<T>> TryParseInt(const char *v, int base = 0) {
+ char *end;
+
+ errno = 0;
+ auto res = details::ParseIntFunc<T>::value(v, &end, base);
+
+ if (v == end) {
+ return {Status::NotOK, "not started as an integer"};
+ }
+
+ if (errno) {
+ return {Status::NotOK, std::strerror(errno)};
+ }
+
+ if (!std::is_same<T, decltype(res)>::value &&
+ (res < std::numeric_limits<T>::min() || res >
std::numeric_limits<T>::max())) {
+ return {Status::NotOK, "out of range of integer type"};
+ }
+
+ return ParseResultAndPos<T>{res, end};
+}
+
+// ParseInt parses a string to a integer,
+// not like TryParseInt, the whole string need to be parsed as an integer,
+// e.g. ParseInt("100MB") -> error status
+template <typename T = long long> // NOLINT
+StatusOr<T> ParseInt(const std::string& v, int base = 0) {
+ const char *begin = v.c_str();
+ auto res = TryParseInt<T>(begin, base);
+
+ if (!res) return res;
+
+ if (std::get<1>(*res) != begin + v.size()) {
+ return {Status::NotOK, "encounter non-integer characters"};
+ }
+
+ return std::get<0>(*res);
+}
+
+template <typename T>
+using NumericRange = std::tuple<T, T>;
+
+// this overload accepts a range {min, max},
+// integer out of the range will trigger an error status
+template <typename T = long long> // NOLINT
+StatusOr<T> ParseInt(const std::string& v, NumericRange<T> range, int base =
0) {
+ auto res = ParseInt<T>(v, base);
+
+ if (!res) return res;
+
+ if (*res < std::get<0>(range) || *res > std::get<1>(range)) {
+ return {Status::NotOK, "out of numeric range"};
+ }
+
+ return *res;
+}
diff --git a/src/status.h b/src/status.h
index 5ec3f5f..a742b4d 100644
--- a/src/status.h
+++ b/src/status.h
@@ -80,12 +80,12 @@ class Status {
}
std::string Msg() const& {
- if (*this) return ok_msg();
+ if (*this) return ok_msg;
return msg_;
}
std::string Msg() && {
- if (*this) return ok_msg();
+ if (*this) return ok_msg;
return std::move(msg_);
}
@@ -95,11 +95,9 @@ class Status {
Code code_;
std::string msg_;
- static constexpr const char* ok_msg() {
- return "ok";
- }
+ static constexpr const char* ok_msg = "ok";
- template <typename T>
+ template <typename>
friend struct StatusOr;
};
@@ -109,10 +107,10 @@ using first_element = typename std::tuple_element<0,
std::tuple<Ts...>>::type;
template <typename T>
using remove_cvref_t = typename std::remove_cv<typename
std::remove_reference<T>::type>::type;
-template <typename T>
+template <typename>
struct StatusOr;
-template <typename T>
+template <typename>
struct IsStatusOr : std::integral_constant<bool, false> {};
template <typename T>
@@ -134,12 +132,12 @@ struct StatusOr {
explicit StatusOr(Status s) : code_(s.code_) {
CHECK(!s);
- new(value_or_error_) error_type(new std::string(std::move(s.msg_)));
+ new(&error_) error_type(new std::string(std::move(s.msg_)));
}
StatusOr(Code code, std::string msg = {}) : code_(code) { // NOLINT
CHECK(code != Code::cOK);
- new(value_or_error_) error_type(new std::string(std::move(msg)));
+ new(&error_) error_type(new std::string(std::move(msg)));
}
template <typename ...Ts,
@@ -151,26 +149,34 @@ struct StatusOr {
!std::is_same<StatusOr, remove_cvref_t<first_element<Ts...>>>::value
), int>::type = 0> // NOLINT
explicit StatusOr(Ts && ... args) : code_(Code::cOK) {
- new(value_or_error_) value_type(std::forward<Ts>(args)...);
+ new(&value_) value_type(std::forward<Ts>(args)...);
}
StatusOr(T&& value) : code_(Code::cOK) { // NOLINT
- new(value_or_error_) value_type(std::move(value));
+ new(&value_) value_type(std::move(value));
}
StatusOr(const T& value) : code_(Code::cOK) { // NOLINT
- new(value_or_error_) value_type(value);
+ new(&value_) value_type(value);
}
StatusOr(const StatusOr&) = delete;
- StatusOr(StatusOr&& other) : code_(other.code_) {
+
+ template <typename U, typename std::enable_if<std::is_convertible<U,
T>::value, int>::type = 0>
+ StatusOr(StatusOr<U>&& other) : code_(other.code_) {
if (code_ == Code::cOK) {
- new(value_or_error_) value_type(std::move(other.getValue()));
+ new(&value_) value_type(std::move(other.value_));
} else {
- new(value_or_error_) error_type(std::move(other.getError()));
+ new(&error_) error_type(std::move(other.error_));
}
}
+ template <typename U, typename std::enable_if<!std::is_convertible<U,
T>::value, int>::type = 0>
+ StatusOr(StatusOr<U>&& other) : code_(other.code_) {
+ CHECK(code_ != Code::cOK);
+ new(&error_) error_type(std::move(other.error_));
+ }
+
Status& operator=(const Status&) = delete;
template <Code code>
@@ -181,12 +187,12 @@ struct StatusOr {
Status ToStatus() const& {
if (*this) return Status::OK();
- return Status(code_, *getError());
+ return Status(code_, *error_);
}
Status ToStatus() && {
if (*this) return Status::OK();
- return Status(code_, std::move(*getError()));
+ return Status(code_, std::move(*error_));
}
Code GetCode() const {
@@ -195,17 +201,17 @@ struct StatusOr {
value_type& GetValue() & {
CHECK(*this);
- return getValue();
+ return value_;
}
value_type&& GetValue() && {
CHECK(*this);
- return std::move(getValue());
+ return std::move(value_);
}
const value_type& GetValue() const& {
CHECK(*this);
- return getValue();
+ return value_;
}
value_type& operator*() & {
@@ -229,41 +235,30 @@ struct StatusOr {
}
std::string Msg() const& {
- if (*this) return Status::ok_msg();
- return *getError();
+ if (*this) return Status::ok_msg;
+ return *error_;
}
std::string Msg() && {
- if (*this) return Status::ok_msg();
- return std::move(*getError());
+ if (*this) return Status::ok_msg;
+ return std::move(*error_);
}
~StatusOr() {
if (*this) {
- getValue().~value_type();
+ value_.~value_type();
} else {
- getError().~error_type();
+ error_.~error_type();
}
}
private:
Status::Code code_;
- alignas(value_type) alignas(error_type) unsigned char value_or_error_
- [sizeof(value_type) < sizeof(error_type) ? sizeof(error_type) :
sizeof(value_type)];
-
- value_type& getValue() {
- return *reinterpret_cast<value_type*>(value_or_error_);
- }
-
- const value_type& getValue() const {
- return *reinterpret_cast<const value_type*>(value_or_error_);
- }
-
- error_type& getError() {
- return *reinterpret_cast<error_type*>(value_or_error_);
- }
+ union {
+ value_type value_;
+ error_type error_;
+ };
- const error_type& getError() const {
- return *reinterpret_cast<const error_type*>(value_or_error_);
- }
+ template <typename>
+ friend struct StatusOr;
};
diff --git a/tests/cppunit/main.cc b/tests/cppunit/main.cc
index 06cc4fc..8ef4873 100644
--- a/tests/cppunit/main.cc
+++ b/tests/cppunit/main.cc
@@ -27,7 +27,8 @@ Server *GetServer() {
}
int main(int argc, char **argv) {
-// gflags::SetUsageMessage("kvrocks unittest");
testing::InitGoogleTest(&argc, argv);
+ google::InitGoogleLogging(argv[0]);
+ google::InstallFailureSignalHandler();
return RUN_ALL_TESTS();
}
diff --git a/tests/cppunit/parse_util.cc b/tests/cppunit/parse_util.cc
new file mode 100644
index 0000000..6c00c8f
--- /dev/null
+++ b/tests/cppunit/parse_util.cc
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <gtest/gtest.h>
+
+#include <parse_util.h>
+
+TEST(ParseUtil, TryParseInt) {
+ long long v;
+ const char *str = "12345hellooo", *end;
+
+ std::tie(v, end) = *TryParseInt(str);
+ ASSERT_EQ(v, 12345);
+ ASSERT_EQ(end - str, 5);
+
+ ASSERT_EQ(TryParseInt("hello").Msg(), "not started as an integer");
+ ASSERT_FALSE(TryParseInt("9999999999999999999999999999999999"));
+ ASSERT_FALSE(TryParseInt("1", 100));
+}
+
+TEST(ParseUtil, ParseInt) {
+ ASSERT_EQ(*ParseInt("-2333"), -2333);
+ ASSERT_EQ(*ParseInt("0x1a"), 26);
+ ASSERT_EQ(*ParseInt("011"), 9);
+ ASSERT_EQ(*ParseInt("111", 2), 7);
+ ASSERT_EQ(*ParseInt("11", 010), 9);
+ ASSERT_EQ(*ParseInt("11", 10), 11);
+ ASSERT_EQ(*ParseInt("11", 0x10), 17);
+
+ ASSERT_EQ(ParseInt("hello").Msg(), "not started as an integer");
+ ASSERT_EQ(ParseInt("123hello").Msg(), "encounter non-integer characters");
+ ASSERT_FALSE(ParseInt("9999999999999999999999999999999999"));
+ ASSERT_EQ(ParseInt<short>("99999").Msg(), "out of range of integer type");
+ ASSERT_EQ(*ParseInt<short>("30000"), 30000);
+ ASSERT_EQ(*ParseInt<int>("99999"), 99999);
+ ASSERT_EQ(ParseInt<int>("3000000000").Msg(), "out of range of integer type");
+
+ ASSERT_EQ(*ParseInt("123", {0, 123}), 123);
+ ASSERT_EQ(ParseInt("124", {0, 123}).Msg(), "out of numeric range");
+}