diff --git a/CHANGELOG.md b/CHANGELOG.md index 74c01a04..2d8d9eb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ HEAD ---- * Added operator `==` to compare `JsonVariant` and strings (issue #402) +* Reduced memory consumption by not duplicating spaces and comments v5.7.3 ------ diff --git a/include/ArduinoJson/Data/BlockJsonBuffer.hpp b/include/ArduinoJson/Data/BlockJsonBuffer.hpp deleted file mode 100644 index 3dff178f..00000000 --- a/include/ArduinoJson/Data/BlockJsonBuffer.hpp +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright Benoit Blanchon 2014-2016 -// MIT License -// -// Arduino JSON library -// https://github.com/bblanchon/ArduinoJson -// If you like this project, please add a star! - -#pragma once - -#include "../JsonBuffer.hpp" - -#include - -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wnon-virtual-dtor" -#elif defined(__GNUC__) -#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) -#pragma GCC diagnostic push -#endif -#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" -#endif - -namespace ArduinoJson { -namespace Internals { -class DefaultAllocator { - public: - void* allocate(size_t size) { - return malloc(size); - } - void deallocate(void* pointer) { - free(pointer); - } -}; - -template -class BlockJsonBuffer : public JsonBuffer { - struct Block; - struct EmptyBlock { - Block* next; - size_t capacity; - size_t size; - }; - struct Block : EmptyBlock { - uint8_t data[1]; - }; - - public: - BlockJsonBuffer(size_t initialSize = 256) - : _head(NULL), _nextBlockSize(initialSize) {} - - ~BlockJsonBuffer() { - Block* currentBlock = _head; - - while (currentBlock != NULL) { - Block* nextBlock = currentBlock->next; - _allocator.deallocate(currentBlock); - currentBlock = nextBlock; - } - } - - size_t size() const { - size_t total = 0; - for (const Block* b = _head; b; b = b->next) total += b->size; - return total; - } - - virtual void* alloc(size_t bytes) { - return canAllocInHead(bytes) ? allocInHead(bytes) : allocInNewBlock(bytes); - } - - private: - bool canAllocInHead(size_t bytes) const { - return _head != NULL && _head->size + bytes <= _head->capacity; - } - - void* allocInHead(size_t bytes) { - void* p = _head->data + _head->size; - _head->size += round_size_up(bytes); - return p; - } - - void* allocInNewBlock(size_t bytes) { - size_t capacity = _nextBlockSize; - if (bytes > capacity) capacity = bytes; - if (!addNewBlock(capacity)) return NULL; - _nextBlockSize *= 2; - return allocInHead(bytes); - } - - bool addNewBlock(size_t capacity) { - size_t bytes = sizeof(EmptyBlock) + capacity; - Block* block = static_cast(_allocator.allocate(bytes)); - if (block == NULL) return false; - block->capacity = capacity; - block->size = 0; - block->next = _head; - _head = block; - return true; - } - - TAllocator _allocator; - Block* _head; - size_t _nextBlockSize; -}; -} -} - -#if defined(__clang__) -#pragma clang diagnostic pop -#elif defined(__GNUC__) -#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) -#pragma GCC diagnostic pop -#endif -#endif diff --git a/include/ArduinoJson/Data/StringFuncs.hpp b/include/ArduinoJson/Data/StringFuncs.hpp index e32efae7..a899ab76 100644 --- a/include/ArduinoJson/Data/StringFuncs.hpp +++ b/include/ArduinoJson/Data/StringFuncs.hpp @@ -30,6 +30,17 @@ template struct StringFuncs : StringFuncs {}; struct CharPtrFuncs { + class Iterator { + const char* _ptr; + + public: + Iterator(const char* ptr) : _ptr(ptr ? ptr : "") {} + + char next() { + return *_ptr++; + } + }; + static bool equals(const char* str, const char* expected) { return strcmp(str, expected) == 0; } @@ -71,6 +82,10 @@ struct StdStringFuncs { return static_cast(dup); } + struct Iterator : CharPtrFuncs::Iterator { + Iterator(const TString& str) : CharPtrFuncs::Iterator(str.c_str()) {} + }; + static bool equals(const TString& str, const char* expected) { return str == expected; } @@ -99,6 +114,18 @@ struct StringFuncs : StdStringFuncs {}; #if ARDUINOJSON_ENABLE_PROGMEM template <> struct StringFuncs { + class Iterator { + const char* _ptr; + + public: + Iterator(const __FlashStringHelper* ptr) + : _ptr(reinterpret_cast(ptr)) {} + + char next() { + return pgm_read_byte_near(_ptr++); + } + }; + static bool equals(const __FlashStringHelper* str, const char* expected) { return strcmp_P(expected, (PGM_P)str) == 0; } diff --git a/include/ArduinoJson/Deserialization/Comments.hpp b/include/ArduinoJson/Deserialization/Comments.hpp index eb70cc7e..6586777e 100644 --- a/include/ArduinoJson/Deserialization/Comments.hpp +++ b/include/ArduinoJson/Deserialization/Comments.hpp @@ -12,51 +12,51 @@ namespace Internals { template void skipSpacesAndComments(TInput& input) { for (;;) { - switch (input.peek()) { + switch (input.current()) { // spaces case ' ': case '\t': case '\r': case '\n': - input.skip(); + input.move(); continue; // comments case '/': - switch (input.peekNext()) { + switch (input.next()) { // C-style block comment case '*': - input.skip(); // skip '/' - input.skip(); // skip '*' + input.move(); // skip '/' + input.move(); // skip '*' for (;;) { - switch (input.peek()) { + switch (input.current()) { case '\0': return; case '*': - input.skip(); // skip '*' - if (input.peek() == '/') { - input.skip(); // skip '/' + input.move(); // skip '*' + if (input.current() == '/') { + input.move(); // skip '/' return; } break; default: - input.skip(); + input.move(); } } break; // C++-style line comment case '/': - input.skip(); // skip '/' + input.move(); // skip '/' for (;;) { - switch (input.peek()) { + switch (input.current()) { case '\0': return; case '\n': - input.skip(); + input.move(); return; default: - input.skip(); + input.move(); } } return; diff --git a/include/ArduinoJson/Deserialization/JsonParser.hpp b/include/ArduinoJson/Deserialization/JsonParser.hpp index 1955488e..b4aa771a 100644 --- a/include/ArduinoJson/Deserialization/JsonParser.hpp +++ b/include/ArduinoJson/Deserialization/JsonParser.hpp @@ -18,12 +18,14 @@ namespace Internals { // Parse JSON string to create JsonArrays and JsonObjects // This internal class is not indended to be used directly. // Instead, use JsonBuffer.parseArray() or .parseObject() +template class JsonParser { public: - JsonParser(JsonBuffer *buffer, char *json, uint8_t nestingLimit) + JsonParser(JsonBuffer *buffer, TReader reader, TWriter writer, + uint8_t nestingLimit) : _buffer(buffer), - _reader(json), - _writer(json), + _reader(reader), + _writer(writer), _nestingLimit(nestingLimit) {} JsonArray &parseArray(); @@ -36,7 +38,9 @@ class JsonParser { } private: - static bool eat(StringReader &, char charToSkip); + JsonParser &operator=(const JsonParser &); // non-copiable + + static bool eat(TReader &, char charToSkip); FORCE_INLINE bool eat(char charToSkip) { return eat(_reader, charToSkip); } @@ -63,9 +67,42 @@ class JsonParser { } JsonBuffer *_buffer; - StringReader _reader; - StringWriter _writer; + TReader _reader; + TWriter _writer; uint8_t _nestingLimit; }; + +template +struct JsonParserBuilder { + typedef typename Internals::StringFuncs::Iterator InputIterator; + typedef JsonParser, TJsonBuffer &> TParser; + + static TParser makeParser(TJsonBuffer *buffer, const TString &json, + uint8_t nestingLimit) { + return TParser(buffer, InputIterator(json), *buffer, nestingLimit); + } +}; + +template +struct JsonParserBuilder { + typedef typename Internals::StringFuncs::Iterator InputIterator; + typedef JsonParser, StringWriter> TParser; + + static TParser makeParser(TJsonBuffer *buffer, char *json, + uint8_t nestingLimit) { + return TParser(buffer, InputIterator(json), json, nestingLimit); + } +}; + +template +struct JsonParserBuilder + : JsonParserBuilder {}; + +template +inline typename JsonParserBuilder::TParser makeParser( + TJsonBuffer *buffer, TString &json, uint8_t nestingLimit) { + return JsonParserBuilder::makeParser(buffer, json, + nestingLimit); +} } } diff --git a/include/ArduinoJson/Deserialization/JsonParserImpl.hpp b/include/ArduinoJson/Deserialization/JsonParserImpl.hpp index ef0315eb..dc861d73 100644 --- a/include/ArduinoJson/Deserialization/JsonParserImpl.hpp +++ b/include/ArduinoJson/Deserialization/JsonParserImpl.hpp @@ -10,16 +10,19 @@ #include "Comments.hpp" #include "JsonParser.hpp" -inline bool ArduinoJson::Internals::JsonParser::eat(StringReader &reader, - char charToSkip) { +template +inline bool ArduinoJson::Internals::JsonParser::eat( + TReader &reader, char charToSkip) { skipSpacesAndComments(reader); - if (reader.peek() != charToSkip) return false; - reader.skip(); + if (reader.current() != charToSkip) return false; + reader.move(); skipSpacesAndComments(reader); return true; } -inline bool ArduinoJson::Internals::JsonParser::parseAnythingTo( +template +inline bool +ArduinoJson::Internals::JsonParser::parseAnythingTo( JsonVariant *destination) { if (_nestingLimit == 0) return false; _nestingLimit--; @@ -28,11 +31,13 @@ inline bool ArduinoJson::Internals::JsonParser::parseAnythingTo( return success; } -inline bool ArduinoJson::Internals::JsonParser::parseAnythingToUnsafe( +template +inline bool +ArduinoJson::Internals::JsonParser::parseAnythingToUnsafe( JsonVariant *destination) { skipSpacesAndComments(_reader); - switch (_reader.peek()) { + switch (_reader.current()) { case '[': return parseArrayTo(destination); @@ -44,8 +49,9 @@ inline bool ArduinoJson::Internals::JsonParser::parseAnythingToUnsafe( } } +template inline ArduinoJson::JsonArray & -ArduinoJson::Internals::JsonParser::parseArray() { +ArduinoJson::Internals::JsonParser::parseArray() { // Create an empty array JsonArray &array = _buffer->createArray(); @@ -76,7 +82,8 @@ ERROR_NO_MEMORY: return JsonArray::invalid(); } -inline bool ArduinoJson::Internals::JsonParser::parseArrayTo( +template +inline bool ArduinoJson::Internals::JsonParser::parseArrayTo( JsonVariant *destination) { JsonArray &array = parseArray(); if (!array.success()) return false; @@ -85,8 +92,9 @@ inline bool ArduinoJson::Internals::JsonParser::parseArrayTo( return true; } +template inline ArduinoJson::JsonObject & -ArduinoJson::Internals::JsonParser::parseObject() { +ArduinoJson::Internals::JsonParser::parseObject() { // Create an empty object JsonObject &object = _buffer->createObject(); @@ -124,7 +132,8 @@ ERROR_NO_MEMORY: return JsonObject::invalid(); } -inline bool ArduinoJson::Internals::JsonParser::parseObjectTo( +template +inline bool ArduinoJson::Internals::JsonParser::parseObjectTo( JsonVariant *destination) { JsonObject &object = parseObject(); if (!object.success()) return false; @@ -133,46 +142,49 @@ inline bool ArduinoJson::Internals::JsonParser::parseObjectTo( return true; } -inline const char *ArduinoJson::Internals::JsonParser::parseString() { - const char *str = _writer.startString(); +template +inline const char * +ArduinoJson::Internals::JsonParser::parseString() { + typename TypeTraits::RemoveReference::type::String str = + _writer.startString(); - char c = _reader.peek(); + char c = _reader.current(); if (isQuote(c)) { // quotes - _reader.skip(); + _reader.move(); char stopChar = c; for (;;) { - c = _reader.peek(); + c = _reader.current(); if (c == '\0') break; - _reader.skip(); + _reader.move(); if (c == stopChar) break; if (c == '\\') { // replace char - c = Encoding::unescapeChar(_reader.peek()); + c = Encoding::unescapeChar(_reader.current()); if (c == '\0') break; - _reader.skip(); + _reader.move(); } - _writer.append(c); + str.append(c); } } else { // no quotes for (;;) { if (!isLetterOrNumber(c)) break; - _reader.skip(); - _writer.append(c); - c = _reader.peek(); + _reader.move(); + str.append(c); + c = _reader.current(); } } - _writer.stopString(); - return str; + return str.c_str(); } -inline bool ArduinoJson::Internals::JsonParser::parseStringTo( +template +inline bool ArduinoJson::Internals::JsonParser::parseStringTo( JsonVariant *destination) { - bool hasQuotes = isQuote(_reader.peek()); + bool hasQuotes = isQuote(_reader.current()); const char *value = parseString(); if (value == NULL) return false; if (hasQuotes) { diff --git a/include/ArduinoJson/Deserialization/StringReader.hpp b/include/ArduinoJson/Deserialization/StringReader.hpp index 969540d8..e0e6fe78 100644 --- a/include/ArduinoJson/Deserialization/StringReader.hpp +++ b/include/ArduinoJson/Deserialization/StringReader.hpp @@ -13,24 +13,29 @@ namespace Internals { // Parse JSON string to create JsonArrays and JsonObjects // This internal class is not indended to be used directly. // Instead, use JsonBuffer.parseArray() or .parseObject() +template class StringReader { + TIterator _input; + char _current, _next; + public: - StringReader(const char *input) : _ptr(input ? input : "") {} - - void skip() { - _ptr++; + StringReader(const TIterator& input) : _input(input) { + _current = _input.next(); + _next = _input.next(); } - char peek() const { - return _ptr[0]; + void move() { + _current = _next; + _next = _input.next(); } - char peekNext() const { - return _ptr[1]; + char current() const { + return _current; } - private: - const char *_ptr; + char next() const { + return _next; + } }; } } diff --git a/include/ArduinoJson/Deserialization/StringWriter.hpp b/include/ArduinoJson/Deserialization/StringWriter.hpp index e42ff489..9ace9a3f 100644 --- a/include/ArduinoJson/Deserialization/StringWriter.hpp +++ b/include/ArduinoJson/Deserialization/StringWriter.hpp @@ -10,27 +10,34 @@ namespace ArduinoJson { namespace Internals { -// Parse JSON string to create JsonArrays and JsonObjects -// This internal class is not indended to be used directly. -// Instead, use JsonBuffer.parseArray() or .parseObject() class StringWriter { public: - StringWriter(char *buffer) : _ptr(buffer) {} + class String { + public: + String(char** ptr) : _writePtr(ptr), _startPtr(*ptr) {} - const char *startString() { - return _ptr; - } + void append(char c) { + *(*_writePtr)++ = c; + } - void stopString() { - *_ptr++ = 0; - } + const char* c_str() const { + *(*_writePtr)++ = 0; + return _startPtr; + } - void append(char c) { - *_ptr++ = c; + private: + char** _writePtr; + char* _startPtr; + }; + + StringWriter(char* buffer) : _ptr(buffer) {} + + String startString() { + return String(&_ptr); } private: - char *_ptr; + char* _ptr; }; } } diff --git a/include/ArduinoJson/DynamicJsonBuffer.hpp b/include/ArduinoJson/DynamicJsonBuffer.hpp index e2acf57f..67eab0d8 100644 --- a/include/ArduinoJson/DynamicJsonBuffer.hpp +++ b/include/ArduinoJson/DynamicJsonBuffer.hpp @@ -7,12 +7,153 @@ #pragma once -#include "Data/BlockJsonBuffer.hpp" +#include "JsonBufferBase.hpp" + +#include + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnon-virtual-dtor" +#elif defined(__GNUC__) +#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) +#pragma GCC diagnostic push +#endif +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#endif namespace ArduinoJson { +class DefaultAllocator { + public: + void* allocate(size_t size) { + return malloc(size); + } + void deallocate(void* pointer) { + free(pointer); + } +}; + +template +class DynamicJsonBufferBase + : public JsonBufferBase > { + struct Block; + struct EmptyBlock { + Block* next; + size_t capacity; + size_t size; + }; + struct Block : EmptyBlock { + uint8_t data[1]; + }; + + public: + DynamicJsonBufferBase(size_t initialSize = 256) + : _head(NULL), _nextBlockCapacity(initialSize) {} + + ~DynamicJsonBufferBase() { + Block* currentBlock = _head; + + while (currentBlock != NULL) { + Block* nextBlock = currentBlock->next; + _allocator.deallocate(currentBlock); + currentBlock = nextBlock; + } + } + + size_t size() const { + size_t total = 0; + for (const Block* b = _head; b; b = b->next) total += b->size; + return total; + } + + virtual void* alloc(size_t bytes) { + alignNextAlloc(); + return canAllocInHead(bytes) ? allocInHead(bytes) : allocInNewBlock(bytes); + } + + class String { + public: + String(DynamicJsonBufferBase* parent) + : _parent(parent), _start(NULL), _length(0) {} + + void append(char c) { + if (_parent->canAllocInHead(1)) { + char* end = static_cast(_parent->allocInHead(1)); + *end = c; + if (_length == 0) _start = end; + } else { + char* newStart = + static_cast(_parent->allocInNewBlock(_length + 1)); + if (_start && newStart) memcpy(newStart, _start, _length); + newStart[_length] = c; + _start = newStart; + } + _length++; + } + + const char* c_str() { + append(0); + return _start; + } + + private: + DynamicJsonBufferBase* _parent; + char* _start; + int _length; + }; + + String startString() { + return String(this); + } + + private: + void alignNextAlloc() { + if (_head) _head->size = this->round_size_up(_head->size); + } + + bool canAllocInHead(size_t bytes) const { + return _head != NULL && _head->size + bytes <= _head->capacity; + } + + void* allocInHead(size_t bytes) { + void* p = _head->data + _head->size; + _head->size += bytes; + return p; + } + + void* allocInNewBlock(size_t bytes) { + size_t capacity = _nextBlockCapacity; + if (bytes > capacity) capacity = bytes; + if (!addNewBlock(capacity)) return NULL; + _nextBlockCapacity *= 2; + return allocInHead(bytes); + } + + bool addNewBlock(size_t capacity) { + size_t bytes = sizeof(EmptyBlock) + capacity; + Block* block = static_cast(_allocator.allocate(bytes)); + if (block == NULL) return false; + block->capacity = capacity; + block->size = 0; + block->next = _head; + _head = block; + return true; + } + + TAllocator _allocator; + Block* _head; + size_t _nextBlockCapacity; +}; + +#if defined(__clang__) +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) +#pragma GCC diagnostic pop +#endif +#endif + // Implements a JsonBuffer with dynamic memory allocation. // You are strongly encouraged to consider using StaticJsonBuffer which is much // more suitable for embedded systems. -typedef Internals::BlockJsonBuffer - DynamicJsonBuffer; +typedef DynamicJsonBufferBase DynamicJsonBuffer; } diff --git a/include/ArduinoJson/JsonBuffer.hpp b/include/ArduinoJson/JsonBuffer.hpp index 220a805c..fdef06a1 100644 --- a/include/ArduinoJson/JsonBuffer.hpp +++ b/include/ArduinoJson/JsonBuffer.hpp @@ -51,58 +51,6 @@ class JsonBuffer { // allocation fails. JsonObject &createObject(); - // Allocates and populate a JsonArray from a JSON string. - // - // The First argument is a pointer to the JSON string, the memory must be - // writable - // because the parser will insert null-terminators and replace escaped chars. - // - // The second argument set the nesting limit - // - // Returns a reference to the new JsonObject or JsonObject::invalid() if the - // allocation fails. - JsonArray &parseArray( - char *json, uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT); - - // With this overload, the JsonBuffer will make a copy of the string - template - JsonArray &parseArray(const TString &json, - uint8_t nesting = ARDUINOJSON_DEFAULT_NESTING_LIMIT) { - return parseArray(strdup(json), nesting); - } - - // Allocates and populate a JsonObject from a JSON string. - // - // The First argument is a pointer to the JSON string, the memory must be - // writable - // because the parser will insert null-terminators and replace escaped chars. - // - // The second argument set the nesting limit - // - // Returns a reference to the new JsonObject or JsonObject::invalid() if the - // allocation fails. - JsonObject &parseObject( - char *json, uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT); - - // With this overload, the JsonBuffer will make a copy of the string - template - JsonObject &parseObject(const TString &json, - uint8_t nesting = ARDUINOJSON_DEFAULT_NESTING_LIMIT) { - return parseObject(strdup(json), nesting); - } - - // Generalized version of parseArray() and parseObject(), also works for - // integral types. - JsonVariant parse(char *json, - uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT); - - // With this overload, the JsonBuffer will make a copy of the string - template - JsonVariant parse(const TString &json, - uint8_t nesting = ARDUINOJSON_DEFAULT_NESTING_LIMIT) { - return parse(strdup(json), nesting); - } - // Duplicate a string template char *strdup(const TString &src) { @@ -114,7 +62,7 @@ class JsonBuffer { virtual void *alloc(size_t size) = 0; protected: - // Preserve aligment if nessary + // Preserve aligment if necessary static FORCE_INLINE size_t round_size_up(size_t bytes) { #if ARDUINOJSON_ENABLE_ALIGNMENT const size_t x = sizeof(void *) - 1; diff --git a/include/ArduinoJson/JsonBufferBase.hpp b/include/ArduinoJson/JsonBufferBase.hpp new file mode 100644 index 00000000..645dd9ea --- /dev/null +++ b/include/ArduinoJson/JsonBufferBase.hpp @@ -0,0 +1,97 @@ +// Copyright Benoit Blanchon 2014-2016 +// MIT License +// +// Arduino JSON library +// https://github.com/bblanchon/ArduinoJson +// If you like this project, please add a star! + +#pragma once + +#include "Deserialization/JsonParser.hpp" + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnon-virtual-dtor" +#elif defined(__GNUC__) +#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) +#pragma GCC diagnostic push +#endif +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#endif + +namespace ArduinoJson { +template +class JsonBufferBase : public JsonBuffer { + public: + // Allocates and populate a JsonArray from a JSON string. + // + // The First argument is a pointer to the JSON string, the memory must be + // writable + // because the parser will insert null-terminators and replace escaped chars. + // + // The second argument set the nesting limit + // + // Returns a reference to the new JsonObject or JsonObject::invalid() if the + // allocation fails. + // With this overload, the JsonBuffer will make a copy of the string + template + JsonArray &parseArray( + const TString &json, + uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT) { + return Internals::makeParser(that(), json, nestingLimit).parseArray(); + } + template + JsonArray &parseArray( + TString &json, uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT) { + return Internals::makeParser(that(), json, nestingLimit).parseArray(); + } + + // Allocates and populate a JsonObject from a JSON string. + // + // The First argument is a pointer to the JSON string, the memory must be + // writable + // because the parser will insert null-terminators and replace escaped chars. + // + // The second argument set the nesting limit + // + // Returns a reference to the new JsonObject or JsonObject::invalid() if the + // allocation fails. + template + JsonObject &parseObject( + const TString &json, + uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT) { + return Internals::makeParser(that(), json, nestingLimit).parseObject(); + } + template + JsonObject &parseObject( + TString &json, uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT) { + return Internals::makeParser(that(), json, nestingLimit).parseObject(); + } + + // Generalized version of parseArray() and parseObject(), also works for + // integral types. + template + JsonVariant parse(const TString &json, + uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT) { + return Internals::makeParser(that(), json, nestingLimit).parseVariant(); + } + template + JsonVariant parse(TString &json, + uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT) { + return Internals::makeParser(that(), json, nestingLimit).parseVariant(); + } + + private: + TDerived *that() { + return static_cast(this); + } +}; +} + +#if defined(__clang__) +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) +#pragma GCC diagnostic pop +#endif +#endif diff --git a/include/ArduinoJson/JsonBufferImpl.hpp b/include/ArduinoJson/JsonBufferImpl.hpp index bd0b91bf..67c76970 100644 --- a/include/ArduinoJson/JsonBufferImpl.hpp +++ b/include/ArduinoJson/JsonBufferImpl.hpp @@ -18,21 +18,3 @@ inline ArduinoJson::JsonObject &ArduinoJson::JsonBuffer::createObject() { JsonObject *ptr = new (this) JsonObject(this); return ptr ? *ptr : JsonObject::invalid(); } - -inline ArduinoJson::JsonArray &ArduinoJson::JsonBuffer::parseArray( - char *json, uint8_t nestingLimit) { - Internals::JsonParser parser(this, json, nestingLimit); - return parser.parseArray(); -} - -inline ArduinoJson::JsonObject &ArduinoJson::JsonBuffer::parseObject( - char *json, uint8_t nestingLimit) { - Internals::JsonParser parser(this, json, nestingLimit); - return parser.parseObject(); -} - -inline ArduinoJson::JsonVariant ArduinoJson::JsonBuffer::parse( - char *json, uint8_t nestingLimit) { - Internals::JsonParser parser(this, json, nestingLimit); - return parser.parseVariant(); -} diff --git a/include/ArduinoJson/JsonVariantComparisons.hpp b/include/ArduinoJson/JsonVariantComparisons.hpp index a0b79e9d..c9e7204a 100644 --- a/include/ArduinoJson/JsonVariantComparisons.hpp +++ b/include/ArduinoJson/JsonVariantComparisons.hpp @@ -7,7 +7,7 @@ #pragma once -#include "Internals/StringFuncs.hpp" +#include "Data/StringFuncs.hpp" #include "JsonVariantBase.hpp" #include "TypeTraits/EnableIf.hpp" diff --git a/include/ArduinoJson/StaticJsonBuffer.hpp b/include/ArduinoJson/StaticJsonBuffer.hpp index 2ae9a3ab..2908772d 100644 --- a/include/ArduinoJson/StaticJsonBuffer.hpp +++ b/include/ArduinoJson/StaticJsonBuffer.hpp @@ -7,7 +7,7 @@ #pragma once -#include "JsonBuffer.hpp" +#include "JsonBufferBase.hpp" #if defined(__clang__) #pragma clang diagnostic push @@ -21,32 +21,87 @@ namespace ArduinoJson { -// Implements a JsonBuffer with fixed memory allocation. -// The template paramenter CAPACITY specifies the capacity of the buffer in -// bytes. -template -class StaticJsonBuffer : public JsonBuffer { +class StaticJsonBufferBase : public JsonBufferBase { public: - explicit StaticJsonBuffer() : _size(0) {} + class String { + public: + String(StaticJsonBufferBase* parent) : _parent(parent) { + _start = parent->_buffer + parent->_size; + } + + void append(char c) { + if (_parent->canAlloc(1)) { + char* last = static_cast(_parent->doAlloc(1)); + *last = c; + } + } + + const char* c_str() const { + if (_parent->canAlloc(1)) { + char* last = static_cast(_parent->doAlloc(1)); + *last = '\0'; + return _start; + } else { + return NULL; + } + } + + private: + StaticJsonBufferBase* _parent; + char* _start; + }; + + StaticJsonBufferBase(char* buffer, size_t capa) + : _buffer(buffer), _capacity(capa), _size(0) {} size_t capacity() const { - return CAPACITY; + return _capacity; } size_t size() const { return _size; } virtual void* alloc(size_t bytes) { - if (_size + bytes > CAPACITY) return NULL; - void* p = &_buffer[_size]; - _size += round_size_up(bytes); - return p; + alignNextAlloc(); + if (!canAlloc(bytes)) return NULL; + return doAlloc(bytes); + } + + String startString() { + return String(this); } private: - uint8_t _buffer[CAPACITY]; + void alignNextAlloc() { + _size = round_size_up(_size); + } + + bool canAlloc(size_t bytes) const { + return _size + bytes <= _capacity; + } + + void* doAlloc(size_t bytes) { + void* p = &_buffer[_size]; + _size += bytes; + return p; + } + + char* _buffer; + size_t _capacity; size_t _size; }; + +// Implements a JsonBuffer with fixed memory allocation. +// The template paramenter CAPACITY specifies the capacity of the buffer in +// bytes. +template +class StaticJsonBuffer : public StaticJsonBufferBase { + public: + explicit StaticJsonBuffer() : StaticJsonBufferBase(_buffer, CAPACITY) {} + + private: + char _buffer[CAPACITY]; +}; } #if defined(__clang__) diff --git a/test/DynamicJsonBuffer_Basic_Tests.cpp b/test/DynamicJsonBuffer_Basic_Tests.cpp index 31d94f8f..61365ac7 100644 --- a/test/DynamicJsonBuffer_Basic_Tests.cpp +++ b/test/DynamicJsonBuffer_Basic_Tests.cpp @@ -30,13 +30,19 @@ TEST_F(DynamicJsonBuffer_Basic_Tests, ReturnDifferentPointer) { ASSERT_NE(p1, p2); } -TEST_F(DynamicJsonBuffer_Basic_Tests, Alignment) { - size_t mask = sizeof(void*) - 1; +static bool isAligned(void* ptr) { + const size_t mask = sizeof(void*) - 1; + size_t addr = reinterpret_cast(ptr); + return (addr & mask) == 0; +} - for (size_t size = 1; size <= sizeof(void*); size++) { - size_t addr = reinterpret_cast(buffer.alloc(1)); - ASSERT_EQ(0, addr & mask); - } +TEST_F(DynamicJsonBuffer_Basic_Tests, Alignment) { + // make room for tow but not three + buffer = DynamicJsonBuffer(2 * sizeof(void*) + 1); + + ASSERT_TRUE(isAligned(buffer.alloc(1))); // this on is aligned by design + ASSERT_TRUE(isAligned(buffer.alloc(1))); // this one fits in the first block + ASSERT_TRUE(isAligned(buffer.alloc(1))); // this one requires a new block } TEST_F(DynamicJsonBuffer_Basic_Tests, strdup) { diff --git a/test/DynamicJsonBuffer_NoMemory_Tests.cpp b/test/DynamicJsonBuffer_NoMemory_Tests.cpp index abdb87f9..fe190734 100644 --- a/test/DynamicJsonBuffer_NoMemory_Tests.cpp +++ b/test/DynamicJsonBuffer_NoMemory_Tests.cpp @@ -5,18 +5,20 @@ // https://github.com/bblanchon/ArduinoJson // If you like this project, please add a star! -#include #include +#include class NoMemoryAllocator { public: - void* allocate(size_t) { return NULL; } + void* allocate(size_t) { + return NULL; + } void deallocate(void*) {} }; class DynamicJsonBuffer_NoMemory_Tests : public ::testing::Test { protected: - Internals::BlockJsonBuffer _jsonBuffer; + DynamicJsonBufferBase _jsonBuffer; }; TEST_F(DynamicJsonBuffer_NoMemory_Tests, FixCodeCoverage) { diff --git a/test/DynamicJsonBuffer_String_Tests.cpp b/test/DynamicJsonBuffer_String_Tests.cpp new file mode 100644 index 00000000..ad3e0fc4 --- /dev/null +++ b/test/DynamicJsonBuffer_String_Tests.cpp @@ -0,0 +1,48 @@ +// Copyright Benoit Blanchon 2014-2016 +// MIT License +// +// Arduino JSON library +// https://github.com/bblanchon/ArduinoJson +// If you like this project, please add a star! + +#include +#include + +TEST(DynamicJsonBuffer_String_Tests, WorksWhenBufferIsBigEnough) { + DynamicJsonBuffer jsonBuffer(6); + + DynamicJsonBuffer::String str = jsonBuffer.startString(); + str.append('h'); + str.append('e'); + str.append('l'); + str.append('l'); + str.append('o'); + + ASSERT_STREQ("hello", str.c_str()); +} + +TEST(DynamicJsonBuffer_String_Tests, GrowsWhenBufferIsTooSmall) { + DynamicJsonBuffer jsonBuffer(5); + + DynamicJsonBuffer::String str = jsonBuffer.startString(); + str.append('h'); + str.append('e'); + str.append('l'); + str.append('l'); + str.append('o'); + + ASSERT_STREQ("hello", str.c_str()); +} + +TEST(DynamicJsonBuffer_String_Tests, SizeIncreases) { + DynamicJsonBuffer jsonBuffer(5); + + DynamicJsonBuffer::String str = jsonBuffer.startString(); + ASSERT_EQ(0, jsonBuffer.size()); + + str.append('h'); + ASSERT_EQ(1, jsonBuffer.size()); + + str.c_str(); + ASSERT_EQ(2, jsonBuffer.size()); +} diff --git a/test/StaticJsonBuffer_ParseArray_Tests.cpp b/test/StaticJsonBuffer_ParseArray_Tests.cpp index c1139768..4f358f2c 100644 --- a/test/StaticJsonBuffer_ParseArray_Tests.cpp +++ b/test/StaticJsonBuffer_ParseArray_Tests.cpp @@ -5,12 +5,12 @@ // https://github.com/bblanchon/ArduinoJson // If you like this project, please add a star! -#include #include +#include class StaticJsonBuffer_ParseArray_Tests : public testing::Test { protected: - void with(JsonBuffer& jsonBuffer) { + void with(StaticJsonBufferBase& jsonBuffer) { _jsonBuffer = &jsonBuffer; } @@ -27,7 +27,7 @@ class StaticJsonBuffer_ParseArray_Tests : public testing::Test { } private: - JsonBuffer* _jsonBuffer; + StaticJsonBufferBase* _jsonBuffer; char _jsonString[256]; }; @@ -86,3 +86,11 @@ TEST_F(StaticJsonBuffer_ParseArray_Tests, ConstCharPtrNull) { .parseArray(static_cast(0)) .success()); } + +TEST_F(StaticJsonBuffer_ParseArray_Tests, CopyStringNotSpaces) { + StaticJsonBuffer<100> jsonBuffer; + jsonBuffer.parseArray(" [ \"1234567\" ] "); + ASSERT_EQ(JSON_ARRAY_SIZE(1) + sizeof("1234567"), jsonBuffer.size()); + // note we use a string of 8 bytes to be sure that the StaticJsonBuffer + // will not insert bytes to enforce alignement +} diff --git a/test/StaticJsonBuffer_ParseObject_Tests.cpp b/test/StaticJsonBuffer_ParseObject_Tests.cpp index 0043b7bf..8b8c2fb7 100644 --- a/test/StaticJsonBuffer_ParseObject_Tests.cpp +++ b/test/StaticJsonBuffer_ParseObject_Tests.cpp @@ -10,7 +10,7 @@ class StaticJsonBuffer_ParseObject_Tests : public testing::Test { protected: - void with(JsonBuffer& jsonBuffer) { + void with(StaticJsonBufferBase& jsonBuffer) { _jsonBuffer = &jsonBuffer; } @@ -27,7 +27,7 @@ class StaticJsonBuffer_ParseObject_Tests : public testing::Test { } private: - JsonBuffer* _jsonBuffer; + StaticJsonBufferBase* _jsonBuffer; char _jsonString[256]; }; diff --git a/test/StaticJsonBuffer_String_Tests.cpp b/test/StaticJsonBuffer_String_Tests.cpp new file mode 100644 index 00000000..a4cb47fc --- /dev/null +++ b/test/StaticJsonBuffer_String_Tests.cpp @@ -0,0 +1,48 @@ +// Copyright Benoit Blanchon 2014-2016 +// MIT License +// +// Arduino JSON library +// https://github.com/bblanchon/ArduinoJson +// If you like this project, please add a star! + +#include +#include + +TEST(StaticJsonBuffer_String_Tests, WorksWhenBufferIsBigEnough) { + StaticJsonBuffer<6> jsonBuffer; + + StaticJsonBufferBase::String str = jsonBuffer.startString(); + str.append('h'); + str.append('e'); + str.append('l'); + str.append('l'); + str.append('o'); + + ASSERT_STREQ("hello", str.c_str()); +} + +TEST(StaticJsonBuffer_String_Tests, ReturnsNullWhenTooSmall) { + StaticJsonBuffer<5> jsonBuffer; + + StaticJsonBufferBase::String str = jsonBuffer.startString(); + str.append('h'); + str.append('e'); + str.append('l'); + str.append('l'); + str.append('o'); + + ASSERT_EQ(NULL, str.c_str()); +} + +TEST(StaticJsonBuffer_String_Tests, SizeIncreases) { + StaticJsonBuffer<5> jsonBuffer; + + StaticJsonBufferBase::String str = jsonBuffer.startString(); + ASSERT_EQ(0, jsonBuffer.size()); + + str.append('h'); + ASSERT_EQ(1, jsonBuffer.size()); + + str.c_str(); + ASSERT_EQ(2, jsonBuffer.size()); +}