diff options
author | Andy Schwerin <schwerin@10gen.com> | 2012-09-21 14:52:49 -0400 |
---|---|---|
committer | Andy Schwerin <schwerin@10gen.com> | 2012-09-25 15:05:32 -0400 |
commit | 801992d49afe770bc2f240735028e161da11c532 (patch) | |
tree | 0a1b85c8f7758d6c55a1c04811732011fae34c82 /src/mongo/base | |
parent | 3aa13e9159d839760b54419fd54636cd11ae5e8b (diff) | |
download | mongo-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/SConscript | 4 | ||||
-rw-r--r-- | src/mongo/base/error_codes.h | 3 | ||||
-rw-r--r-- | src/mongo/base/parse_number.cpp | 117 | ||||
-rw-r--r-- | src/mongo/base/parse_number.h | 74 | ||||
-rw-r--r-- | src/mongo/base/parse_number_test.cpp | 152 |
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 |