From 185eccf6f53ec18c83257c90eb3517ece1c82b35 Mon Sep 17 00:00:00 2001 From: Benoit Blanchon Date: Sat, 25 Mar 2017 21:55:13 +0100 Subject: [PATCH] Added custom implementation of `strtol()` (issue #465) `char` is now treated as an integral type (issue #337, #370) --- CHANGELOG.md | 2 + include/ArduinoJson/JsonVariant.hpp | 109 +++++++++--------- include/ArduinoJson/JsonVariantImpl.hpp | 69 ++++------- include/ArduinoJson/Polyfills/isFloat.hpp | 3 +- include/ArduinoJson/Polyfills/isInteger.hpp | 22 ++++ include/ArduinoJson/Polyfills/parseFloat.hpp | 2 + .../ArduinoJson/Polyfills/parseInteger.hpp | 56 ++++----- include/ArduinoJson/TypeTraits/IsIntegral.hpp | 4 +- test/Issue90.cpp | 37 ------ test/JsonVariant_Storage_Tests.cpp | 3 + test/Polyfills_IsFloat_Tests.cpp | 4 + test/Polyfills_IsInteger_Tests.cpp | 45 ++++++++ test/Polyfills_ParseFloat_Tests.cpp | 8 ++ test/Polyfills_ParseInteger_Tests.cpp | 79 +++++++++++++ 14 files changed, 261 insertions(+), 182 deletions(-) create mode 100644 include/ArduinoJson/Polyfills/isInteger.hpp delete mode 100644 test/Issue90.cpp create mode 100644 test/Polyfills_IsInteger_Tests.cpp create mode 100644 test/Polyfills_ParseInteger_Tests.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index c2c3f3e7..14e82af8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ HEAD ---- * 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 ------ diff --git a/include/ArduinoJson/JsonVariant.hpp b/include/ArduinoJson/JsonVariant.hpp index 31a3151d..01b85a73 100644 --- a/include/ArduinoJson/JsonVariant.hpp +++ b/include/ArduinoJson/JsonVariant.hpp @@ -70,13 +70,15 @@ class JsonVariant : public JsonVariantBase { } // Create a JsonVariant containing an integer value. + // JsonVariant(char) // JsonVariant(signed short) // JsonVariant(signed int) // JsonVariant(signed long) + // JsonVariant(signed char) template - JsonVariant(T value, - typename TypeTraits::EnableIf< - TypeTraits::IsSignedIntegral::value>::type * = 0) { + JsonVariant(T value, typename TypeTraits::EnableIf< + TypeTraits::IsSignedIntegral::value || + TypeTraits::IsSame::value>::type * = 0) { using namespace Internals; if (value >= 0) { _type = JSON_POSITIVE_INTEGER; @@ -129,24 +131,26 @@ class JsonVariant : public JsonVariantBase { // Get the variant as the specified type. // - // short as() const; - // int as() const; - // long as() const; + // char as() const; + // signed char as() const; + // signed short as() const; + // signed int as() const; + // signed long as() const; + // unsigned char as() const; + // unsigned short as() const; + // unsigned int as() const; + // unsigned long as() const; template - const typename TypeTraits::EnableIf::value, - T>::type + const typename TypeTraits::EnableIf::value, T>::type as() const { - return static_cast(variantAsInteger()); + return variantAsInteger(); } - // - // short as() const; - // int as() const; - // long as() const; + // bool as() const template - const typename TypeTraits::EnableIf::value, + const typename TypeTraits::EnableIf::value, T>::type as() const { - return static_cast(asUnsignedInteger()); + return variantAsInteger() != 0; } // // double as() const; @@ -155,7 +159,7 @@ class JsonVariant : public JsonVariantBase { const typename TypeTraits::EnableIf::value, T>::type as() const { - return static_cast(variantAsFloat()); + return variantAsFloat(); } // // const char* as() const; @@ -180,14 +184,6 @@ class JsonVariant : public JsonVariantBase { return s; } // - // const bool as() const - template - const typename TypeTraits::EnableIf::value, - T>::type - as() const { - return variantAsInteger() != 0; - } - // // JsonArray& as const; // JsonArray& as const; template @@ -242,32 +238,35 @@ class JsonVariant : public JsonVariantBase { // Tells weither the variant has the specified type. // Returns true if the variant has type type T, false otherwise. // - // short as() const; - // int as() const; - // long as() const; + // bool is() const; + // bool is() const; + // bool is() const; + // bool is() const; + // bool is() const; + // bool is() const; + // bool is() const; + // bool is() const; + // bool is() const; template - const typename TypeTraits::EnableIf::value && - !TypeTraits::IsSame::value, - bool>::type + typename TypeTraits::EnableIf::value, bool>::type is() const { - return isInteger(); + return variantIsInteger(); } // - // double is() const; - // float is() const; + // bool is() const; + // bool is() const; template - const typename TypeTraits::EnableIf::value, - bool>::type + typename TypeTraits::EnableIf::value, + bool>::type is() const { - return isFloat(); + return variantIsFloat(); } // - // const bool is() const + // bool is() const template - const typename TypeTraits::EnableIf::value, - bool>::type + typename TypeTraits::EnableIf::value, bool>::type is() const { - return isBoolean(); + return variantIsBoolean(); } // // bool is() const; @@ -277,7 +276,7 @@ class JsonVariant : public JsonVariantBase { TypeTraits::IsSame::value, bool>::type is() const { - return isString(); + return variantIsString(); } // // bool is const; @@ -291,7 +290,7 @@ class JsonVariant : public JsonVariantBase { JsonArray>::value, bool>::type is() const { - return isArray(); + return variantIsArray(); } // // bool is const; @@ -305,7 +304,7 @@ class JsonVariant : public JsonVariantBase { JsonObject>::value, bool>::type is() const { - return isObject(); + return variantIsObject(); } // Returns true if the variant has a value @@ -314,27 +313,23 @@ class JsonVariant : public JsonVariantBase { } private: - // It's not allowed to store a char - template - JsonVariant(T value, typename TypeTraits::EnableIf< - TypeTraits::IsSame::value>::type * = 0); - JsonArray &variantAsArray() const; JsonObject &variantAsObject() const; const char *variantAsString() const; - Internals::JsonFloat variantAsFloat() const; - Internals::JsonInteger variantAsInteger() const; - Internals::JsonUInt asUnsignedInteger() const; - bool isBoolean() const; - bool isFloat() const; - bool isInteger() const; - bool isArray() const { + template + T variantAsFloat() const; + template + T variantAsInteger() const; + bool variantIsBoolean() const; + bool variantIsFloat() const; + bool variantIsInteger() const; + bool variantIsArray() const { return _type == Internals::JSON_ARRAY; } - bool isObject() const { + bool variantIsObject() const { return _type == Internals::JSON_OBJECT; } - bool isString() const { + bool variantIsString() const { return _type == Internals::JSON_STRING || (_type == Internals::JSON_UNPARSED && _content.asString && !strcmp("null", _content.asString)); diff --git a/include/ArduinoJson/JsonVariantImpl.hpp b/include/ArduinoJson/JsonVariantImpl.hpp index ccf38dbd..d90d97e3 100644 --- a/include/ArduinoJson/JsonVariantImpl.hpp +++ b/include/ArduinoJson/JsonVariantImpl.hpp @@ -12,11 +12,10 @@ #include "JsonObject.hpp" #include "JsonVariant.hpp" #include "Polyfills/isFloat.hpp" +#include "Polyfills/isInteger.hpp" #include "Polyfills/parseFloat.hpp" #include "Polyfills/parseInteger.hpp" -#include // for errno -#include // for strtol, strtod #include // for strcmp namespace ArduinoJson { @@ -49,42 +48,24 @@ inline JsonObject &JsonVariant::variantAsObject() const { return JsonObject::invalid(); } -inline Internals::JsonInteger JsonVariant::variantAsInteger() const { +template +inline T JsonVariant::variantAsInteger() const { using namespace Internals; switch (_type) { case JSON_UNDEFINED: return 0; case JSON_POSITIVE_INTEGER: case JSON_BOOLEAN: - return _content.asInteger; + return static_cast(_content.asInteger); case JSON_NEGATIVE_INTEGER: - return -static_cast(_content.asInteger); + return static_cast(_content.asInteger * -1); case JSON_STRING: case JSON_UNPARSED: if (!_content.asString) return 0; if (!strcmp("true", _content.asString)) return 1; - return Polyfills::parseInteger(_content.asString); + return Polyfills::parseInteger(_content.asString); default: - return static_cast(_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(_content.asString); - default: - return static_cast(_content.asFloat); + return static_cast(_content.asFloat); } } @@ -97,27 +78,26 @@ inline const char *JsonVariant::variantAsString() const { return NULL; } -inline Internals::JsonFloat JsonVariant::variantAsFloat() const { +template +inline T JsonVariant::variantAsFloat() const { using namespace Internals; switch (_type) { case JSON_UNDEFINED: return 0; case JSON_POSITIVE_INTEGER: case JSON_BOOLEAN: - return static_cast(_content.asInteger); + return static_cast(_content.asInteger); case JSON_NEGATIVE_INTEGER: - return -static_cast(_content.asInteger); + return -static_cast(_content.asInteger); case JSON_STRING: case JSON_UNPARSED: - return _content.asString - ? Polyfills::parseFloat(_content.asString) - : 0; + return Polyfills::parseFloat(_content.asString); default: - return _content.asFloat; + return static_cast(_content.asFloat); } } -inline bool JsonVariant::isBoolean() const { +inline bool JsonVariant::variantIsBoolean() const { using namespace Internals; if (_type == JSON_BOOLEAN) return true; @@ -127,27 +107,18 @@ inline bool JsonVariant::isBoolean() const { !strcmp(_content.asString, "false"); } -inline bool JsonVariant::isInteger() const { +inline bool JsonVariant::variantIsInteger() const { using namespace Internals; - if (_type == JSON_POSITIVE_INTEGER || _type == JSON_NEGATIVE_INTEGER) - return true; - if (_type != JSON_UNPARSED || _content.asString == NULL) return false; - - char *end; - errno = 0; - strtol(_content.asString, &end, 10); - - return *end == '\0' && errno == 0; + return _type == JSON_POSITIVE_INTEGER || _type == JSON_NEGATIVE_INTEGER || + (_type == JSON_UNPARSED && Polyfills::isInteger(_content.asString)); } -inline bool JsonVariant::isFloat() const { +inline bool JsonVariant::variantIsFloat() const { using namespace Internals; - if (_type >= JSON_FLOAT_0_DECIMALS) return true; - if (_type != JSON_UNPARSED || _content.asString == NULL) return false; - - return Polyfills::isFloat(_content.asString); + return _type >= JSON_FLOAT_0_DECIMALS || + (_type == JSON_UNPARSED && Polyfills::isFloat(_content.asString)); } #if ARDUINOJSON_ENABLE_STD_STREAM diff --git a/include/ArduinoJson/Polyfills/isFloat.hpp b/include/ArduinoJson/Polyfills/isFloat.hpp index 7557c632..da207d62 100644 --- a/include/ArduinoJson/Polyfills/isFloat.hpp +++ b/include/ArduinoJson/Polyfills/isFloat.hpp @@ -8,12 +8,13 @@ #pragma once #include "./ctype.hpp" -#include "./math.hpp" namespace ArduinoJson { namespace Polyfills { inline bool isFloat(const char* s) { + if (!s) return false; + if (!strcmp(s, "NaN")) return true; if (issign(*s)) s++; if (!strcmp(s, "Infinity")) return true; diff --git a/include/ArduinoJson/Polyfills/isInteger.hpp b/include/ArduinoJson/Polyfills/isInteger.hpp new file mode 100644 index 00000000..a631351e --- /dev/null +++ b/include/ArduinoJson/Polyfills/isInteger.hpp @@ -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'; +} +} +} diff --git a/include/ArduinoJson/Polyfills/parseFloat.hpp b/include/ArduinoJson/Polyfills/parseFloat.hpp index e9c5d98e..bcc4489a 100644 --- a/include/ArduinoJson/Polyfills/parseFloat.hpp +++ b/include/ArduinoJson/Polyfills/parseFloat.hpp @@ -20,6 +20,8 @@ inline T parseFloat(const char* s) { typedef typename traits::mantissa_type mantissa_t; typedef typename traits::exponent_type exponent_t; + if (!s) return 0; + bool negative_result = false; switch (*s) { case '-': diff --git a/include/ArduinoJson/Polyfills/parseInteger.hpp b/include/ArduinoJson/Polyfills/parseInteger.hpp index 90584c4e..bdfd4a28 100644 --- a/include/ArduinoJson/Polyfills/parseInteger.hpp +++ b/include/ArduinoJson/Polyfills/parseInteger.hpp @@ -9,48 +9,32 @@ #include +#include "../Configuration.hpp" +#include "./ctype.hpp" + namespace ArduinoJson { namespace Polyfills { template -T parseInteger(const char *s); +T parseInteger(const char *s) { + if (!s) return 0; -template <> -inline long parseInteger(const char *s) { - return ::strtol(s, NULL, 10); -} + T result = 0; + bool negative_result = false; -template <> -inline unsigned long parseInteger(const char *s) { - return ::strtoul(s, NULL, 10); -} + switch (*s) { + case '-': + negative_result = true; + case '+': + s++; + break; + } -template <> -inline int parseInteger(const char *s) { - return ::atoi(s); -} + while (isdigit(*s)) { + result = static_cast(result * 10 + (*s - '0')); + s++; + } -#if ARDUINOJSON_USE_LONG_LONG -template <> -inline long long parseInteger(const char *s) { - return ::strtoll(s, NULL, 10); -} - -template <> -inline unsigned long long parseInteger(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(const char *s) { - return ::_strtoui64(s, NULL, 10); -} -#endif + return negative_result ? static_cast(result*-1) : result; +} } } diff --git a/include/ArduinoJson/TypeTraits/IsIntegral.hpp b/include/ArduinoJson/TypeTraits/IsIntegral.hpp index 21b5c424..be39c5fa 100644 --- a/include/ArduinoJson/TypeTraits/IsIntegral.hpp +++ b/include/ArduinoJson/TypeTraits/IsIntegral.hpp @@ -19,8 +19,8 @@ template struct IsIntegral { static const bool value = TypeTraits::IsSignedIntegral::value || TypeTraits::IsUnsignedIntegral::value || - TypeTraits::IsSame::value || - TypeTraits::IsSame::value; + TypeTraits::IsSame::value; + // CAUTION: differs from std::is_integral as it doesn't include bool }; template diff --git a/test/Issue90.cpp b/test/Issue90.cpp deleted file mode 100644 index dffe669e..00000000 --- a/test/Issue90.cpp +++ /dev/null @@ -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 -#include // for LONG_MAX - -#define ARDUINOJSON_USE_LONG_LONG 0 -#define ARDUINOJSON_USE_INT64 0 -#include - -#define SUITE Issue90 - -static const char* superLong = - "12345678901234567890123456789012345678901234567890123456789012345678901234" - "5678901234567890123456789012345678901234567890123456789012345678901234567"; - -static const JsonVariant variant = RawJson(superLong); - -TEST(SUITE, IsNotALong) { - ASSERT_FALSE(variant.is()); -} - -TEST(SUITE, AsLong) { - ASSERT_EQ(LONG_MAX, variant.as()); -} - -TEST(SUITE, IsAString) { - ASSERT_FALSE(variant.is()); -} - -TEST(SUITE, AsString) { - ASSERT_STREQ(superLong, variant.as()); -} diff --git a/test/JsonVariant_Storage_Tests.cpp b/test/JsonVariant_Storage_Tests.cpp index 07d3d6c6..45293b76 100644 --- a/test/JsonVariant_Storage_Tests.cpp +++ b/test/JsonVariant_Storage_Tests.cpp @@ -63,6 +63,9 @@ TEST_F(JsonVariant_Storage_Tests, Double) { TEST_F(JsonVariant_Storage_Tests, Float) { testNumericType(); } +TEST_F(JsonVariant_Storage_Tests, Char) { + testNumericType(); +} TEST_F(JsonVariant_Storage_Tests, SChar) { testNumericType(); } diff --git a/test/Polyfills_IsFloat_Tests.cpp b/test/Polyfills_IsFloat_Tests.cpp index 29dd241a..8c886bda 100644 --- a/test/Polyfills_IsFloat_Tests.cpp +++ b/test/Polyfills_IsFloat_Tests.cpp @@ -18,6 +18,10 @@ struct Polyfills_IsFloat_Tests : testing::Test { }; #define TEST_(X) TEST_F(Polyfills_IsFloat_Tests, X) +TEST_(Null) { + check(false, NULL); +} + TEST_(NoExponent) { check(true, "3.14"); check(true, "-3.14"); diff --git a/test/Polyfills_IsInteger_Tests.cpp b/test/Polyfills_IsInteger_Tests.cpp new file mode 100644 index 00000000..376b8e03 --- /dev/null +++ b/test/Polyfills_IsInteger_Tests.cpp @@ -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 +#include + +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"); +} diff --git a/test/Polyfills_ParseFloat_Tests.cpp b/test/Polyfills_ParseFloat_Tests.cpp index be25e7a8..d13629f3 100644 --- a/test/Polyfills_ParseFloat_Tests.cpp +++ b/test/Polyfills_ParseFloat_Tests.cpp @@ -54,6 +54,14 @@ struct Polyfills_ParseFloat_Double_Tests : testing::Test { }; #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) { check("3.14", 3.14); check("-3.14", -3.14); diff --git a/test/Polyfills_ParseInteger_Tests.cpp b/test/Polyfills_ParseInteger_Tests.cpp new file mode 100644 index 00000000..31c98e58 --- /dev/null +++ b/test/Polyfills_ParseInteger_Tests.cpp @@ -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 +#include +#include + +using namespace ArduinoJson::Polyfills; + +struct Polyfills_ParseInteger_Tests : testing::Test { + template + void check(const char* input, T expected) { + T actual = parseInteger(input); + EXPECT_EQ(expected, actual) << input; + } +}; + +#define TEST_(X) TEST_F(Polyfills_ParseInteger_Tests, X) + +TEST_(int8_t) { + check("-128", -128); + check("127", 127); + check("+127", 127); + check("3.14", 3); + // check(" 42", 0); + check("x42", 0); + check("128", -128); + check("-129", 127); + check(NULL, 0); +} + +TEST_(int16_t) { + check("-32768", -32768); + check("32767", 32767); + check("+32767", 32767); + check("3.14", 3); + // check(" 42", 0); + check("x42", 0); + check("-32769", 32767); + check("32768", -32768); + check(NULL, 0); +} + +TEST_(int32_t) { + check("-2147483648", (-2147483647 - 1)); + check("2147483647", 2147483647); + check("+2147483647", 2147483647); + check("3.14", 3); + // check(" 42", 0); + check("x42", 0); + check("-2147483649", 2147483647); + check("2147483648", (-2147483647 - 1)); +} + +TEST_(uint8_t) { + check("0", 0); + check("255", 255); + check("+255", 255); + check("3.14", 3); + // check(" 42", 0); + check("x42", 0); + check("-1", 255); + check("256", 0); +} + +TEST_(uint16_t) { + check("0", 0); + check("65535", 65535); + check("+65535", 65535); + check("3.14", 3); + // check(" 42", 0); + check("x42", 0); + check("-1", 65535); + check("65536", 0); +}