summaryrefslogtreecommitdiff
path: root/src/mongo/base
diff options
context:
space:
mode:
authorAndy Schwerin <schwerin@10gen.com>2012-09-21 14:52:49 -0400
committerAndy Schwerin <schwerin@10gen.com>2012-09-25 15:05:32 -0400
commit801992d49afe770bc2f240735028e161da11c532 (patch)
tree0a1b85c8f7758d6c55a1c04811732011fae34c82 /src/mongo/base
parent3aa13e9159d839760b54419fd54636cd11ae5e8b (diff)
downloadmongo-801992d49afe770bc2f240735028e161da11c532.tar.gz
parseNumberFromString* functions
A uniform interface for parsing numbers out of strings, with implementations for the standard integer types. Extension to double should be straightforward. Tests included.
Diffstat (limited to 'src/mongo/base')
-rw-r--r--src/mongo/base/SConscript4
-rw-r--r--src/mongo/base/error_codes.h3
-rw-r--r--src/mongo/base/parse_number.cpp117
-rw-r--r--src/mongo/base/parse_number.h74
-rw-r--r--src/mongo/base/parse_number_test.cpp152
5 files changed, 350 insertions, 0 deletions
diff --git a/src/mongo/base/SConscript b/src/mongo/base/SConscript
index 5d8c16d615b..ca3416fffd2 100644
--- a/src/mongo/base/SConscript
+++ b/src/mongo/base/SConscript
@@ -4,6 +4,7 @@ Import("env")
env.StaticLibrary('base', ['initializer_dependency_graph.cpp',
'make_string_vector.cpp',
+ 'parse_number.cpp',
'status.cpp'])
env.CppUnitTest('initializer_dependency_graph_test',
@@ -16,3 +17,6 @@ env.CppUnitTest('owned_pointer_vector_test',
env.CppUnitTest('status_test', 'status_test.cpp',
LIBDEPS=['base'])
+
+env.CppUnitTest('parse_number_test', ['parse_number_test.cpp'], LIBDEPS=['base'])
+
diff --git a/src/mongo/base/error_codes.h b/src/mongo/base/error_codes.h
index 1c12f204d81..0cc9dfafdf2 100644
--- a/src/mongo/base/error_codes.h
+++ b/src/mongo/base/error_codes.h
@@ -49,6 +49,7 @@ namespace mongo {
GraphContainsCycle = 5,
HostUnreachable = 6,
HostNotFound = 7,
+ FailedToParse = 9,
MaxError
};
@@ -70,6 +71,8 @@ namespace mongo {
return "DuplicateKey";
case GraphContainsCycle:
return "GraphContainsCycle";
+ case FailedToParse:
+ return "FailedToParse";
default:
return "Unknown error code";
}
diff --git a/src/mongo/base/parse_number.cpp b/src/mongo/base/parse_number.cpp
new file mode 100644
index 00000000000..ed3d0a89cd5
--- /dev/null
+++ b/src/mongo/base/parse_number.cpp
@@ -0,0 +1,117 @@
+/* Copyright 2012 10gen Inc.
+ *
+ * Licensed 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 "mongo/base/parse_number.h"
+
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <limits>
+
+#include "mongo/platform/strtoll.h"
+
+/**
+ * Macro providing the specialized implementation of parseNumberFromStringWithBase<NUMBER_TYPE>
+ * in terms of the strtol-like function CONV_FUNC.
+ */
+#define DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE(NUMBER_TYPE, CONV_FUNC) \
+ template<> \
+ Status parseNumberFromStringWithBase<NUMBER_TYPE>( \
+ const char* stringValue, \
+ int base, \
+ NUMBER_TYPE* out) { \
+ \
+ typedef ::std::numeric_limits<NUMBER_TYPE> limits; \
+ \
+ if (base == 1 || base < 0 || base > 36) \
+ return Status(ErrorCodes::BadValue, "Invalid base", 0); \
+ \
+ if (stringValue[0] == '\0' || ::isspace(stringValue[0])) \
+ return Status(ErrorCodes::FailedToParse, "Unparseable string", 0); \
+ \
+ if (!limits::is_signed && (stringValue[0] == '-')) \
+ return Status(ErrorCodes::FailedToParse, "Negative value", 0); \
+ \
+ errno = 0; \
+ char* endPtr; \
+ NUMBER_TYPE result = CONV_FUNC(stringValue, &endPtr, base); \
+ if (*endPtr != '\0') \
+ return Status(ErrorCodes::FailedToParse, "Non-digit characters at end of string");\
+ \
+ if ((ERANGE == errno) && ((result == limits::max()) || (result == limits::min()))) \
+ return Status(ErrorCodes::FailedToParse, "Value out of range", 0); \
+ \
+ if (errno != 0 && result == 0) \
+ return Status(ErrorCodes::FailedToParse, "Unparseable string", 0); \
+ \
+ *out = result; \
+ return Status::OK(); \
+ }
+
+/**
+ * Macro providing the specialized implementation of parseNumberFromStringWithBase<NUMBER_TYPE>
+ * in terms of parseNumberFromStringWithBase<BIGGER_NUMBER_TYPE>.
+ */
+#define DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE_IN_TERMS_OF(NUMBER_TYPE, BIGGER_NUMBER_TYPE) \
+ template<> \
+ Status parseNumberFromStringWithBase<NUMBER_TYPE>( \
+ const char* stringValue, \
+ int base, \
+ NUMBER_TYPE* out) { \
+ \
+ return parseNumberFromStringWithBaseUsingBiggerNumberType<NUMBER_TYPE, \
+ BIGGER_NUMBER_TYPE>( \
+ stringValue, base, out); \
+ }
+
+namespace mongo {
+
+namespace {
+
+ /**
+ * This function implements the functionality of parseNumberFromStringWithBase<NumberType>
+ * by calling parseNumberFromStringWithBase<BiggerNumberType> and checking for overflow on
+ * the range of NumberType. Used for "int" and "short" types, for which an equivalent to
+ * strtol is unavailable.
+ */
+ template<typename NumberType, typename BiggerNumberType>
+ Status parseNumberFromStringWithBaseUsingBiggerNumberType(
+ const char* stringValue,
+ int base,
+ NumberType* out) {
+ typedef ::std::numeric_limits<NumberType> limits;
+ BiggerNumberType result;
+ Status status = parseNumberFromStringWithBase(stringValue, base, &result);
+ if (Status::OK() != status)
+ return status;
+ if ((result < limits::min()) || (result > limits::max()))
+ return Status(ErrorCodes::FailedToParse, "Value out of range", 0);
+ *out = static_cast<NumberType>(result);
+ return Status::OK();
+ }
+} // namespace
+
+ // Definition of the various supported implementations of parseNumberFromStringWithBase.
+
+ DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE(long, strtol)
+ DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE(long long, strtoll)
+ DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE(unsigned long, strtoul)
+ DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE(unsigned long long, strtoull)
+
+ DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE_IN_TERMS_OF(short, long)
+ DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE_IN_TERMS_OF(unsigned short, unsigned long)
+ DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE_IN_TERMS_OF(int, long)
+ DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE_IN_TERMS_OF(unsigned int, unsigned long)
+} // namespace mongo
diff --git a/src/mongo/base/parse_number.h b/src/mongo/base/parse_number.h
new file mode 100644
index 00000000000..490e163ada2
--- /dev/null
+++ b/src/mongo/base/parse_number.h
@@ -0,0 +1,74 @@
+/* Copyright 2012 10gen Inc.
+ *
+ * Licensed 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.
+ */
+
+/**
+ * Utility functions for parsing numbers from strings.
+ */
+
+#pragma once
+
+#include <string>
+
+#include "mongo/base/status.h"
+
+namespace mongo {
+
+ /**
+ * Parse a number out of a string.
+ *
+ * Parses "stringValue", interpreting it as a number of the given "base". On success, stores
+ * the parsed value into "*result" and returns Status::OK().
+ *
+ * Valid values for "base" are 2-36, with 0 meaning "choose the base by inspecting the prefix
+ * on the number", as in strtol. Returns Status::BadValue if an illegal value is supplied for
+ * "base".
+ *
+ * The entirety of the string must consist of digits in the given base, except optionally the
+ * first character may be "+" or "-", and hexadecimal numbers may begin "0x". Same as strtol,
+ * without the property of stripping whitespace at the beginning, and fails to parse if there
+ * are non-digit characters at the end of the string.
+ *
+ * See parse_number.cpp for the available instantiations, and add any new instantiations there.
+ */
+ template <typename NumberType>
+ Status parseNumberFromStringWithBase(const char* stringValue, int base, NumberType* result);
+
+ /**
+ * Equivalent to parseNumberFromStringWithBase(stringValue, 0, result);
+ */
+ template <typename NumberType>
+ static inline Status parseNumberFromString(const char* stringValue, NumberType* result) {
+ return parseNumberFromStringWithBase(stringValue, 0, result);
+ }
+
+ /**
+ * Same as parseNumberFromStringWithBase, above, but taking a std::string instead of a c-style
+ * string.
+ */
+ template <typename NumberType>
+ static inline Status parseNumberFromStringWithBase(
+ const std::string& stringValue, int base, NumberType* result) {
+ return parseNumberFromStringWithBase(stringValue.c_str(), base, result);
+ }
+
+ /**
+ * Same as parseNumberFromString, above, but taking a std::string instead of a c-style string.
+ */
+ template <typename NumberType>
+ static inline Status parseNumberFromString(const std::string& stringValue, NumberType* result) {
+ return parseNumberFromString(stringValue.c_str(), result);
+ }
+
+} // namespace mongo
diff --git a/src/mongo/base/parse_number_test.cpp b/src/mongo/base/parse_number_test.cpp
new file mode 100644
index 00000000000..d5afb89157e
--- /dev/null
+++ b/src/mongo/base/parse_number_test.cpp
@@ -0,0 +1,152 @@
+/**
+ * Copyright (C) 2012 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "mongo/pch.h"
+
+#include <limits>
+
+#include "mongo/base/parse_number.h"
+#include "mongo/base/status.h"
+#include "mongo/util/mongoutils/str.h" // for str::stream()!
+#include "mongo/unittest/unittest.h"
+
+#define ASSERT_OK(EXPR) ASSERT_EQUALS(::mongo::Status::OK(), (EXPR))
+
+#define ASSERT_PARSES(TYPE, INPUT_STRING, EXPECTED_VALUE) do { \
+ TYPE v; \
+ ASSERT_OK(parseNumberFromString(INPUT_STRING, &v)); \
+ ASSERT_EQUALS(static_cast<TYPE>(EXPECTED_VALUE), v); \
+ } while (false)
+
+#define ASSERT_PARSES_WITH_BASE(TYPE, INPUT_STRING, BASE, EXPECTED_VALUE) do { \
+ TYPE v; \
+ ASSERT_OK(parseNumberFromStringWithBase(INPUT_STRING, BASE, &v)); \
+ ASSERT_EQUALS(static_cast<TYPE>(EXPECTED_VALUE), v); \
+ } while (false)
+
+namespace mongo {
+namespace {
+
+ template <typename _NumberType>
+ class CommonNumberParsingTests {
+ public:
+ typedef _NumberType NumberType;
+ typedef std::numeric_limits<NumberType> Limits;
+
+ static void TestRejectingBadBases() {
+ NumberType ignored;
+ ASSERT_EQUALS(ErrorCodes::BadValue, parseNumberFromStringWithBase("0", -1, &ignored));
+ ASSERT_EQUALS(ErrorCodes::BadValue, parseNumberFromStringWithBase("10", 1, &ignored));
+ ASSERT_EQUALS(ErrorCodes::BadValue, parseNumberFromStringWithBase("-10", 37, &ignored));
+ ASSERT_EQUALS(ErrorCodes::BadValue, parseNumberFromStringWithBase(" ", -1, &ignored));
+ ASSERT_EQUALS(ErrorCodes::BadValue, parseNumberFromStringWithBase("f", 37, &ignored));
+ ASSERT_EQUALS(ErrorCodes::BadValue, parseNumberFromStringWithBase("^%", -1, &ignored));
+ }
+
+ static void TestParsingNonNegatives() {
+ ASSERT_PARSES(NumberType, "10", 10);
+ ASSERT_PARSES(NumberType, "0", 0);
+ ASSERT_PARSES(NumberType, "0xff", 0xff);
+ ASSERT_PARSES(NumberType, "077", 077);
+ }
+
+ static void TestParsingNegatives() {
+ if (Limits::is_signed) {
+ ASSERT_PARSES(NumberType, "-10", -10);
+ ASSERT_PARSES(NumberType, "-0xff", -0xff);
+ ASSERT_PARSES(NumberType, "-077", -077);
+ }
+ else {
+ NumberType ignored;
+ ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("-10", &ignored));
+ ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("-0xff", &ignored));
+ ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("-077", &ignored));
+ }
+ }
+
+ static void TestParsingGarbage() {
+ NumberType ignored;
+ ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("", &ignored));
+ ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString(" ", &ignored));
+ ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString(" 10", &ignored));
+ ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("15b", &ignored));
+ }
+
+ static void TestParsingWithExplicitBase() {
+ NumberType ignored;
+ ASSERT_PARSES_WITH_BASE(NumberType, "15b", 16, 0x15b);
+ ASSERT_PARSES_WITH_BASE(NumberType, "77", 8, 077);
+ ASSERT_PARSES_WITH_BASE(NumberType, "z", 36, 35);
+ ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromStringWithBase("1b", 10, &ignored));
+ ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromStringWithBase("80", 8, &ignored));
+ }
+
+ static void TestParsingLimits() {
+ using namespace mongoutils;
+ NumberType ignored;
+ ASSERT_PARSES(NumberType, (str::stream() << Limits::max()), Limits::max());
+ ASSERT_PARSES(NumberType, (str::stream() << Limits::min()), Limits::min());
+ ASSERT_EQUALS(ErrorCodes::FailedToParse,
+ parseNumberFromString((str::stream() << Limits::max() << '0'), &ignored));
+
+ if (Limits::is_signed)
+ ASSERT_EQUALS(
+ ErrorCodes::FailedToParse,
+ parseNumberFromString((str::stream() << Limits::min() << '0'), &ignored));
+ }
+ };
+
+#define GENERAL_NUMBER_TESTS(SHORT_NAME, TYPE) \
+ class ParseNumberTests##SHORT_NAME : public unittest::Test { \
+ public: \
+ typedef CommonNumberParsingTests<TYPE> TestFns; \
+ }; \
+ TEST_F(ParseNumberTests##SHORT_NAME, RejectBadBases) { \
+ TestFns::TestRejectingBadBases(); \
+ } \
+ TEST_F(ParseNumberTests##SHORT_NAME, ParseNonNegatives) { \
+ TestFns::TestParsingNonNegatives(); \
+ } \
+ TEST_F(ParseNumberTests##SHORT_NAME, ParseNegatives) { \
+ TestFns::TestParsingNegatives(); \
+ } \
+ TEST_F(ParseNumberTests##SHORT_NAME, ParseGarbage) { \
+ TestFns::TestParsingGarbage(); \
+ } \
+ TEST_F(ParseNumberTests##SHORT_NAME, ParseWithExplicitBase) { \
+ TestFns::TestParsingWithExplicitBase(); \
+ } \
+ TEST_F(ParseNumberTests##SHORT_NAME, TestParsingLimits) { \
+ TestFns::TestParsingLimits(); \
+ }
+
+ GENERAL_NUMBER_TESTS(Short, short)
+ GENERAL_NUMBER_TESTS(Int, int)
+ GENERAL_NUMBER_TESTS(Long, long)
+ GENERAL_NUMBER_TESTS(LongLong, long long)
+ GENERAL_NUMBER_TESTS(UnsignedShort, unsigned short)
+ GENERAL_NUMBER_TESTS(UnsignedInt, unsigned int)
+ GENERAL_NUMBER_TESTS(UnsignedLong, unsigned long)
+ GENERAL_NUMBER_TESTS(UnsignedLongLong, unsigned long long)
+ GENERAL_NUMBER_TESTS(Int16, int16_t);
+ GENERAL_NUMBER_TESTS(Int32, int32_t);
+ GENERAL_NUMBER_TESTS(Int64, int64_t);
+ GENERAL_NUMBER_TESTS(UInt16, uint16_t);
+ GENERAL_NUMBER_TESTS(UInt32, uint32_t);
+ GENERAL_NUMBER_TESTS(UInt64, uint64_t);
+
+} // namespace
+} // namespace mongo