Explorar el Código

Added custom implementation of `strtol()` (issue #465)
`char` is now treated as an integral type (issue #337, #370)

Benoit Blanchon hace 8 años
padre
commit
185eccf6f5

+ 2 - 0
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
 ------

+ 52 - 57
include/ArduinoJson/JsonVariant.hpp

@@ -70,13 +70,15 @@ class JsonVariant : public JsonVariantBase<JsonVariant> {
   }
 
   // Create a JsonVariant containing an integer value.
+  // JsonVariant(char)
   // JsonVariant(signed short)
   // JsonVariant(signed int)
   // JsonVariant(signed long)
+  // JsonVariant(signed char)
   template <typename T>
-  JsonVariant(T value,
-              typename TypeTraits::EnableIf<
-                  TypeTraits::IsSignedIntegral<T>::value>::type * = 0) {
+  JsonVariant(T value, typename TypeTraits::EnableIf<
+                           TypeTraits::IsSignedIntegral<T>::value ||
+                           TypeTraits::IsSame<T, char>::value>::type * = 0) {
     using namespace Internals;
     if (value >= 0) {
       _type = JSON_POSITIVE_INTEGER;
@@ -129,24 +131,26 @@ class JsonVariant : public JsonVariantBase<JsonVariant> {
 
   // Get the variant as the specified type.
   //
-  // short as<signed short>() const;
-  // int as<signed int>() const;
-  // long as<signed long>() const;
+  // char as<char>() const;
+  // signed char as<signed char>() 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>
-  const typename TypeTraits::EnableIf<TypeTraits::IsSignedIntegral<T>::value,
-                                      T>::type
+  const typename TypeTraits::EnableIf<TypeTraits::IsIntegral<T>::value, T>::type
   as() const {
-    return static_cast<T>(variantAsInteger());
+    return variantAsInteger<T>();
   }
-  //
-  // short as<unsigned short>() const;
-  // int as<unsigned int>() const;
-  // long as<unsigned long>() const;
+  // bool as<bool>() const
   template <typename T>
-  const typename TypeTraits::EnableIf<TypeTraits::IsUnsignedIntegral<T>::value,
+  const typename TypeTraits::EnableIf<TypeTraits::IsSame<T, bool>::value,
                                       T>::type
   as() const {
-    return static_cast<T>(asUnsignedInteger());
+    return variantAsInteger<int>() != 0;
   }
   //
   // double as<double>() const;
@@ -155,7 +159,7 @@ class JsonVariant : public JsonVariantBase<JsonVariant> {
   const typename TypeTraits::EnableIf<TypeTraits::IsFloatingPoint<T>::value,
                                       T>::type
   as() const {
-    return static_cast<T>(variantAsFloat());
+    return variantAsFloat<T>();
   }
   //
   // const char* as<const char*>() const;
@@ -180,14 +184,6 @@ class JsonVariant : public JsonVariantBase<JsonVariant> {
     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;
   template <typename T>
@@ -242,32 +238,35 @@ class JsonVariant : public JsonVariantBase<JsonVariant> {
   // Tells weither the variant has the specified type.
   // Returns true if the variant has type type T, false otherwise.
   //
-  // short as<short>() const;
-  // int as<int>() const;
-  // long as<long>() const;
+  // bool is<char>() const;
+  // bool is<signed char>() 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>
-  const typename TypeTraits::EnableIf<TypeTraits::IsIntegral<T>::value &&
-                                          !TypeTraits::IsSame<T, bool>::value,
-                                      bool>::type
+  typename TypeTraits::EnableIf<TypeTraits::IsIntegral<T>::value, bool>::type
   is() const {
-    return isInteger();
+    return variantIsInteger();
   }
   //
-  // double is<double>() const;
-  // float is<float>() const;
+  // bool is<double>() const;
+  // bool is<float>() const;
   template <typename T>
-  const typename TypeTraits::EnableIf<TypeTraits::IsFloatingPoint<T>::value,
-                                      bool>::type
+  typename TypeTraits::EnableIf<TypeTraits::IsFloatingPoint<T>::value,
+                                bool>::type
   is() const {
-    return isFloat();
+    return variantIsFloat();
   }
   //
-  // const bool is<bool>() const
+  // bool is<bool>() const
   template <typename T>
-  const typename TypeTraits::EnableIf<TypeTraits::IsSame<T, bool>::value,
-                                      bool>::type
+  typename TypeTraits::EnableIf<TypeTraits::IsSame<T, bool>::value, bool>::type
   is() const {
-    return isBoolean();
+    return variantIsBoolean();
   }
   //
   // bool is<const char*>() const;
@@ -277,7 +276,7 @@ class JsonVariant : public JsonVariantBase<JsonVariant> {
                                     TypeTraits::IsSame<T, char *>::value,
                                 bool>::type
   is() const {
-    return isString();
+    return variantIsString();
   }
   //
   // bool is<JsonArray> const;
@@ -291,7 +290,7 @@ class JsonVariant : public JsonVariantBase<JsonVariant> {
           JsonArray>::value,
       bool>::type
   is() const {
-    return isArray();
+    return variantIsArray();
   }
   //
   // bool is<JsonObject> const;
@@ -305,7 +304,7 @@ class JsonVariant : public JsonVariantBase<JsonVariant> {
           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<JsonVariant> {
   }
 
  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;
   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 <typename T>
+  T variantAsFloat() const;
+  template <typename T>
+  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));

+ 20 - 49
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 <errno.h>   // for errno
-#include <stdlib.h>  // for strtol, strtod
 #include <string.h>  // for strcmp
 
 namespace ArduinoJson {
@@ -49,42 +48,24 @@ inline JsonObject &JsonVariant::variantAsObject() const {
   return JsonObject::invalid();
 }
 
-inline Internals::JsonInteger JsonVariant::variantAsInteger() const {
+template <typename T>
+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<T>(_content.asInteger);
     case JSON_NEGATIVE_INTEGER:
-      return -static_cast<JsonInteger>(_content.asInteger);
+      return static_cast<T>(_content.asInteger * -1);
     case JSON_STRING:
     case JSON_UNPARSED:
       if (!_content.asString) return 0;
       if (!strcmp("true", _content.asString)) return 1;
-      return Polyfills::parseInteger<JsonInteger>(_content.asString);
+      return Polyfills::parseInteger<T>(_content.asString);
     default:
-      return static_cast<JsonInteger>(_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);
+      return static_cast<T>(_content.asFloat);
   }
 }
 
@@ -97,27 +78,26 @@ inline const char *JsonVariant::variantAsString() const {
   return NULL;
 }
 
-inline Internals::JsonFloat JsonVariant::variantAsFloat() const {
+template <typename T>
+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<JsonFloat>(_content.asInteger);
+      return static_cast<T>(_content.asInteger);
     case JSON_NEGATIVE_INTEGER:
-      return -static_cast<JsonFloat>(_content.asInteger);
+      return -static_cast<T>(_content.asInteger);
     case JSON_STRING:
     case JSON_UNPARSED:
-      return _content.asString
-                 ? Polyfills::parseFloat<JsonFloat>(_content.asString)
-                 : 0;
+      return Polyfills::parseFloat<T>(_content.asString);
     default:
-      return _content.asFloat;
+      return static_cast<T>(_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

+ 2 - 1
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;

+ 22 - 0
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';
+}
+}
+}

+ 2 - 0
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 '-':

+ 19 - 35
include/ArduinoJson/Polyfills/parseInteger.hpp

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

+ 2 - 2
include/ArduinoJson/TypeTraits/IsIntegral.hpp

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

+ 0 - 37
test/Issue90.cpp

@@ -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*>());
-}

+ 3 - 0
test/JsonVariant_Storage_Tests.cpp

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

+ 4 - 0
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");

+ 45 - 0
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 <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");
+}

+ 8 - 0
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);

+ 79 - 0
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 <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);
+}