Added custom implementation of strtol() (issue #465)

`char` is now treated as an integral type (issue #337, #370)
This commit is contained in:
Benoit Blanchon
2017-03-25 21:55:13 +01:00
parent c4567bac18
commit 185eccf6f5
14 changed files with 261 additions and 182 deletions

View File

@ -5,6 +5,8 @@ HEAD
---- ----
* Added custom implementation of `strtod()` (issue #453) * Added custom implementation of `strtod()` (issue #453)
* Added custom implementation of `strtol()` (issue #465)
* `char` is now treated as an integral type (issue #337, #370)
v5.8.3 v5.8.3
------ ------

View File

@ -70,13 +70,15 @@ class JsonVariant : public JsonVariantBase<JsonVariant> {
} }
// Create a JsonVariant containing an integer value. // Create a JsonVariant containing an integer value.
// JsonVariant(char)
// JsonVariant(signed short) // JsonVariant(signed short)
// JsonVariant(signed int) // JsonVariant(signed int)
// JsonVariant(signed long) // JsonVariant(signed long)
// JsonVariant(signed char)
template <typename T> template <typename T>
JsonVariant(T value, JsonVariant(T value, typename TypeTraits::EnableIf<
typename TypeTraits::EnableIf< TypeTraits::IsSignedIntegral<T>::value ||
TypeTraits::IsSignedIntegral<T>::value>::type * = 0) { TypeTraits::IsSame<T, char>::value>::type * = 0) {
using namespace Internals; using namespace Internals;
if (value >= 0) { if (value >= 0) {
_type = JSON_POSITIVE_INTEGER; _type = JSON_POSITIVE_INTEGER;
@ -129,24 +131,26 @@ class JsonVariant : public JsonVariantBase<JsonVariant> {
// Get the variant as the specified type. // Get the variant as the specified type.
// //
// short as<signed short>() const; // char as<char>() const;
// int as<signed int>() const; // signed char as<signed char>() const;
// long as<signed long>() const; // signed short as<signed short>() const;
// signed int as<signed int>() const;
// signed long as<signed long>() const;
// unsigned char as<unsigned char>() const;
// unsigned short as<unsigned short>() const;
// unsigned int as<unsigned int>() const;
// unsigned long as<unsigned long>() const;
template <typename T> template <typename T>
const typename TypeTraits::EnableIf<TypeTraits::IsSignedIntegral<T>::value, const typename TypeTraits::EnableIf<TypeTraits::IsIntegral<T>::value, T>::type
T>::type
as() const { as() const {
return static_cast<T>(variantAsInteger()); return variantAsInteger<T>();
} }
// // bool as<bool>() const
// short as<unsigned short>() const;
// int as<unsigned int>() const;
// long as<unsigned long>() const;
template <typename T> template <typename T>
const typename TypeTraits::EnableIf<TypeTraits::IsUnsignedIntegral<T>::value, const typename TypeTraits::EnableIf<TypeTraits::IsSame<T, bool>::value,
T>::type T>::type
as() const { as() const {
return static_cast<T>(asUnsignedInteger()); return variantAsInteger<int>() != 0;
} }
// //
// double as<double>() const; // double as<double>() const;
@ -155,7 +159,7 @@ class JsonVariant : public JsonVariantBase<JsonVariant> {
const typename TypeTraits::EnableIf<TypeTraits::IsFloatingPoint<T>::value, const typename TypeTraits::EnableIf<TypeTraits::IsFloatingPoint<T>::value,
T>::type T>::type
as() const { as() const {
return static_cast<T>(variantAsFloat()); return variantAsFloat<T>();
} }
// //
// const char* as<const char*>() const; // const char* as<const char*>() const;
@ -180,14 +184,6 @@ class JsonVariant : public JsonVariantBase<JsonVariant> {
return s; return s;
} }
// //
// const bool as<bool>() const
template <typename T>
const typename TypeTraits::EnableIf<TypeTraits::IsSame<T, bool>::value,
T>::type
as() const {
return variantAsInteger() != 0;
}
//
// JsonArray& as<JsonArray> const; // JsonArray& as<JsonArray> const;
// JsonArray& as<JsonArray&> const; // JsonArray& as<JsonArray&> const;
template <typename T> template <typename T>
@ -242,32 +238,35 @@ class JsonVariant : public JsonVariantBase<JsonVariant> {
// Tells weither the variant has the specified type. // Tells weither the variant has the specified type.
// Returns true if the variant has type type T, false otherwise. // Returns true if the variant has type type T, false otherwise.
// //
// short as<short>() const; // bool is<char>() const;
// int as<int>() const; // bool is<signed char>() const;
// long as<long>() const; // bool is<signed short>() const;
// bool is<signed int>() const;
// bool is<signed long>() const;
// bool is<unsigned char>() const;
// bool is<unsigned short>() const;
// bool is<unsigned int>() const;
// bool is<unsigned long>() const;
template <typename T> template <typename T>
const typename TypeTraits::EnableIf<TypeTraits::IsIntegral<T>::value && typename TypeTraits::EnableIf<TypeTraits::IsIntegral<T>::value, bool>::type
!TypeTraits::IsSame<T, bool>::value,
bool>::type
is() const { is() const {
return isInteger(); return variantIsInteger();
} }
// //
// double is<double>() const; // bool is<double>() const;
// float is<float>() const; // bool is<float>() const;
template <typename T> template <typename T>
const typename TypeTraits::EnableIf<TypeTraits::IsFloatingPoint<T>::value, typename TypeTraits::EnableIf<TypeTraits::IsFloatingPoint<T>::value,
bool>::type bool>::type
is() const { is() const {
return isFloat(); return variantIsFloat();
} }
// //
// const bool is<bool>() const // bool is<bool>() const
template <typename T> template <typename T>
const typename TypeTraits::EnableIf<TypeTraits::IsSame<T, bool>::value, typename TypeTraits::EnableIf<TypeTraits::IsSame<T, bool>::value, bool>::type
bool>::type
is() const { is() const {
return isBoolean(); return variantIsBoolean();
} }
// //
// bool is<const char*>() const; // bool is<const char*>() const;
@ -277,7 +276,7 @@ class JsonVariant : public JsonVariantBase<JsonVariant> {
TypeTraits::IsSame<T, char *>::value, TypeTraits::IsSame<T, char *>::value,
bool>::type bool>::type
is() const { is() const {
return isString(); return variantIsString();
} }
// //
// bool is<JsonArray> const; // bool is<JsonArray> const;
@ -291,7 +290,7 @@ class JsonVariant : public JsonVariantBase<JsonVariant> {
JsonArray>::value, JsonArray>::value,
bool>::type bool>::type
is() const { is() const {
return isArray(); return variantIsArray();
} }
// //
// bool is<JsonObject> const; // bool is<JsonObject> const;
@ -305,7 +304,7 @@ class JsonVariant : public JsonVariantBase<JsonVariant> {
JsonObject>::value, JsonObject>::value,
bool>::type bool>::type
is() const { is() const {
return isObject(); return variantIsObject();
} }
// Returns true if the variant has a value // Returns true if the variant has a value
@ -314,27 +313,23 @@ class JsonVariant : public JsonVariantBase<JsonVariant> {
} }
private: private:
// It's not allowed to store a char
template <typename T>
JsonVariant(T value, typename TypeTraits::EnableIf<
TypeTraits::IsSame<T, char>::value>::type * = 0);
JsonArray &variantAsArray() const; JsonArray &variantAsArray() const;
JsonObject &variantAsObject() const; JsonObject &variantAsObject() const;
const char *variantAsString() const; const char *variantAsString() const;
Internals::JsonFloat variantAsFloat() const; template <typename T>
Internals::JsonInteger variantAsInteger() const; T variantAsFloat() const;
Internals::JsonUInt asUnsignedInteger() const; template <typename T>
bool isBoolean() const; T variantAsInteger() const;
bool isFloat() const; bool variantIsBoolean() const;
bool isInteger() const; bool variantIsFloat() const;
bool isArray() const { bool variantIsInteger() const;
bool variantIsArray() const {
return _type == Internals::JSON_ARRAY; return _type == Internals::JSON_ARRAY;
} }
bool isObject() const { bool variantIsObject() const {
return _type == Internals::JSON_OBJECT; return _type == Internals::JSON_OBJECT;
} }
bool isString() const { bool variantIsString() const {
return _type == Internals::JSON_STRING || return _type == Internals::JSON_STRING ||
(_type == Internals::JSON_UNPARSED && _content.asString && (_type == Internals::JSON_UNPARSED && _content.asString &&
!strcmp("null", _content.asString)); !strcmp("null", _content.asString));

View File

@ -12,11 +12,10 @@
#include "JsonObject.hpp" #include "JsonObject.hpp"
#include "JsonVariant.hpp" #include "JsonVariant.hpp"
#include "Polyfills/isFloat.hpp" #include "Polyfills/isFloat.hpp"
#include "Polyfills/isInteger.hpp"
#include "Polyfills/parseFloat.hpp" #include "Polyfills/parseFloat.hpp"
#include "Polyfills/parseInteger.hpp" #include "Polyfills/parseInteger.hpp"
#include <errno.h> // for errno
#include <stdlib.h> // for strtol, strtod
#include <string.h> // for strcmp #include <string.h> // for strcmp
namespace ArduinoJson { namespace ArduinoJson {
@ -49,42 +48,24 @@ inline JsonObject &JsonVariant::variantAsObject() const {
return JsonObject::invalid(); return JsonObject::invalid();
} }
inline Internals::JsonInteger JsonVariant::variantAsInteger() const { template <typename T>
inline T JsonVariant::variantAsInteger() const {
using namespace Internals; using namespace Internals;
switch (_type) { switch (_type) {
case JSON_UNDEFINED: case JSON_UNDEFINED:
return 0; return 0;
case JSON_POSITIVE_INTEGER: case JSON_POSITIVE_INTEGER:
case JSON_BOOLEAN: case JSON_BOOLEAN:
return _content.asInteger; return static_cast<T>(_content.asInteger);
case JSON_NEGATIVE_INTEGER: case JSON_NEGATIVE_INTEGER:
return -static_cast<JsonInteger>(_content.asInteger); return static_cast<T>(_content.asInteger * -1);
case JSON_STRING: case JSON_STRING:
case JSON_UNPARSED: case JSON_UNPARSED:
if (!_content.asString) return 0; if (!_content.asString) return 0;
if (!strcmp("true", _content.asString)) return 1; if (!strcmp("true", _content.asString)) return 1;
return Polyfills::parseInteger<JsonInteger>(_content.asString); return Polyfills::parseInteger<T>(_content.asString);
default: default:
return static_cast<JsonInteger>(_content.asFloat); return static_cast<T>(_content.asFloat);
}
}
inline Internals::JsonUInt JsonVariant::asUnsignedInteger() const {
using namespace Internals;
switch (_type) {
case JSON_UNDEFINED:
return 0;
case JSON_POSITIVE_INTEGER:
case JSON_BOOLEAN:
case JSON_NEGATIVE_INTEGER:
return _content.asInteger;
case JSON_STRING:
case JSON_UNPARSED:
if (!_content.asString) return 0;
if (!strcmp("true", _content.asString)) return 1;
return Polyfills::parseInteger<JsonUInt>(_content.asString);
default:
return static_cast<JsonUInt>(_content.asFloat);
} }
} }
@ -97,27 +78,26 @@ inline const char *JsonVariant::variantAsString() const {
return NULL; return NULL;
} }
inline Internals::JsonFloat JsonVariant::variantAsFloat() const { template <typename T>
inline T JsonVariant::variantAsFloat() const {
using namespace Internals; using namespace Internals;
switch (_type) { switch (_type) {
case JSON_UNDEFINED: case JSON_UNDEFINED:
return 0; return 0;
case JSON_POSITIVE_INTEGER: case JSON_POSITIVE_INTEGER:
case JSON_BOOLEAN: case JSON_BOOLEAN:
return static_cast<JsonFloat>(_content.asInteger); return static_cast<T>(_content.asInteger);
case JSON_NEGATIVE_INTEGER: case JSON_NEGATIVE_INTEGER:
return -static_cast<JsonFloat>(_content.asInteger); return -static_cast<T>(_content.asInteger);
case JSON_STRING: case JSON_STRING:
case JSON_UNPARSED: case JSON_UNPARSED:
return _content.asString return Polyfills::parseFloat<T>(_content.asString);
? Polyfills::parseFloat<JsonFloat>(_content.asString)
: 0;
default: default:
return _content.asFloat; return static_cast<T>(_content.asFloat);
} }
} }
inline bool JsonVariant::isBoolean() const { inline bool JsonVariant::variantIsBoolean() const {
using namespace Internals; using namespace Internals;
if (_type == JSON_BOOLEAN) return true; if (_type == JSON_BOOLEAN) return true;
@ -127,27 +107,18 @@ inline bool JsonVariant::isBoolean() const {
!strcmp(_content.asString, "false"); !strcmp(_content.asString, "false");
} }
inline bool JsonVariant::isInteger() const { inline bool JsonVariant::variantIsInteger() const {
using namespace Internals; using namespace Internals;
if (_type == JSON_POSITIVE_INTEGER || _type == JSON_NEGATIVE_INTEGER)
return true;
if (_type != JSON_UNPARSED || _content.asString == NULL) return false; return _type == JSON_POSITIVE_INTEGER || _type == JSON_NEGATIVE_INTEGER ||
(_type == JSON_UNPARSED && Polyfills::isInteger(_content.asString));
char *end;
errno = 0;
strtol(_content.asString, &end, 10);
return *end == '\0' && errno == 0;
} }
inline bool JsonVariant::isFloat() const { inline bool JsonVariant::variantIsFloat() const {
using namespace Internals; using namespace Internals;
if (_type >= JSON_FLOAT_0_DECIMALS) return true;
if (_type != JSON_UNPARSED || _content.asString == NULL) return false; return _type >= JSON_FLOAT_0_DECIMALS ||
(_type == JSON_UNPARSED && Polyfills::isFloat(_content.asString));
return Polyfills::isFloat(_content.asString);
} }
#if ARDUINOJSON_ENABLE_STD_STREAM #if ARDUINOJSON_ENABLE_STD_STREAM

View File

@ -8,12 +8,13 @@
#pragma once #pragma once
#include "./ctype.hpp" #include "./ctype.hpp"
#include "./math.hpp"
namespace ArduinoJson { namespace ArduinoJson {
namespace Polyfills { namespace Polyfills {
inline bool isFloat(const char* s) { inline bool isFloat(const char* s) {
if (!s) return false;
if (!strcmp(s, "NaN")) return true; if (!strcmp(s, "NaN")) return true;
if (issign(*s)) s++; if (issign(*s)) s++;
if (!strcmp(s, "Infinity")) return true; if (!strcmp(s, "Infinity")) return true;

View File

@ -0,0 +1,22 @@
// Copyright Benoit Blanchon 2014-2017
// MIT License
//
// Arduino JSON library
// https://github.com/bblanchon/ArduinoJson
// If you like this project, please add a star!
#pragma once
#include "./ctype.hpp"
namespace ArduinoJson {
namespace Polyfills {
inline bool isInteger(const char* s) {
if (!s) return false;
if (issign(*s)) s++;
while (isdigit(*s)) s++;
return *s == '\0';
}
}
}

View File

@ -20,6 +20,8 @@ inline T parseFloat(const char* s) {
typedef typename traits::mantissa_type mantissa_t; typedef typename traits::mantissa_type mantissa_t;
typedef typename traits::exponent_type exponent_t; typedef typename traits::exponent_type exponent_t;
if (!s) return 0;
bool negative_result = false; bool negative_result = false;
switch (*s) { switch (*s) {
case '-': case '-':

View File

@ -9,48 +9,32 @@
#include <stdlib.h> #include <stdlib.h>
#include "../Configuration.hpp"
#include "./ctype.hpp"
namespace ArduinoJson { namespace ArduinoJson {
namespace Polyfills { namespace Polyfills {
template <typename T> template <typename T>
T parseInteger(const char *s); T parseInteger(const char *s) {
if (!s) return 0;
template <> T result = 0;
inline long parseInteger<long>(const char *s) { bool negative_result = false;
return ::strtol(s, NULL, 10);
switch (*s) {
case '-':
negative_result = true;
case '+':
s++;
break;
} }
template <> while (isdigit(*s)) {
inline unsigned long parseInteger<unsigned long>(const char *s) { result = static_cast<T>(result * 10 + (*s - '0'));
return ::strtoul(s, NULL, 10); s++;
} }
template <> return negative_result ? static_cast<T>(result*-1) : result;
inline int parseInteger<int>(const char *s) { }
return ::atoi(s);
}
#if ARDUINOJSON_USE_LONG_LONG
template <>
inline long long parseInteger<long long>(const char *s) {
return ::strtoll(s, NULL, 10);
}
template <>
inline unsigned long long parseInteger<unsigned long long>(const char *s) {
return ::strtoull(s, NULL, 10);
}
#endif
#if ARDUINOJSON_USE_INT64
template <>
inline __int64 parseInteger<__int64>(const char *s) {
return ::_strtoi64(s, NULL, 10);
}
template <>
inline unsigned __int64 parseInteger<unsigned __int64>(const char *s) {
return ::_strtoui64(s, NULL, 10);
}
#endif
} }
} }

View File

@ -19,8 +19,8 @@ template <typename T>
struct IsIntegral { struct IsIntegral {
static const bool value = TypeTraits::IsSignedIntegral<T>::value || static const bool value = TypeTraits::IsSignedIntegral<T>::value ||
TypeTraits::IsUnsignedIntegral<T>::value || TypeTraits::IsUnsignedIntegral<T>::value ||
TypeTraits::IsSame<T, char>::value || TypeTraits::IsSame<T, char>::value;
TypeTraits::IsSame<T, bool>::value; // CAUTION: differs from std::is_integral as it doesn't include bool
}; };
template <typename T> template <typename T>

View File

@ -1,37 +0,0 @@
// Copyright Benoit Blanchon 2014-2017
// MIT License
//
// Arduino JSON library
// https://github.com/bblanchon/ArduinoJson
// If you like this project, please add a star!
#include <gtest/gtest.h>
#include <limits.h> // for LONG_MAX
#define ARDUINOJSON_USE_LONG_LONG 0
#define ARDUINOJSON_USE_INT64 0
#include <ArduinoJson.h>
#define SUITE Issue90
static const char* superLong =
"12345678901234567890123456789012345678901234567890123456789012345678901234"
"5678901234567890123456789012345678901234567890123456789012345678901234567";
static const JsonVariant variant = RawJson(superLong);
TEST(SUITE, IsNotALong) {
ASSERT_FALSE(variant.is<long>());
}
TEST(SUITE, AsLong) {
ASSERT_EQ(LONG_MAX, variant.as<long>());
}
TEST(SUITE, IsAString) {
ASSERT_FALSE(variant.is<const char*>());
}
TEST(SUITE, AsString) {
ASSERT_STREQ(superLong, variant.as<const char*>());
}

View File

@ -63,6 +63,9 @@ TEST_F(JsonVariant_Storage_Tests, Double) {
TEST_F(JsonVariant_Storage_Tests, Float) { TEST_F(JsonVariant_Storage_Tests, Float) {
testNumericType<float>(); testNumericType<float>();
} }
TEST_F(JsonVariant_Storage_Tests, Char) {
testNumericType<char>();
}
TEST_F(JsonVariant_Storage_Tests, SChar) { TEST_F(JsonVariant_Storage_Tests, SChar) {
testNumericType<signed char>(); testNumericType<signed char>();
} }

View File

@ -18,6 +18,10 @@ struct Polyfills_IsFloat_Tests : testing::Test {
}; };
#define TEST_(X) TEST_F(Polyfills_IsFloat_Tests, X) #define TEST_(X) TEST_F(Polyfills_IsFloat_Tests, X)
TEST_(Null) {
check(false, NULL);
}
TEST_(NoExponent) { TEST_(NoExponent) {
check(true, "3.14"); check(true, "3.14");
check(true, "-3.14"); check(true, "-3.14");

View File

@ -0,0 +1,45 @@
// Copyright Benoit Blanchon 2014-2017
// MIT License
//
// Arduino JSON library
// https://github.com/bblanchon/ArduinoJson
// If you like this project, please add a star!
#include <gtest/gtest.h>
#include <ArduinoJson/Polyfills/isInteger.hpp>
using namespace ArduinoJson::Polyfills;
struct Polyfills_IsInteger_Tests : testing::Test {
void check(bool expected, const char* input) {
bool actual = isInteger(input);
EXPECT_EQ(expected, actual) << input;
}
};
#define TEST_(X) TEST_F(Polyfills_IsInteger_Tests, X)
TEST_(Null) {
check(false, NULL);
}
TEST_(FloatNotInteger) {
check(false, "3.14");
check(false, "-3.14");
check(false, "+3.14");
}
TEST_(Spaces) {
check(false, "42 ");
check(false, " 42");
}
TEST_(Valid) {
check(true, "42");
check(true, "-42");
check(true, "+42");
}
TEST_(ExtraSign) {
check(false, "--42");
check(false, "++42");
}

View File

@ -54,6 +54,14 @@ struct Polyfills_ParseFloat_Double_Tests : testing::Test {
}; };
#define TEST_DOUBLE(X) TEST_F(Polyfills_ParseFloat_Double_Tests, X) #define TEST_DOUBLE(X) TEST_F(Polyfills_ParseFloat_Double_Tests, X)
TEST_DOUBLE(Null) {
check(NULL, 0);
}
TEST_FLOAT(Null) {
check(NULL, 0);
}
TEST_DOUBLE(Short_NoExponent) { TEST_DOUBLE(Short_NoExponent) {
check("3.14", 3.14); check("3.14", 3.14);
check("-3.14", -3.14); check("-3.14", -3.14);

View File

@ -0,0 +1,79 @@
// Copyright Benoit Blanchon 2014-2017
// MIT License
//
// Arduino JSON library
// https://github.com/bblanchon/ArduinoJson
// If you like this project, please add a star!
#include <gtest/gtest.h>
#include <stdint.h>
#include <ArduinoJson/Polyfills/parseInteger.hpp>
using namespace ArduinoJson::Polyfills;
struct Polyfills_ParseInteger_Tests : testing::Test {
template <typename T>
void check(const char* input, T expected) {
T actual = parseInteger<T>(input);
EXPECT_EQ(expected, actual) << input;
}
};
#define TEST_(X) TEST_F(Polyfills_ParseInteger_Tests, X)
TEST_(int8_t) {
check<int8_t>("-128", -128);
check<int8_t>("127", 127);
check<int8_t>("+127", 127);
check<int8_t>("3.14", 3);
// check<int8_t>(" 42", 0);
check<int8_t>("x42", 0);
check<int8_t>("128", -128);
check<int8_t>("-129", 127);
check<int8_t>(NULL, 0);
}
TEST_(int16_t) {
check<int16_t>("-32768", -32768);
check<int16_t>("32767", 32767);
check<int16_t>("+32767", 32767);
check<int16_t>("3.14", 3);
// check<int16_t>(" 42", 0);
check<int16_t>("x42", 0);
check<int16_t>("-32769", 32767);
check<int16_t>("32768", -32768);
check<int16_t>(NULL, 0);
}
TEST_(int32_t) {
check<int32_t>("-2147483648", (-2147483647 - 1));
check<int32_t>("2147483647", 2147483647);
check<int32_t>("+2147483647", 2147483647);
check<int32_t>("3.14", 3);
// check<int32_t>(" 42", 0);
check<int32_t>("x42", 0);
check<int32_t>("-2147483649", 2147483647);
check<int32_t>("2147483648", (-2147483647 - 1));
}
TEST_(uint8_t) {
check<uint8_t>("0", 0);
check<uint8_t>("255", 255);
check<uint8_t>("+255", 255);
check<uint8_t>("3.14", 3);
// check<uint8_t>(" 42", 0);
check<uint8_t>("x42", 0);
check<uint8_t>("-1", 255);
check<uint8_t>("256", 0);
}
TEST_(uint16_t) {
check<uint16_t>("0", 0);
check<uint16_t>("65535", 65535);
check<uint16_t>("+65535", 65535);
check<uint16_t>("3.14", 3);
// check<uint16_t>(" 42", 0);
check<uint16_t>("x42", 0);
check<uint16_t>("-1", 65535);
check<uint16_t>("65536", 0);
}