Refactored StringBuilder into StringStorage

This commit is contained in:
Benoit Blanchon
2020-07-11 17:51:39 +02:00
parent 04c59985a1
commit 1600d39693
13 changed files with 128 additions and 193 deletions

View File

@ -525,7 +525,7 @@ TEST_CASE("Filtering") {
10, 10,
DeserializationError::InvalidInput, DeserializationError::InvalidInput,
"{}", "{}",
JSON_OBJECT_SIZE(0) + 8 JSON_OBJECT_SIZE(0)
}, },
{ {
// incomplete comment after key // incomplete comment after key
@ -534,7 +534,7 @@ TEST_CASE("Filtering") {
10, 10,
DeserializationError::IncompleteInput, DeserializationError::IncompleteInput,
"{}", "{}",
JSON_OBJECT_SIZE(0) + 8 JSON_OBJECT_SIZE(0)
}, },
{ {
// invalid comment after colon // invalid comment after colon
@ -730,20 +730,3 @@ TEST_CASE("Overloads") {
} }
#endif #endif
} }
TEST_CASE("StringMover::reclaim()") {
StaticJsonDocument<200> filter;
filter["a"] = true;
filter["c"] = true;
char input[] = "{\"a\":1,\"b\":2,\"c\":1}";
StaticJsonDocument<200> doc;
deserializeJson(doc, input, DeserializationOption::Filter(filter));
REQUIRE(doc.as<std::string>() == "{\"a\":1,\"c\":1}");
CHECK(input[0] == 'a');
CHECK(input[1] == 0);
CHECK(input[2] == 'c');
CHECK(input[3] == 0);
}

View File

@ -7,7 +7,7 @@ add_executable(MemoryPoolTests
allocString.cpp allocString.cpp
clear.cpp clear.cpp
size.cpp size.cpp
StringBuilder.cpp StringCopier.cpp
) )
add_test(MemoryPool MemoryPoolTests) add_test(MemoryPool MemoryPoolTests)

View File

@ -2,40 +2,44 @@
// Copyright Benoit Blanchon 2014-2020 // Copyright Benoit Blanchon 2014-2020
// MIT License // MIT License
#include <ArduinoJson/Memory/MemoryPool.hpp> #include <ArduinoJson/StringStorage/StringCopier.hpp>
#include <ArduinoJson/Memory/StringBuilder.hpp>
#include <catch.hpp> #include <catch.hpp>
using namespace ARDUINOJSON_NAMESPACE; using namespace ARDUINOJSON_NAMESPACE;
TEST_CASE("StringBuilder") { TEST_CASE("StringCopier") {
char buffer[4096]; char buffer[4096];
SECTION("Works when buffer is big enough") { SECTION("Works when buffer is big enough") {
MemoryPool pool(buffer, addPadding(JSON_STRING_SIZE(6))); MemoryPool pool(buffer, addPadding(JSON_STRING_SIZE(6)));
StringCopier str;
StringBuilder str(&pool); str.startString(&pool);
str.append("hello"); str.append("hello");
str.append('\0');
REQUIRE(str.complete() == std::string("hello")); REQUIRE(str.isValid() == true);
REQUIRE(str.c_str() == std::string("hello"));
} }
SECTION("Returns null when too small") { SECTION("Returns null when too small") {
MemoryPool pool(buffer, sizeof(void*)); MemoryPool pool(buffer, sizeof(void*));
StringCopier str;
StringBuilder str(&pool); str.startString(&pool);
str.append("hello world!"); str.append("hello world!");
REQUIRE(str.complete() == 0); REQUIRE(str.isValid() == false);
} }
SECTION("Increases size of memory pool") { SECTION("Increases size of memory pool") {
MemoryPool pool(buffer, addPadding(JSON_STRING_SIZE(6))); MemoryPool pool(buffer, addPadding(JSON_STRING_SIZE(6)));
StringCopier str;
StringBuilder str(&pool); str.startString(&pool);
str.append('h'); str.append('h');
str.complete(); str.commit(&pool);
REQUIRE(JSON_STRING_SIZE(1) == pool.size()); REQUIRE(1 == pool.size());
} }
} }

View File

@ -22,11 +22,6 @@ TEST_CASE("MemoryPool::size()") {
REQUIRE(0 == pool.size()); REQUIRE(0 == pool.size());
} }
SECTION("size() == capacity() after allocExpandableString()") {
pool.allocExpandableString();
REQUIRE(pool.size() == pool.capacity());
}
SECTION("Decreases after freezeString()") { SECTION("Decreases after freezeString()") {
StringSlot a = pool.allocExpandableString(); StringSlot a = pool.allocExpandableString();
pool.freezeString(a, 1); pool.freezeString(a, 1);

View File

@ -12,12 +12,14 @@ using namespace ARDUINOJSON_NAMESPACE;
static void testCodepoint(uint32_t codepoint, std::string expected) { static void testCodepoint(uint32_t codepoint, std::string expected) {
char buffer[4096]; char buffer[4096];
MemoryPool pool(buffer, 4096); MemoryPool pool(buffer, 4096);
StringBuilder str(&pool); StringCopier str;
str.startString(&pool);
CAPTURE(codepoint); CAPTURE(codepoint);
Utf8::encodeCodepoint(codepoint, str); Utf8::encodeCodepoint(codepoint, str);
REQUIRE(str.complete() == expected); str.append('\0');
REQUIRE(str.c_str() == expected);
} }
TEST_CASE("Utf8::encodeCodepoint()") { TEST_CASE("Utf8::encodeCodepoint()") {

View File

@ -32,9 +32,8 @@ deserialize(JsonDocument &doc, const TString &input, NestingLimit nestingLimit,
TFilter filter) { TFilter filter) {
Reader<TString> reader(input); Reader<TString> reader(input);
doc.clear(); doc.clear();
return makeDeserializer<TDeserializer>( return makeDeserializer<TDeserializer>(doc.memoryPool(), reader,
doc.memoryPool(), reader, makeStringStorage(input))
makeStringStorage(doc.memoryPool(), input))
.parse(doc.data(), filter, nestingLimit); .parse(doc.data(), filter, nestingLimit);
} }
// //
@ -48,9 +47,8 @@ DeserializationError deserialize(JsonDocument &doc, TChar *input,
TFilter filter) { TFilter filter) {
BoundedReader<TChar *> reader(input, inputSize); BoundedReader<TChar *> reader(input, inputSize);
doc.clear(); doc.clear();
return makeDeserializer<TDeserializer>( return makeDeserializer<TDeserializer>(doc.memoryPool(), reader,
doc.memoryPool(), reader, makeStringStorage(input))
makeStringStorage(doc.memoryPool(), input))
.parse(doc.data(), filter, nestingLimit); .parse(doc.data(), filter, nestingLimit);
} }
// //
@ -62,9 +60,8 @@ DeserializationError deserialize(JsonDocument &doc, TStream &input,
NestingLimit nestingLimit, TFilter filter) { NestingLimit nestingLimit, TFilter filter) {
Reader<TStream> reader(input); Reader<TStream> reader(input);
doc.clear(); doc.clear();
return makeDeserializer<TDeserializer>( return makeDeserializer<TDeserializer>(doc.memoryPool(), reader,
doc.memoryPool(), reader, makeStringStorage(input))
makeStringStorage(doc.memoryPool(), input))
.parse(doc.data(), filter, nestingLimit); .parse(doc.data(), filter, nestingLimit);
} }

View File

@ -19,22 +19,10 @@ namespace ARDUINOJSON_NAMESPACE {
template <typename TReader, typename TStringStorage> template <typename TReader, typename TStringStorage>
class JsonDeserializer { class JsonDeserializer {
typedef typename remove_reference<TStringStorage>::type::StringBuilder
StringBuilder;
struct StringOrError {
DeserializationError err;
const char *value;
StringOrError(DeserializationError e) : err(e) {}
StringOrError(DeserializationError::Code c) : err(c) {}
StringOrError(const char *s) : err(DeserializationError::Ok), value(s) {}
};
public: public:
JsonDeserializer(MemoryPool &pool, TReader reader, JsonDeserializer(MemoryPool &pool, TReader reader,
TStringStorage stringStorage) TStringStorage stringStorage)
: _pool(&pool), _stringStorage(stringStorage), _latch(reader) {} : _stringStorage(stringStorage), _latch(reader), _pool(&pool) {}
template <typename TFilter> template <typename TFilter>
DeserializationError parse(VariantData &variant, TFilter filter, DeserializationError parse(VariantData &variant, TFilter filter,
@ -224,9 +212,10 @@ class JsonDeserializer {
// Read each key value pair // Read each key value pair
for (;;) { for (;;) {
_stringStorage.startString(_pool);
// Parse key // Parse key
StringOrError key = parseKey(); err = parseKey();
err = key.err; // <- this trick saves 62 bytes on AVR
if (err) if (err)
return err; return err;
@ -237,17 +226,21 @@ class JsonDeserializer {
if (!eat(':')) if (!eat(':'))
return DeserializationError::InvalidInput; return DeserializationError::InvalidInput;
TFilter memberFilter = filter[key.value]; const char *key = _stringStorage.c_str();
TFilter memberFilter = filter[key];
if (memberFilter.allow()) { if (memberFilter.allow()) {
VariantData *variant = object.getMember(adaptString(key.value)); VariantData *variant = object.getMember(adaptString(key));
if (!variant) { if (!variant) {
_stringStorage.commit(_pool);
// Allocate slot in object // Allocate slot in object
VariantSlot *slot = object.addSlot(_pool); VariantSlot *slot = object.addSlot(_pool);
if (!slot) if (!slot)
return DeserializationError::NoMemory; return DeserializationError::NoMemory;
slot->setOwnedKey(make_not_null(key.value)); slot->setOwnedKey(make_not_null(key));
variant = slot->data(); variant = slot->data();
} }
@ -257,7 +250,6 @@ class JsonDeserializer {
if (err) if (err)
return err; return err;
} else { } else {
_stringStorage.reclaim(key.value);
err = skipVariant(nestingLimit.decrement()); err = skipVariant(nestingLimit.decrement());
if (err) if (err)
return err; return err;
@ -332,7 +324,7 @@ class JsonDeserializer {
} }
} }
StringOrError parseKey() { DeserializationError parseKey() {
if (isQuote(current())) { if (isQuote(current())) {
return parseQuotedString(); return parseQuotedString();
} else { } else {
@ -341,15 +333,16 @@ class JsonDeserializer {
} }
DeserializationError parseStringValue(VariantData &variant) { DeserializationError parseStringValue(VariantData &variant) {
StringOrError result = parseQuotedString(); _stringStorage.startString(_pool);
if (result.err) DeserializationError err = parseQuotedString();
return result.err; if (err)
variant.setOwnedString(make_not_null(result.value)); return err;
_stringStorage.commit(_pool);
variant.setOwnedString(make_not_null(_stringStorage.c_str()));
return DeserializationError::Ok; return DeserializationError::Ok;
} }
StringOrError parseQuotedString() { DeserializationError parseQuotedString() {
StringBuilder builder = _stringStorage.startString();
#if ARDUINOJSON_DECODE_UNICODE #if ARDUINOJSON_DECODE_UNICODE
Utf16::Codepoint codepoint; Utf16::Codepoint codepoint;
#endif #endif
@ -377,7 +370,7 @@ class JsonDeserializer {
if (err) if (err)
return err; return err;
if (codepoint.append(codeunit)) if (codepoint.append(codeunit))
Utf8::encodeCodepoint(codepoint.value(), builder); Utf8::encodeCodepoint(codepoint.value(), _stringStorage);
continue; continue;
#else #else
return DeserializationError::NotSupported; return DeserializationError::NotSupported;
@ -390,35 +383,37 @@ class JsonDeserializer {
move(); move();
} }
builder.append(c); _stringStorage.append(c);
} }
const char *result = builder.complete(); _stringStorage.append('\0');
if (!result)
if (!_stringStorage.isValid())
return DeserializationError::NoMemory; return DeserializationError::NoMemory;
return result;
return DeserializationError::Ok;
} }
StringOrError parseNonQuotedString() { DeserializationError parseNonQuotedString() {
StringBuilder builder = _stringStorage.startString();
char c = current(); char c = current();
ARDUINOJSON_ASSERT(c); ARDUINOJSON_ASSERT(c);
if (canBeInNonQuotedString(c)) { // no quotes if (canBeInNonQuotedString(c)) { // no quotes
do { do {
move(); move();
builder.append(c); _stringStorage.append(c);
c = current(); c = current();
} while (canBeInNonQuotedString(c)); } while (canBeInNonQuotedString(c));
} else { } else {
return DeserializationError::InvalidInput; return DeserializationError::InvalidInput;
} }
const char *result = builder.complete(); _stringStorage.append('\0');
if (!result)
if (!_stringStorage.isValid())
return DeserializationError::NoMemory; return DeserializationError::NoMemory;
return result;
return DeserializationError::Ok;
} }
DeserializationError skipString() { DeserializationError skipString() {
@ -597,9 +592,9 @@ class JsonDeserializer {
} }
} }
MemoryPool *_pool;
TStringStorage _stringStorage; TStringStorage _stringStorage;
Latch<TReader> _latch; Latch<TReader> _latch;
MemoryPool *_pool;
}; };
// deserializeJson(JsonDocument&, const std::string&, ...) // deserializeJson(JsonDocument&, const std::string&, ...)

View File

@ -77,21 +77,16 @@ class MemoryPool {
StringSlot s; StringSlot s;
s.value = _left; s.value = _left;
s.size = size_t(_right - _left); s.size = size_t(_right - _left);
_left = _right;
checkInvariants(); checkInvariants();
return s; return s;
} }
void freezeString(StringSlot& s, size_t newSize) { void freezeString(StringSlot& s, size_t newSize) {
_left -= (s.size - newSize); _left = (s.value + newSize);
s.size = newSize; s.size = newSize;
checkInvariants(); checkInvariants();
} }
void reclaimLastString(const char* s) {
_left = const_cast<char*>(s);
}
void clear() { void clear() {
_left = _begin; _left = _begin;
_right = _end; _right = _end;

View File

@ -1,51 +0,0 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2020
// MIT License
#pragma once
#include <ArduinoJson/Memory/MemoryPool.hpp>
namespace ARDUINOJSON_NAMESPACE {
class StringBuilder {
public:
explicit StringBuilder(MemoryPool* parent) : _parent(parent), _size(0) {
_slot = _parent->allocExpandableString();
}
void append(const char* s) {
while (*s) append(*s++);
}
void append(const char* s, size_t n) {
while (n-- > 0) append(*s++);
}
void append(char c) {
if (!_slot.value)
return;
if (_size >= _slot.size) {
_slot.value = 0;
return;
}
_slot.value[_size++] = c;
}
char* complete() {
append('\0');
if (_slot.value) {
_parent->freezeString(_slot, _size);
}
return _slot.value;
}
private:
MemoryPool* _parent;
size_t _size;
StringSlot _slot;
};
} // namespace ARDUINOJSON_NAMESPACE

View File

@ -15,9 +15,6 @@ namespace ARDUINOJSON_NAMESPACE {
template <typename TReader, typename TStringStorage> template <typename TReader, typename TStringStorage>
class MsgPackDeserializer { class MsgPackDeserializer {
typedef typename remove_reference<TStringStorage>::type::StringBuilder
StringBuilder;
public: public:
MsgPackDeserializer(MemoryPool &pool, TReader reader, MsgPackDeserializer(MemoryPool &pool, TReader reader,
TStringStorage stringStorage) TStringStorage stringStorage)
@ -241,16 +238,18 @@ class MsgPackDeserializer {
} }
DeserializationError readString(const char *&result, size_t n) { DeserializationError readString(const char *&result, size_t n) {
StringBuilder builder = _stringStorage.startString(); _stringStorage.startString(_pool);
for (; n; --n) { for (; n; --n) {
uint8_t c; uint8_t c;
if (!readBytes(c)) if (!readBytes(c))
return DeserializationError::IncompleteInput; return DeserializationError::IncompleteInput;
builder.append(static_cast<char>(c)); _stringStorage.append(static_cast<char>(c));
} }
result = builder.complete(); _stringStorage.append('\0');
if (!result) if (!_stringStorage.isValid())
return DeserializationError::NoMemory; return DeserializationError::NoMemory;
_stringStorage.commit(_pool);
result = _stringStorage.c_str();
return DeserializationError::Ok; return DeserializationError::Ok;
} }

View File

@ -5,25 +5,51 @@
#pragma once #pragma once
#include <ArduinoJson/Memory/MemoryPool.hpp> #include <ArduinoJson/Memory/MemoryPool.hpp>
#include <ArduinoJson/Memory/StringBuilder.hpp>
namespace ARDUINOJSON_NAMESPACE { namespace ARDUINOJSON_NAMESPACE {
class StringCopier { class StringCopier {
public: public:
typedef ARDUINOJSON_NAMESPACE::StringBuilder StringBuilder; void startString(MemoryPool* pool) {
_slot = pool->allocExpandableString();
StringCopier(MemoryPool* pool) : _pool(pool) {} _size = 0;
StringBuilder startString() {
return StringBuilder(_pool);
} }
void reclaim(const char* s) { void commit(MemoryPool* pool) {
_pool->reclaimLastString(s); ARDUINOJSON_ASSERT(_slot.value);
pool->freezeString(_slot, _size);
}
void append(const char* s) {
while (*s) append(*s++);
}
void append(const char* s, size_t n) {
while (n-- > 0) append(*s++);
}
void append(char c) {
if (!_slot.value)
return;
if (_size >= _slot.size) {
_slot.value = 0;
return;
}
_slot.value[_size++] = c;
}
bool isValid() {
return _slot.value != 0;
}
const char* c_str() {
return _slot.value;
} }
private: private:
MemoryPool* _pool; size_t _size;
StringSlot _slot;
}; };
} // namespace ARDUINOJSON_NAMESPACE } // namespace ARDUINOJSON_NAMESPACE

View File

@ -10,36 +10,28 @@ namespace ARDUINOJSON_NAMESPACE {
class StringMover { class StringMover {
public: public:
class StringBuilder { StringMover(char* ptr) : _writePtr(ptr) {}
public:
StringBuilder(char** ptr) : _writePtr(ptr), _startPtr(*ptr) {}
void append(char c) { void startString(MemoryPool*) {
*(*_writePtr)++ = char(c); _startPtr = _writePtr;
}
char* complete() const {
*(*_writePtr)++ = 0;
return _startPtr;
}
private:
char** _writePtr;
char* _startPtr;
};
StringMover(char* ptr) : _ptr(ptr) {}
StringBuilder startString() {
return StringBuilder(&_ptr);
} }
// recover memory from last string void commit(MemoryPool*) const {}
void reclaim(const char* str) {
_ptr = const_cast<char*>(str); void append(char c) {
*_writePtr++ = c;
}
bool isValid() const {
return true;
}
const char* c_str() const {
return _startPtr;
} }
private: private:
char* _ptr; char* _writePtr;
char* _startPtr;
}; };
} // namespace ARDUINOJSON_NAMESPACE } // namespace ARDUINOJSON_NAMESPACE

View File

@ -13,8 +13,8 @@ template <typename TInput, typename Enable = void>
struct StringStorage { struct StringStorage {
typedef StringCopier type; typedef StringCopier type;
static type create(MemoryPool& pool, TInput&) { static type create(TInput&) {
return type(&pool); return type();
} }
}; };
@ -23,20 +23,18 @@ struct StringStorage<TChar*,
typename enable_if<!is_const<TChar>::value>::type> { typename enable_if<!is_const<TChar>::value>::type> {
typedef StringMover type; typedef StringMover type;
static type create(MemoryPool&, TChar* input) { static type create(TChar* input) {
return type(reinterpret_cast<char*>(input)); return type(reinterpret_cast<char*>(input));
} }
}; };
template <typename TInput> template <typename TInput>
typename StringStorage<TInput>::type makeStringStorage(MemoryPool& pool, typename StringStorage<TInput>::type makeStringStorage(TInput& input) {
TInput& input) { return StringStorage<TInput>::create(input);
return StringStorage<TInput>::create(pool, input);
} }
template <typename TChar> template <typename TChar>
typename StringStorage<TChar*>::type makeStringStorage(MemoryPool& pool, typename StringStorage<TChar*>::type makeStringStorage(TChar* input) {
TChar* input) { return StringStorage<TChar*>::create(input);
return StringStorage<TChar*>::create(pool, input);
} }
} // namespace ARDUINOJSON_NAMESPACE } // namespace ARDUINOJSON_NAMESPACE