Commit f45a5d5c authored by Kenton Varda's avatar Kenton Varda

Merge pull request #328 from katreniak/bka-parse

Implement method kj::StringPtr:parseAs<NUMBER_TYPE>
parents 539f9d83 651aaf3b
......@@ -384,31 +384,6 @@ void JsonCodec::encodeField(StructSchema::Field field, DynamicValue::Reader inpu
}
namespace {
int64_t parseInt64(kj::StringPtr s) {
char *endPtr;
errno = 0;
int64_t value = strtoll(s.begin(), &endPtr, 10);
KJ_REQUIRE(endPtr == s.end(), "String does not contain valid number", s);
KJ_REQUIRE(errno != ERANGE, "Value out-of-range", s);
return value;
}
uint64_t parseUInt64(kj::StringPtr s) {
char *endPtr;
errno = 0;
uint64_t value = strtoull(s.begin(), &endPtr, 10);
KJ_REQUIRE(endPtr == s.end(), "String does not contain valid number", s);
KJ_REQUIRE(errno != ERANGE, "Value out-of-range", s);
return value;
}
double parseFloat64(kj::StringPtr s) {
char *endPtr;
errno = 0;
double value = strtod(s.begin(), &endPtr);
KJ_REQUIRE(endPtr == s.end(), "String does not contain valid floating number", s);
return value;
}
template <typename SetFn, typename DecodeArrayFn, typename DecodeObjectFn>
void decodeField(Type type, JsonValue::Reader value, SetFn setFn, DecodeArrayFn decodeArrayFn,
......@@ -436,7 +411,7 @@ void decodeField(Type type, JsonValue::Reader value, SetFn setFn, DecodeArrayFn
setFn(value.getNumber());
break;
case JsonValue::STRING:
setFn(parseInt64(value.getString()));
setFn(value.getString().parseAs<int64_t>());
break;
default:
KJ_FAIL_REQUIRE("Expected integer value");
......@@ -452,7 +427,7 @@ void decodeField(Type type, JsonValue::Reader value, SetFn setFn, DecodeArrayFn
setFn(value.getNumber());
break;
case JsonValue::STRING:
setFn(parseUInt64(value.getString()));
setFn(value.getString().parseAs<uint64_t>());
break;
default:
KJ_FAIL_REQUIRE("Expected integer value");
......@@ -468,7 +443,7 @@ void decodeField(Type type, JsonValue::Reader value, SetFn setFn, DecodeArrayFn
setFn(value.getNumber());
break;
case JsonValue::STRING:
setFn(parseFloat64(value.getString()));
setFn(value.getString().parseAs<double>());
break;
default:
KJ_FAIL_REQUIRE("Expected float value");
......
......@@ -67,6 +67,90 @@ TEST(String, StartsEndsWith) {
EXPECT_TRUE(StringPtr("foobar").endsWith(""));
}
TEST(String, parseAs) {
EXPECT_EQ(StringPtr("0").parseAs<double>(), 0.0);
EXPECT_EQ(StringPtr("0.0").parseAs<double>(), 0.0);
EXPECT_EQ(StringPtr("1").parseAs<double>(), 1.0);
EXPECT_EQ(StringPtr("1.0").parseAs<double>(), 1.0);
EXPECT_EQ(StringPtr("1e100").parseAs<double>(), 1e100);
EXPECT_EQ(StringPtr("inf").parseAs<double>(), inf());
EXPECT_EQ(StringPtr("infinity").parseAs<double>(), inf());
EXPECT_EQ(StringPtr("INF").parseAs<double>(), inf());
EXPECT_EQ(StringPtr("INFINITY").parseAs<double>(), inf());
EXPECT_EQ(StringPtr("1e100000").parseAs<double>(), inf());
EXPECT_EQ(StringPtr("-inf").parseAs<double>(), -inf());
EXPECT_EQ(StringPtr("-infinity").parseAs<double>(), -inf());
EXPECT_EQ(StringPtr("-INF").parseAs<double>(), -inf());
EXPECT_EQ(StringPtr("-INFINITY").parseAs<double>(), -inf());
EXPECT_EQ(StringPtr("-1e100000").parseAs<double>(), -inf());
EXPECT_TRUE(isNaN(StringPtr("nan").parseAs<double>()));
EXPECT_TRUE(isNaN(StringPtr("NAN").parseAs<double>()));
EXPECT_TRUE(isNaN(StringPtr("NaN").parseAs<double>()));
KJ_EXPECT_THROW_MESSAGE("not contain valid", StringPtr("").parseAs<double>());
KJ_EXPECT_THROW_MESSAGE("not contain valid", StringPtr("a").parseAs<double>());
KJ_EXPECT_THROW_MESSAGE("not contain valid", StringPtr("1a").parseAs<double>());
KJ_EXPECT_THROW_MESSAGE("not contain valid", StringPtr("+-1").parseAs<double>());
EXPECT_EQ(StringPtr("1").parseAs<float>(), 1.0);
EXPECT_EQ(StringPtr("1").parseAs<int64_t>(), 1);
EXPECT_EQ(StringPtr("9223372036854775807").parseAs<int64_t>(), 9223372036854775807LL);
EXPECT_EQ(StringPtr("-9223372036854775808").parseAs<int64_t>(), -9223372036854775808ULL);
KJ_EXPECT_THROW_MESSAGE("out-of-range", StringPtr("9223372036854775808").parseAs<int64_t>());
KJ_EXPECT_THROW_MESSAGE("out-of-range", StringPtr("-9223372036854775809").parseAs<int64_t>());
KJ_EXPECT_THROW_MESSAGE("not contain valid", StringPtr("").parseAs<int64_t>());
KJ_EXPECT_THROW_MESSAGE("not contain valid", StringPtr("a").parseAs<int64_t>());
KJ_EXPECT_THROW_MESSAGE("not contain valid", StringPtr("1a").parseAs<int64_t>());
KJ_EXPECT_THROW_MESSAGE("not contain valid", StringPtr("+-1").parseAs<int64_t>());
EXPECT_EQ(StringPtr("010").parseAs<int64_t>(), 10);
EXPECT_EQ(StringPtr("0010").parseAs<int64_t>(), 10);
EXPECT_EQ(StringPtr("0x10").parseAs<int64_t>(), 16);
EXPECT_EQ(StringPtr("0X10").parseAs<int64_t>(), 16);
EXPECT_EQ(StringPtr("-010").parseAs<int64_t>(), -10);
EXPECT_EQ(StringPtr("-0x10").parseAs<int64_t>(), -16);
EXPECT_EQ(StringPtr("-0X10").parseAs<int64_t>(), -16);
EXPECT_EQ(StringPtr("1").parseAs<uint64_t>(), 1);
EXPECT_EQ(StringPtr("0").parseAs<uint64_t>(), 0);
EXPECT_EQ(StringPtr("18446744073709551615").parseAs<uint64_t>(), 18446744073709551615ULL);
KJ_EXPECT_THROW_MESSAGE("out-of-range", StringPtr("-1").parseAs<uint64_t>());
KJ_EXPECT_THROW_MESSAGE("out-of-range", StringPtr("18446744073709551616").parseAs<uint64_t>());
KJ_EXPECT_THROW_MESSAGE("not contain valid", StringPtr("").parseAs<uint64_t>());
KJ_EXPECT_THROW_MESSAGE("not contain valid", StringPtr("a").parseAs<uint64_t>());
KJ_EXPECT_THROW_MESSAGE("not contain valid", StringPtr("1a").parseAs<uint64_t>());
KJ_EXPECT_THROW_MESSAGE("not contain valid", StringPtr("+-1").parseAs<uint64_t>());
EXPECT_EQ(StringPtr("1").parseAs<int32_t>(), 1);
EXPECT_EQ(StringPtr("2147483647").parseAs<int32_t>(), 2147483647);
EXPECT_EQ(StringPtr("-2147483648").parseAs<int32_t>(), -2147483648);
KJ_EXPECT_THROW_MESSAGE("out-of-range", StringPtr("2147483648").parseAs<int32_t>());
KJ_EXPECT_THROW_MESSAGE("out-of-range", StringPtr("-2147483649").parseAs<int32_t>());
EXPECT_EQ(StringPtr("1").parseAs<uint32_t>(), 1);
EXPECT_EQ(StringPtr("0").parseAs<uint32_t>(), 0U);
EXPECT_EQ(StringPtr("4294967295").parseAs<uint32_t>(), 4294967295U);
KJ_EXPECT_THROW_MESSAGE("out-of-range", StringPtr("-1").parseAs<uint32_t>());
KJ_EXPECT_THROW_MESSAGE("out-of-range", StringPtr("4294967296").parseAs<uint32_t>());
EXPECT_EQ(StringPtr("1").parseAs<int16_t>(), 1);
EXPECT_EQ(StringPtr("1").parseAs<uint16_t>(), 1);
EXPECT_EQ(StringPtr("1").parseAs<int8_t>(), 1);
EXPECT_EQ(StringPtr("1").parseAs<uint8_t>(), 1);
EXPECT_EQ(StringPtr("1").parseAs<char>(), 1);
EXPECT_EQ(StringPtr("1").parseAs<signed char>(), 1);
EXPECT_EQ(StringPtr("1").parseAs<unsigned char>(), 1);
EXPECT_EQ(StringPtr("1").parseAs<short>(), 1);
EXPECT_EQ(StringPtr("1").parseAs<unsigned short>(), 1);
EXPECT_EQ(StringPtr("1").parseAs<int>(), 1);
EXPECT_EQ(StringPtr("1").parseAs<unsigned>(), 1);
EXPECT_EQ(StringPtr("1").parseAs<long>(), 1);
EXPECT_EQ(StringPtr("1").parseAs<unsigned long>(), 1);
EXPECT_EQ(StringPtr("1").parseAs<long long>(), 1);
EXPECT_EQ(StringPtr("1").parseAs<unsigned long long>(), 1);
EXPECT_EQ(heapString("1").parseAs<int>(), 1);
}
#if KJ_COMPILER_SUPPORTS_STL_STRING_INTEROP
TEST(String, StlInterop) {
std::string foo = "foo";
......
......@@ -33,6 +33,75 @@ namespace kj {
// Warns that sprintf() is buffer-overrunny. We know that, it's cool.
#endif
namespace {
bool isHex(const char *s) {
if (*s == '-') s++;
return s[0] == '0' && (s[1] == 'x' || s[1] == 'X');
}
long long parseSigned(const StringPtr& s, long long min, long long max) {
KJ_REQUIRE(s != nullptr, "String does not contain valid number", s);
char *endPtr;
errno = 0;
auto value = strtoll(s.begin(), &endPtr, isHex(s.cStr()) ? 16 : 10);
KJ_REQUIRE(endPtr == s.end(), "String does not contain valid number", s);
KJ_REQUIRE(errno != ERANGE, "Value out-of-range", s);
KJ_REQUIRE(value >= min && value <= max, "Value out-of-range", value, min, max);
return value;
}
unsigned long long parseUnsigned(const StringPtr& s, unsigned long long max) {
KJ_REQUIRE(s != nullptr, "String does not contain valid number", s);
char *endPtr;
errno = 0;
auto value = strtoull(s.begin(), &endPtr, isHex(s.cStr()) ? 16 : 10);
KJ_REQUIRE(endPtr == s.end(), "String does not contain valid number", s);
KJ_REQUIRE(errno != ERANGE, "Value out-of-range", s);
KJ_REQUIRE(value <= max, "Value out-of-range", value, max);
KJ_REQUIRE(s[0] != '-', "Value out-of-range", s); //strtoull("-1") does not fail with ERANGE
return value;
}
template <typename T>
T parseInteger(const StringPtr& s) {
if (static_cast<T>(minValue) < 0) {
long long min = static_cast<T>(minValue);
long long max = static_cast<T>(maxValue);
return static_cast<T>(parseSigned(s, min, max));
} else {
unsigned long long max = static_cast<T>(maxValue);
return static_cast<T>(parseUnsigned(s, max));
}
}
double parseDouble(const StringPtr& s) {
KJ_REQUIRE(s != nullptr, "String does not contain valid number", s);
char *endPtr;
errno = 0;
auto value = strtod(s.begin(), &endPtr);
KJ_REQUIRE(endPtr == s.end(), "String does not contain valid floating number", s);
return value;
}
} // namespace
#define PARSE_AS_INTEGER(T) \
template <> T StringPtr::parseAs<T>() const { return parseInteger<T>(*this); }
PARSE_AS_INTEGER(char);
PARSE_AS_INTEGER(signed char);
PARSE_AS_INTEGER(unsigned char);
PARSE_AS_INTEGER(short);
PARSE_AS_INTEGER(unsigned short);
PARSE_AS_INTEGER(int);
PARSE_AS_INTEGER(unsigned int);
PARSE_AS_INTEGER(long);
PARSE_AS_INTEGER(unsigned long);
PARSE_AS_INTEGER(long long);
PARSE_AS_INTEGER(unsigned long long);
#undef PARSE_AS_INTEGER
template <> double StringPtr::parseAs<double>() const { return parseDouble(*this); }
template <> float StringPtr::parseAs<float>() const { return parseDouble(*this); }
String heapString(size_t size) {
char* buffer = _::HeapArrayDisposer::allocate<char>(size + 1);
buffer[size] = '\0';
......
......@@ -112,6 +112,14 @@ public:
inline Maybe<size_t> findFirst(char c) const;
inline Maybe<size_t> findLast(char c) const;
template <typename T>
T parseAs() const;
// Parse string as template number type.
// Integer numbers prefixed by "0x" and "0X" are parsed in base 16 (like strtoi with base 0).
// Integer numbers prefixed by "0" are parsed in base 10 (unlike strtoi with base 0).
// Overflowed integer numbers throw exception.
// Overflowed floating numbers return inf.
private:
inline StringPtr(ArrayPtr<const char> content): content(content) {}
......@@ -121,6 +129,20 @@ private:
inline bool operator==(const char* a, const StringPtr& b) { return b == a; }
inline bool operator!=(const char* a, const StringPtr& b) { return b != a; }
template <> char StringPtr::parseAs<char>() const;
template <> signed char StringPtr::parseAs<signed char>() const;
template <> unsigned char StringPtr::parseAs<unsigned char>() const;
template <> short StringPtr::parseAs<short>() const;
template <> unsigned short StringPtr::parseAs<unsigned short>() const;
template <> int StringPtr::parseAs<int>() const;
template <> unsigned StringPtr::parseAs<unsigned>() const;
template <> long StringPtr::parseAs<long>() const;
template <> unsigned long StringPtr::parseAs<unsigned long>() const;
template <> long long StringPtr::parseAs<long long>() const;
template <> unsigned long long StringPtr::parseAs<unsigned long long>() const;
template <> float StringPtr::parseAs<float>() const;
template <> double StringPtr::parseAs<double>() const;
// =======================================================================================
// String -- A NUL-terminated Array<char> containing UTF-8 text.
//
......@@ -182,6 +204,10 @@ public:
inline Maybe<size_t> findFirst(char c) const { return StringPtr(*this).findFirst(c); }
inline Maybe<size_t> findLast(char c) const { return StringPtr(*this).findLast(c); }
template <typename T>
T parseAs() const { return StringPtr(*this).parseAs<T>(); }
// Parse as number
private:
Array<char> content;
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment