1
0
Fork 0
arangodb/3rdParty/velocypack/src/Builder.cpp

849 lines
26 KiB
C++

////////////////////////////////////////////////////////////////////////////////
/// @brief Library to build up VPack documents.
///
/// DISCLAIMER
///
/// Copyright 2015 ArangoDB GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Max Neunhoeffer
/// @author Jan Steemann
/// @author Copyright 2015, ArangoDB GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
#include <unordered_set>
#include "velocypack/velocypack-common.h"
#include "velocypack/Builder.h"
#include "velocypack/Dumper.h"
#include "velocypack/Iterator.h"
#include "velocypack/Sink.h"
using namespace arangodb::velocypack;
std::string Builder::toString() const {
Options options;
options.prettyPrint = true;
std::string buffer;
StringSink sink(&buffer);
Dumper::dump(slice(), &sink, &options);
return std::move(buffer);
}
std::string Builder::toJson() const {
std::string buffer;
StringSink sink(&buffer);
Dumper::dump(slice(), &sink);
return std::move(buffer);
}
void Builder::doActualSort(std::vector<SortEntry>& entries) {
VELOCYPACK_ASSERT(entries.size() > 1);
std::sort(entries.begin(), entries.end(),
[](SortEntry const& a, SortEntry const& b) {
// return true iff a < b:
uint8_t const* pa = a.nameStart;
uint64_t sizea = a.nameSize;
uint8_t const* pb = b.nameStart;
uint64_t sizeb = b.nameSize;
size_t const compareLength = checkOverflow((std::min)(sizea, sizeb));
int res = memcmp(pa, pb, compareLength);
return (res < 0 || (res == 0 && sizea < sizeb));
});
};
uint8_t const* Builder::findAttrName(uint8_t const* base, uint64_t& len,
Options const* options) {
uint8_t const b = *base;
if (b >= 0x40 && b <= 0xbe) {
// short UTF-8 string
len = b - 0x40;
return base + 1;
}
if (b == 0xbf) {
// long UTF-8 string
len = 0;
// read string length
for (size_t i = 8; i >= 1; i--) {
len = (len << 8) + base[i];
}
return base + 1 + 8; // string starts here
}
// translate attribute name
Slice s(base, options);
return findAttrName(s.makeKey().start(), len, options);
}
void Builder::sortObjectIndexShort(uint8_t* objBase,
std::vector<ValueLength>& offsets,
Options const* options) {
auto cmp = [&](ValueLength a, ValueLength b) -> bool {
uint8_t const* aa = objBase + a;
uint8_t const* bb = objBase + b;
if (*aa >= 0x40 && *aa <= 0xbe && *bb >= 0x40 && *bb <= 0xbe) {
// The fast path, short strings:
uint8_t m = (std::min)(*aa - 0x40, *bb - 0x40);
int c = memcmp(aa + 1, bb + 1, checkOverflow(m));
return (c < 0 || (c == 0 && *aa < *bb));
} else {
uint64_t lena;
uint64_t lenb;
aa = findAttrName(aa, lena, options);
bb = findAttrName(bb, lenb, options);
uint64_t m = (std::min)(lena, lenb);
int c = memcmp(aa, bb, checkOverflow(m));
return (c < 0 || (c == 0 && lena < lenb));
}
};
std::sort(offsets.begin(), offsets.end(), cmp);
}
void Builder::sortObjectIndexLong(uint8_t* objBase,
std::vector<ValueLength>& offsets,
Options const* options) {
// on some platforms we can use a thread-local vector
#if __llvm__ == 1
// nono thread local
std::vector<Builder::SortEntry> entries;
#elif defined(_WIN32) && defined(_MSC_VER)
std::vector<Builder::SortEntry> entries;
#else
// thread local vector for sorting large object attributes
thread_local std::vector<Builder::SortEntry> entries;
entries.clear();
#endif
size_t const n = offsets.size();
entries.reserve(n);
for (size_t i = 0; i < n; i++) {
SortEntry e;
e.offset = offsets[i];
e.nameStart = findAttrName(objBase + e.offset, e.nameSize, options);
entries.push_back(e);
}
VELOCYPACK_ASSERT(entries.size() == n);
doActualSort(entries);
// copy back the sorted offsets
for (size_t i = 0; i < n; i++) {
offsets[i] = entries[i].offset;
}
}
void Builder::sortObjectIndex(uint8_t* objBase,
std::vector<ValueLength>& offsets,
Options const* options) {
if (offsets.size() > 32) {
sortObjectIndexLong(objBase, offsets, options);
} else {
sortObjectIndexShort(objBase, offsets, options);
}
}
void Builder::removeLast() {
if (_stack.empty()) {
throw Exception(Exception::BuilderNeedOpenCompound);
}
ValueLength& tos = _stack.back();
std::vector<ValueLength>& index = _index[_stack.size() - 1];
if (index.empty()) {
throw Exception(Exception::BuilderNeedSubvalue);
}
_pos = tos + index.back();
index.pop_back();
}
Builder& Builder::close() {
if (isClosed()) {
throw Exception(Exception::BuilderNeedOpenCompound);
}
ValueLength& tos = _stack.back();
uint8_t const head = _start[tos];
VELOCYPACK_ASSERT(head == 0x06 || head == 0x0b || head == 0x13 ||
head == 0x14);
bool const isArray = (head == 0x06 || head == 0x13);
std::vector<ValueLength>& index = _index[_stack.size() - 1];
if (index.empty()) {
// empty Array or Object
_start[tos] = (isArray ? 0x01 : 0x0a);
VELOCYPACK_ASSERT(_pos == tos + 9);
_pos -= 8; // no bytelength and number subvalues needed
_stack.pop_back();
// Intentionally leave _index[depth] intact to avoid future allocs!
return *this;
}
// From now on index.size() > 0
VELOCYPACK_ASSERT(index.size() > 0);
// check if we can use the compact Array / Object format
if (index.size() > 1 && ((head == 0x13 || head == 0x14) ||
(head == 0x06 && options->buildUnindexedArrays) ||
(head == 0x0b && options->buildUnindexedObjects))) {
// use compact notation
ValueLength nLen =
getVariableValueLength(static_cast<ValueLength>(index.size()));
VELOCYPACK_ASSERT(nLen > 0);
ValueLength byteSize = _pos - (tos + 8) + nLen;
VELOCYPACK_ASSERT(byteSize > 0);
ValueLength bLen = getVariableValueLength(byteSize);
byteSize += bLen;
if (getVariableValueLength(byteSize) != bLen) {
byteSize += 1;
bLen += 1;
}
if (bLen < 9) {
// can only use compact notation if total byte length is at most 8 bytes
// long
_start[tos] = (isArray ? 0x13 : 0x14);
ValueLength targetPos = 1 + bLen;
if (_pos > (tos + 9)) {
ValueLength len = _pos - (tos + 9);
memmove(_start + tos + targetPos, _start + tos + 9, checkOverflow(len));
}
// store byte length
VELOCYPACK_ASSERT(byteSize > 0);
storeVariableValueLength<false>(_start + tos + 1, byteSize);
// need additional memory for storing the number of values
if (nLen > 8 - bLen) {
reserveSpace(nLen);
}
storeVariableValueLength<true>(_start + tos + byteSize - 1,
static_cast<ValueLength>(index.size()));
_pos -= 8;
_pos += nLen + bLen;
_stack.pop_back();
return *this;
}
}
// fix head byte in case a compact Array / Object was originally requested
_start[tos] = (isArray ? 0x06 : 0x0b);
bool needIndexTable = true;
bool needNrSubs = true;
if (index.size() == 1) {
needIndexTable = false;
if (_start[tos] == 0x06) {
needNrSubs = false;
}
// For objects we leave needNrSubs at true here!
} else if (_start[tos] == 0x06 && // an Array
(_pos - tos) - index[0] == index.size() * (index[1] - index[0])) {
// In this case it could be that all entries have the same length
// and we do not need an offset table at all:
bool noTable = true;
ValueLength const subLen = index[1] - index[0];
if ((_pos - tos) - index[index.size() - 1] != subLen) {
noTable = false;
} else {
for (size_t i = 1; i < index.size() - 1; i++) {
if (index[i + 1] - index[i] != subLen) {
noTable = false;
break;
}
}
}
if (noTable) {
needIndexTable = false;
needNrSubs = false;
}
}
// First determine byte length and its format:
unsigned int offsetSize;
// can be 1, 2, 4 or 8 for the byte width of the offsets,
// the byte length and the number of subvalues:
if (_pos - tos + (needIndexTable ? index.size() : 0) - (needNrSubs ? 6 : 7) <=
0xff) {
// We have so far used _pos - tos bytes, including the reserved 8
// bytes for byte length and number of subvalues. In the 1-byte number
// case we would win back 6 bytes but would need one byte per subvalue
// for the index table
offsetSize = 1;
} else if (_pos - tos + (needIndexTable ? 2 * index.size() : 0) <= 0xffff) {
offsetSize = 2;
} else if (_pos - tos + (needIndexTable ? 4 * index.size() : 0) <=
0xffffffffu) {
offsetSize = 4;
} else {
offsetSize = 8;
}
// Maybe we need to move down data:
if (offsetSize == 1) {
unsigned int targetPos = 3;
if (!needIndexTable && _start[tos] == 0x06) {
targetPos = 2;
}
if (_pos > (tos + 9)) {
ValueLength len = _pos - (tos + 9);
memmove(_start + tos + targetPos, _start + tos + 9, checkOverflow(len));
}
_pos -= (9 - targetPos);
for (size_t i = 0; i < index.size(); i++) {
index[i] -= (9 - targetPos);
}
}
// One could move down things in the offsetSize == 2 case as well,
// since we only need 4 bytes in the beginning. However, saving these
// 4 bytes has been sacrificed on the Altar of Performance.
// Now build the table:
if (needIndexTable) {
ValueLength tableBase;
reserveSpace(offsetSize * index.size() + (offsetSize == 8 ? 8 : 0));
tableBase = _pos;
_pos += offsetSize * index.size();
if (_start[tos] == 0x0b) {
// Object
if (!options->sortAttributeNames) {
_start[tos] = 0x0f; // unsorted
} else if (index.size() >= 2 && options->sortAttributeNames) {
sortObjectIndex(_start + tos, index, options);
}
}
for (size_t i = 0; i < index.size(); i++) {
uint64_t x = index[i];
for (size_t j = 0; j < offsetSize; j++) {
_start[tableBase + offsetSize * i + j] = x & 0xff;
x >>= 8;
}
}
} else { // no index table
if (_start[tos] == 0x06) {
_start[tos] = 0x02;
}
}
// Finally fix the byte width in the type byte:
if (offsetSize > 1) {
if (offsetSize == 2) {
_start[tos] += 1;
} else if (offsetSize == 4) {
_start[tos] += 2;
} else { // offsetSize == 8
_start[tos] += 3;
if (needNrSubs) {
appendLength(index.size(), 8);
}
}
}
// Fix the byte length in the beginning:
ValueLength x = _pos - tos;
for (unsigned int i = 1; i <= offsetSize; i++) {
_start[tos + i] = x & 0xff;
x >>= 8;
}
if (offsetSize < 8 && needNrSubs) {
x = index.size();
for (unsigned int i = offsetSize + 1; i <= 2 * offsetSize; i++) {
_start[tos + i] = x & 0xff;
x >>= 8;
}
}
// And, if desired, check attribute uniqueness:
if (options->checkAttributeUniqueness && index.size() > 1 &&
_start[tos] >= 0x0b) {
// check uniqueness of attribute names
checkAttributeUniqueness(Slice(_start + tos, options));
}
// Now the array or object is complete, we pop a ValueLength
// off the _stack:
_stack.pop_back();
// Intentionally leave _index[depth] intact to avoid future allocs!
return *this;
}
// checks whether an Object value has a specific key attribute
bool Builder::hasKey(std::string const& key) const {
if (_stack.empty()) {
throw Exception(Exception::BuilderNeedOpenObject);
}
ValueLength const& tos = _stack.back();
if (_start[tos] != 0x0b && _start[tos] != 0x14) {
throw Exception(Exception::BuilderNeedOpenObject);
}
std::vector<ValueLength> const& index = _index[_stack.size() - 1];
if (index.empty()) {
return false;
}
for (size_t i = 0; i < index.size(); ++i) {
Slice s(_start + tos + index[i], options);
if (s.makeKey().isEqualString(key)) {
return true;
}
}
return false;
}
// return the value for a specific key of an Object value
Slice Builder::getKey(std::string const& key) const {
if (_stack.empty()) {
throw Exception(Exception::BuilderNeedOpenObject);
}
ValueLength const tos = _stack.back();
if (_start[tos] != 0x0b && _start[tos] != 0x14) {
throw Exception(Exception::BuilderNeedOpenObject);
}
std::vector<ValueLength> const& index = _index[_stack.size() - 1];
if (index.empty()) {
return Slice();
}
for (size_t i = 0; i < index.size(); ++i) {
Slice s(_start + tos + index[i], options);
if (s.makeKey().isEqualString(key)) {
return Slice(s.start() + s.byteSize(), options);
}
}
return Slice();
}
uint8_t* Builder::set(Value const& item) {
auto const oldPos = _start + _pos;
auto ctype = item.cType();
checkKeyIsString(item.valueType() == ValueType::String);
// This method builds a single further VPack item at the current
// append position. If this is an array or object, then an index
// table is created and a new ValueLength is pushed onto the stack.
switch (item.valueType()) {
case ValueType::None: {
throw Exception(Exception::BuilderUnexpectedType,
"Cannot set a ValueType::None");
}
case ValueType::Null: {
reserveSpace(1);
_start[_pos++] = 0x18;
break;
}
case ValueType::Bool: {
if (ctype != Value::CType::Bool) {
throw Exception(Exception::BuilderUnexpectedValue,
"Must give bool for ValueType::Bool");
}
reserveSpace(1);
if (item.getBool()) {
_start[_pos++] = 0x1a;
} else {
_start[_pos++] = 0x19;
}
break;
}
case ValueType::Double: {
static_assert(sizeof(double) == sizeof(uint64_t),
"size of double is not 8 bytes");
double v = 0.0;
uint64_t x;
switch (ctype) {
case Value::CType::Double:
v = item.getDouble();
break;
case Value::CType::Int64:
v = static_cast<double>(item.getInt64());
break;
case Value::CType::UInt64:
v = static_cast<double>(item.getUInt64());
break;
default:
throw Exception(Exception::BuilderUnexpectedValue,
"Must give number for ValueType::Double");
}
reserveSpace(1 + sizeof(double));
_start[_pos++] = 0x1b;
memcpy(&x, &v, sizeof(double));
appendLength(x, 8);
break;
}
case ValueType::External: {
if (options->disallowExternals) {
// External values explicitly disallowed as a security
// precaution
throw Exception(Exception::BuilderExternalsDisallowed);
}
if (ctype != Value::CType::VoidPtr) {
throw Exception(Exception::BuilderUnexpectedValue,
"Must give void pointer for ValueType::External");
}
reserveSpace(1 + sizeof(void*));
// store pointer. this doesn't need to be portable
_start[_pos++] = 0x1d;
void const* value = item.getExternal();
memcpy(_start + _pos, &value, sizeof(void*));
_pos += sizeof(void*);
break;
}
case ValueType::SmallInt: {
int64_t vv = 0;
switch (ctype) {
case Value::CType::Double:
vv = static_cast<int64_t>(item.getDouble());
break;
case Value::CType::Int64:
vv = item.getInt64();
break;
case Value::CType::UInt64:
vv = static_cast<int64_t>(item.getUInt64());
break;
default:
throw Exception(Exception::BuilderUnexpectedValue,
"Must give number for ValueType::SmallInt");
}
if (vv < -6 || vv > 9) {
throw Exception(Exception::NumberOutOfRange,
"Number out of range of ValueType::SmallInt");
}
reserveSpace(1);
if (vv >= 0) {
_start[_pos++] = static_cast<uint8_t>(vv + 0x30);
} else {
_start[_pos++] = static_cast<uint8_t>(vv + 0x40);
}
break;
}
case ValueType::Int: {
int64_t v;
switch (ctype) {
case Value::CType::Double:
v = static_cast<int64_t>(item.getDouble());
break;
case Value::CType::Int64:
v = item.getInt64();
break;
case Value::CType::UInt64:
v = toInt64(item.getUInt64());
break;
default:
throw Exception(Exception::BuilderUnexpectedValue,
"Must give number for ValueType::Int");
}
addInt(v);
break;
}
case ValueType::UInt: {
uint64_t v = 0;
switch (ctype) {
case Value::CType::Double:
if (item.getDouble() < 0.0) {
throw Exception(
Exception::BuilderUnexpectedValue,
"Must give non-negative number for ValueType::UInt");
}
v = static_cast<uint64_t>(item.getDouble());
break;
case Value::CType::Int64:
if (item.getInt64() < 0) {
throw Exception(
Exception::BuilderUnexpectedValue,
"Must give non-negative number for ValueType::UInt");
}
v = static_cast<uint64_t>(item.getInt64());
break;
case Value::CType::UInt64:
v = item.getUInt64();
break;
default:
throw Exception(Exception::BuilderUnexpectedValue,
"Must give number for ValueType::UInt");
}
addUInt(v);
break;
}
case ValueType::UTCDate: {
int64_t v;
switch (ctype) {
case Value::CType::Double:
v = static_cast<int64_t>(item.getDouble());
break;
case Value::CType::Int64:
v = item.getInt64();
break;
case Value::CType::UInt64:
v = toInt64(item.getUInt64());
break;
default:
throw Exception(Exception::BuilderUnexpectedValue,
"Must give number for ValueType::UTCDate");
}
addUTCDate(v);
break;
}
case ValueType::String: {
if (ctype != Value::CType::String && ctype != Value::CType::CharPtr) {
throw Exception(
Exception::BuilderUnexpectedValue,
"Must give a string or char const* for ValueType::String");
}
std::string const* s;
std::string value;
if (ctype == Value::CType::String) {
s = item.getString();
} else {
value = item.getCharPtr();
s = &value;
}
size_t const size = s->size();
if (size <= 126) {
// short string
reserveSpace(1 + size);
_start[_pos++] = static_cast<uint8_t>(0x40 + size);
memcpy(_start + _pos, s->c_str(), size);
} else {
// long string
reserveSpace(1 + 8 + size);
_start[_pos++] = 0xbf;
appendLength(size, 8);
memcpy(_start + _pos, s->c_str(), size);
}
_pos += size;
break;
}
case ValueType::Array: {
addArray(item._unindexed);
break;
}
case ValueType::Object: {
addObject(item._unindexed);
break;
}
case ValueType::Binary: {
if (ctype != Value::CType::String && ctype != Value::CType::CharPtr) {
throw Exception(
Exception::BuilderUnexpectedValue,
"Must provide std::string or char const* for ValueType::Binary");
}
std::string const* s;
std::string value;
if (ctype == Value::CType::String) {
s = item.getString();
} else {
value = item.getCharPtr();
s = &value;
}
ValueLength v = s->size();
appendUInt(v, 0xbf);
memcpy(_start + _pos, s->c_str(), checkOverflow(v));
_pos += v;
break;
}
case ValueType::MinKey: {
reserveSpace(1);
_start[_pos++] = 0x1e;
break;
}
case ValueType::MaxKey: {
reserveSpace(1);
_start[_pos++] = 0x1f;
break;
}
case ValueType::BCD: {
throw Exception(Exception::NotImplemented);
}
case ValueType::Custom: {
throw Exception(Exception::BuilderUnexpectedType,
"Cannot set a ValueType::Custom with this method");
}
}
return oldPos;
}
uint8_t* Builder::set(Slice const& item) {
checkKeyIsString(item.isString());
ValueLength const l = item.byteSize();
reserveSpace(l);
memcpy(_start + _pos, item.start(), checkOverflow(l));
_pos += l;
return _start + _pos - l;
}
uint8_t* Builder::set(ValuePair const& pair) {
// This method builds a single further VPack item at the current
// append position. This is the case for ValueType::String,
// ValueType::Binary, or ValueType::Custom, which can be built
// with two pieces of information
checkKeyIsString(pair.valueType() == ValueType::String);
if (pair.valueType() == ValueType::Binary) {
uint64_t v = pair.getSize();
appendUInt(v, 0xbf);
memcpy(_start + _pos, pair.getStart(), checkOverflow(v));
_pos += v;
return nullptr; // unused here
} else if (pair.valueType() == ValueType::String) {
uint64_t size = pair.getSize();
if (size > 126) {
// long string
reserveSpace(1 + 8 + size);
_start[_pos++] = 0xbf;
appendLength(size, 8);
_pos += size;
} else {
// short string
reserveSpace(1 + size);
_start[_pos++] = static_cast<uint8_t>(0x40 + size);
_pos += size;
}
// Note that the data is not filled in! It is the responsibility
// of the caller to fill in
// _start + _pos - size .. _start + _pos - 1
// with valid UTF-8!
return _start + _pos - size;
} else if (pair.valueType() == ValueType::Custom) {
// We only reserve space here, the caller has to fill in the custom type
uint64_t size = pair.getSize();
reserveSpace(size);
uint8_t const* p = pair.getStart();
if (p != nullptr) {
memcpy(_start + _pos, p, checkOverflow(size));
}
_pos += size;
return _start + _pos - size;
}
throw Exception(Exception::BuilderUnexpectedType,
"Only ValueType::Binary, ValueType::String and "
"ValueType::Custom are valid for ValuePair argument");
}
void Builder::checkAttributeUniqueness(Slice const& obj) const {
VELOCYPACK_ASSERT(options->checkAttributeUniqueness == true);
ValueLength const n = obj.length();
if (obj.isSorted()) {
// object attributes are sorted
Slice previous = obj.keyAt(0);
ValueLength len;
char const* p = previous.getString(len);
// compare each two adjacent attribute names
for (ValueLength i = 1; i < n; ++i) {
Slice current = obj.keyAt(i);
// keyAt() guarantees a string as returned type
VELOCYPACK_ASSERT(current.isString());
ValueLength len2;
char const* q = current.getString(len2);
if (len == len2 && memcmp(p, q, checkOverflow(len2)) == 0) {
// identical key
throw Exception(Exception::DuplicateAttributeName);
}
// re-use already calculated values for next round
len = len2;
p = q;
}
} else {
std::unordered_set<std::string> keys;
for (ValueLength i = 0; i < n; ++i) {
// note: keyAt() already translates integer attributes
Slice key = obj.keyAt(i);
// keyAt() guarantees a string as returned type
VELOCYPACK_ASSERT(key.isString());
if (!keys.emplace(key.copyString()).second) {
throw Exception(Exception::DuplicateAttributeName);
}
}
}
}
uint8_t* Builder::add(std::string const& attrName, Value const& sub) {
return addInternal<Value>(attrName, sub);
}
uint8_t* Builder::add(std::string const& attrName, ValuePair const& sub) {
return addInternal<ValuePair>(attrName, sub);
}
uint8_t* Builder::add(std::string const& attrName, Slice const& sub) {
return addInternal<Slice>(attrName, sub);
}
// Add all subkeys and subvalues into an object from an ObjectIterator
// and leaves open the object intentionally
uint8_t* Builder::add(ObjectIterator& sub) {
return add(std::move(sub));
}
uint8_t* Builder::add(ObjectIterator&& sub) {
if (_stack.empty()) {
throw Exception(Exception::BuilderNeedOpenObject);
}
ValueLength& tos = _stack.back();
if (_start[tos] != 0x0b && _start[tos] != 0x14) {
throw Exception(Exception::BuilderNeedOpenObject);
}
if (_keyWritten) {
throw Exception(Exception::BuilderKeyAlreadyWritten);
}
auto const oldPos = _start + _pos;
while (sub.valid()) {
add(sub.key().copyString(), sub.value());
sub.next();
}
return oldPos;
}
uint8_t* Builder::add(Value const& sub) { return addInternal<Value>(sub); }
uint8_t* Builder::add(ValuePair const& sub) {
return addInternal<ValuePair>(sub);
}
uint8_t* Builder::add(Slice const& sub) { return addInternal<Slice>(sub); }
// Add all subkeys and subvalues into an object from an ArrayIterator
// and leaves open the array intentionally
uint8_t* Builder::add(ArrayIterator& sub) {
return add(std::move(sub));
}
uint8_t* Builder::add(ArrayIterator&& sub) {
if (_stack.empty()) {
throw Exception(Exception::BuilderNeedOpenArray);
}
ValueLength& tos = _stack.back();
if (_start[tos] != 0x06 && _start[tos] != 0x13) {
throw Exception(Exception::BuilderNeedOpenArray);
}
auto const oldPos = _start + _pos;
while (sub.valid()) {
add(sub.value());
sub.next();
}
return oldPos;
}
static_assert(sizeof(double) == 8, "double is not 8 bytes");