Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff8ba3cb74 | ||
|
|
0c3e83325a | ||
|
|
ebcd4ae4ab | ||
|
|
47462527b2 | ||
|
|
ae30ba55c3 | ||
|
|
731363a325 | ||
|
|
fdcf3aa390 | ||
|
|
209348c151 | ||
|
|
7c3888fe42 | ||
|
|
5bbc7a1098 |
No files matched your search
@@ -80,8 +80,7 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
|
||||
Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes
|
||||
constant time on average.
|
||||
2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the
|
||||
index (unlike `BasicJsonType`'s array, which is random-access). Constant if no element of the array is an array
|
||||
or object, since the elements are then adjacent in the index.
|
||||
index (unlike `BasicJsonType`'s array, which is random-access).
|
||||
3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
|
||||
that level (as 1.) or the index into the array (as 2.).
|
||||
|
||||
|
||||
@@ -28,8 +28,7 @@ This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.
|
||||
|
||||
Linear in the size of the array or object: unlike [`front()`](front.md), which only ever looks at the first
|
||||
element, `back()` has to walk every element to find where they end, since elements are not a fixed size in the
|
||||
index. Constant for an array none of whose elements is an array or object, since the elements are then adjacent
|
||||
in the index.
|
||||
index.
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
@@ -86,8 +86,7 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
|
||||
Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes
|
||||
constant time on average.
|
||||
2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the
|
||||
index (unlike `BasicJsonType`'s array, which is random-access). Constant if no element of the array is an array
|
||||
or object, since the elements are then adjacent in the index.
|
||||
index (unlike `BasicJsonType`'s array, which is random-access).
|
||||
3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
|
||||
that level (as 1.) or the index into the array (as 2.).
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#undef JSON_SCAN_SSSE3_TARGET
|
||||
#undef JSON_SCAN_VECTOR
|
||||
#undef JSON_SCAN_VECTOR_UTF8
|
||||
#undef JSON_DUMP_SHORT_STRINGS
|
||||
|
||||
#ifndef JSON_TEST_KEEP_MACROS
|
||||
#undef JSON_CATCH
|
||||
|
||||
@@ -34,6 +34,14 @@
|
||||
#include <nlohmann/detail/string_utils.hpp>
|
||||
#include <nlohmann/detail/value_t.hpp>
|
||||
|
||||
// the short-string fast path of the serializer assembles words from little-endian
|
||||
// loads, see serializer::put_short_quoted()
|
||||
#if defined(_MSC_VER) || defined(__x86_64__) || defined(__i386__) || (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
|
||||
#define JSON_DUMP_SHORT_STRINGS 1
|
||||
#else
|
||||
#define JSON_DUMP_SHORT_STRINGS 0
|
||||
#endif
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
@@ -80,11 +88,14 @@ class serializer
|
||||
const std::size_t indent_step_ = 0,
|
||||
error_handler_t error_handler_ = error_handler_t::strict)
|
||||
: o(&s)
|
||||
, number_buffer() // NOLINT(readability-redundant-member-init)
|
||||
, string_buffer() // NOLINT(readability-redundant-member-init)
|
||||
, indent_char(ichar)
|
||||
, pretty_print(pretty_print_)
|
||||
, ensure_ascii(ensure_ascii_)
|
||||
, indent_step(indent_step_)
|
||||
, error_handler(error_handler_)
|
||||
, write_buffer() // NOLINT(readability-redundant-member-init)
|
||||
{}
|
||||
|
||||
// deleted because of pointer members
|
||||
@@ -159,34 +170,27 @@ class serializer
|
||||
|
||||
if (pretty_print)
|
||||
{
|
||||
put_literal("{\n");
|
||||
|
||||
// variable to hold indentation for recursive calls
|
||||
const auto new_indent = next_indent(current_indent, indent_step);
|
||||
|
||||
put_literal_indent("{\n", new_indent);
|
||||
|
||||
// first n-1 elements
|
||||
auto i = val.m_data.m_value.object->cbegin();
|
||||
for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
|
||||
{
|
||||
put_indent(new_indent);
|
||||
put_char('"');
|
||||
dump_escaped(i->first);
|
||||
put_literal("\": ");
|
||||
dump_internal(i->second, new_indent, depth + 1);
|
||||
put_literal(",\n");
|
||||
put_quoted(i->first, "\": ");
|
||||
dump_element(i->second, new_indent, depth + 1);
|
||||
put_literal_indent(",\n", new_indent);
|
||||
}
|
||||
|
||||
// last element
|
||||
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
||||
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
||||
put_indent(new_indent);
|
||||
put_char('"');
|
||||
dump_escaped(i->first);
|
||||
put_literal("\": ");
|
||||
dump_internal(i->second, new_indent, depth + 1);
|
||||
put_quoted(i->first, "\": ");
|
||||
dump_element(i->second, new_indent, depth + 1);
|
||||
|
||||
put_char('\n');
|
||||
put_indent(current_indent);
|
||||
put_literal_indent("\n", current_indent);
|
||||
put_char('}');
|
||||
}
|
||||
else
|
||||
@@ -197,20 +201,16 @@ class serializer
|
||||
auto i = val.m_data.m_value.object->cbegin();
|
||||
for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
|
||||
{
|
||||
put_char('"');
|
||||
dump_escaped(i->first);
|
||||
put_literal("\":");
|
||||
dump_internal(i->second, current_indent, depth + 1);
|
||||
put_quoted(i->first, "\":");
|
||||
dump_element(i->second, current_indent, depth + 1);
|
||||
put_char(',');
|
||||
}
|
||||
|
||||
// last element
|
||||
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
||||
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
||||
put_char('"');
|
||||
dump_escaped(i->first);
|
||||
put_literal("\":");
|
||||
dump_internal(i->second, current_indent, depth + 1);
|
||||
put_quoted(i->first, "\":");
|
||||
dump_element(i->second, current_indent, depth + 1);
|
||||
|
||||
put_char('}');
|
||||
}
|
||||
@@ -234,27 +234,24 @@ class serializer
|
||||
|
||||
if (pretty_print)
|
||||
{
|
||||
put_literal("[\n");
|
||||
|
||||
// variable to hold indentation for recursive calls
|
||||
const auto new_indent = next_indent(current_indent, indent_step);
|
||||
|
||||
put_literal_indent("[\n", new_indent);
|
||||
|
||||
// first n-1 elements
|
||||
for (auto i = val.m_data.m_value.array->cbegin();
|
||||
i != val.m_data.m_value.array->cend() - 1; ++i)
|
||||
{
|
||||
put_indent(new_indent);
|
||||
dump_internal(*i, new_indent, depth + 1);
|
||||
put_literal(",\n");
|
||||
dump_element(*i, new_indent, depth + 1);
|
||||
put_literal_indent(",\n", new_indent);
|
||||
}
|
||||
|
||||
// last element
|
||||
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
||||
put_indent(new_indent);
|
||||
dump_internal(val.m_data.m_value.array->back(), new_indent, depth + 1);
|
||||
dump_element(val.m_data.m_value.array->back(), new_indent, depth + 1);
|
||||
|
||||
put_char('\n');
|
||||
put_indent(current_indent);
|
||||
put_literal_indent("\n", current_indent);
|
||||
put_char(']');
|
||||
}
|
||||
else
|
||||
@@ -265,13 +262,13 @@ class serializer
|
||||
for (auto i = val.m_data.m_value.array->cbegin();
|
||||
i != val.m_data.m_value.array->cend() - 1; ++i)
|
||||
{
|
||||
dump_internal(*i, current_indent, depth + 1);
|
||||
dump_element(*i, current_indent, depth + 1);
|
||||
put_char(',');
|
||||
}
|
||||
|
||||
// last element
|
||||
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
||||
dump_internal(val.m_data.m_value.array->back(), current_indent, depth + 1);
|
||||
dump_element(val.m_data.m_value.array->back(), current_indent, depth + 1);
|
||||
|
||||
put_char(']');
|
||||
}
|
||||
@@ -294,6 +291,27 @@ class serializer
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
@brief serialize an element of a container
|
||||
|
||||
Most elements are scalars, which need none of the state dump_internal() keeps
|
||||
for a container; writing them from here spares each the entry and exit of
|
||||
that function.
|
||||
*/
|
||||
JSON_HEDLEY_ALWAYS_INLINE void dump_element(const BasicJsonType& val,
|
||||
const std::size_t current_indent,
|
||||
const std::size_t depth)
|
||||
{
|
||||
if (val.m_data.m_type == value_t::object || val.m_data.m_type == value_t::array)
|
||||
{
|
||||
dump_internal(val, current_indent, depth);
|
||||
}
|
||||
else
|
||||
{
|
||||
dump_scalar(val, current_indent);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write out @a val and everything below it without the call stack
|
||||
|
||||
@@ -353,16 +371,13 @@ class serializer
|
||||
put_indent(frame.child_indent);
|
||||
}
|
||||
|
||||
put_char('"');
|
||||
dump_escaped(frame.object_it->first);
|
||||
|
||||
if (pretty_print)
|
||||
{
|
||||
put_literal("\": ");
|
||||
put_quoted(frame.object_it->first, "\": ");
|
||||
}
|
||||
else
|
||||
{
|
||||
put_literal("\":");
|
||||
put_quoted(frame.object_it->first, "\":");
|
||||
}
|
||||
|
||||
const BasicJsonType& element = frame.object_it->second;
|
||||
@@ -537,78 +552,13 @@ class serializer
|
||||
{
|
||||
case value_t::string:
|
||||
{
|
||||
put_char('"');
|
||||
dump_escaped(*val.m_data.m_value.string);
|
||||
put_char('"');
|
||||
dump_string_value(*val.m_data.m_value.string);
|
||||
return;
|
||||
}
|
||||
|
||||
case value_t::binary:
|
||||
{
|
||||
if (pretty_print)
|
||||
{
|
||||
put_literal("{\n");
|
||||
|
||||
// variable to hold indentation for the bytes
|
||||
const auto new_indent = next_indent(current_indent, indent_step);
|
||||
|
||||
put_indent(new_indent);
|
||||
|
||||
put_literal("\"bytes\": [");
|
||||
|
||||
if (!val.m_data.m_value.binary->empty())
|
||||
{
|
||||
for (auto i = val.m_data.m_value.binary->cbegin();
|
||||
i != val.m_data.m_value.binary->cend() - 1; ++i)
|
||||
{
|
||||
dump_byte(*i);
|
||||
put_literal(", ");
|
||||
}
|
||||
dump_byte(val.m_data.m_value.binary->back());
|
||||
}
|
||||
|
||||
put_literal("],\n");
|
||||
put_indent(new_indent);
|
||||
|
||||
put_literal("\"subtype\": ");
|
||||
if (val.m_data.m_value.binary->has_subtype())
|
||||
{
|
||||
dump_integer(val.m_data.m_value.binary->subtype());
|
||||
}
|
||||
else
|
||||
{
|
||||
put_literal("null");
|
||||
}
|
||||
put_char('\n');
|
||||
put_indent(current_indent);
|
||||
put_char('}');
|
||||
}
|
||||
else
|
||||
{
|
||||
put_literal("{\"bytes\":[");
|
||||
|
||||
if (!val.m_data.m_value.binary->empty())
|
||||
{
|
||||
for (auto i = val.m_data.m_value.binary->cbegin();
|
||||
i != val.m_data.m_value.binary->cend() - 1; ++i)
|
||||
{
|
||||
dump_byte(*i);
|
||||
put_char(',');
|
||||
}
|
||||
dump_byte(val.m_data.m_value.binary->back());
|
||||
}
|
||||
|
||||
put_literal("],\"subtype\":");
|
||||
if (val.m_data.m_value.binary->has_subtype())
|
||||
{
|
||||
dump_integer(val.m_data.m_value.binary->subtype());
|
||||
put_char('}');
|
||||
}
|
||||
else
|
||||
{
|
||||
put_literal("null}");
|
||||
}
|
||||
}
|
||||
dump_binary_value(current_indent, *val.m_data.m_value.binary);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -627,19 +577,19 @@ class serializer
|
||||
|
||||
case value_t::number_integer:
|
||||
{
|
||||
dump_integer(val.m_data.m_value.number_integer);
|
||||
dump_signed_value(val.m_data.m_value.number_integer);
|
||||
return;
|
||||
}
|
||||
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
dump_integer(val.m_data.m_value.number_unsigned);
|
||||
dump_unsigned_value(val.m_data.m_value.number_unsigned);
|
||||
return;
|
||||
}
|
||||
|
||||
case value_t::number_float:
|
||||
{
|
||||
dump_float(val.m_data.m_value.number_float);
|
||||
dump_float_value(val.m_data.m_value.number_float);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -662,6 +612,107 @@ class serializer
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief write the string @a s in quotes; kept out of line, so that the
|
||||
/// other scalars do not pay for its register needs (and the other way round)
|
||||
JSON_HEDLEY_NEVER_INLINE void dump_string_value(const string_t& s)
|
||||
{
|
||||
#if JSON_DUMP_SHORT_STRINGS
|
||||
if (s.size() <= short_string_max && put_short_quoted(s, "\""))
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
put_char('"');
|
||||
dump_escaped(s);
|
||||
put_char('"');
|
||||
}
|
||||
|
||||
// The numbers, and the rest of what dump_scalar() writes, are left to
|
||||
// functions of their own as well: a single function holding all of them
|
||||
// saves and restores every register any of them needs, on every call. Now
|
||||
// dump_scalar() only dispatches, and each writer pays for what it uses.
|
||||
JSON_HEDLEY_NEVER_INLINE void dump_signed_value(const number_integer_t x)
|
||||
{
|
||||
dump_integer(x);
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NEVER_INLINE void dump_unsigned_value(const number_unsigned_t x)
|
||||
{
|
||||
dump_integer(x);
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NEVER_INLINE void dump_float_value(const number_float_t x)
|
||||
{
|
||||
dump_float(x);
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NEVER_INLINE void dump_binary_value(const std::size_t current_indent, const typename BasicJsonType::binary_t& binary)
|
||||
{
|
||||
if (pretty_print)
|
||||
{
|
||||
put_literal("{\n");
|
||||
|
||||
// variable to hold indentation for the bytes
|
||||
const auto new_indent = next_indent(current_indent, indent_step);
|
||||
|
||||
put_indent(new_indent);
|
||||
|
||||
put_literal("\"bytes\": [");
|
||||
|
||||
if (!binary.empty())
|
||||
{
|
||||
for (auto i = binary.cbegin();
|
||||
i != binary.cend() - 1; ++i)
|
||||
{
|
||||
dump_byte(*i);
|
||||
put_literal(", ");
|
||||
}
|
||||
dump_byte(binary.back());
|
||||
}
|
||||
|
||||
put_literal("],\n");
|
||||
put_indent(new_indent);
|
||||
|
||||
put_literal("\"subtype\": ");
|
||||
if (binary.has_subtype())
|
||||
{
|
||||
dump_integer(binary.subtype());
|
||||
}
|
||||
else
|
||||
{
|
||||
put_literal("null");
|
||||
}
|
||||
put_char('\n');
|
||||
put_indent(current_indent);
|
||||
put_char('}');
|
||||
}
|
||||
else
|
||||
{
|
||||
put_literal("{\"bytes\":[");
|
||||
|
||||
if (!binary.empty())
|
||||
{
|
||||
for (auto i = binary.cbegin();
|
||||
i != binary.cend() - 1; ++i)
|
||||
{
|
||||
dump_byte(*i);
|
||||
put_char(',');
|
||||
}
|
||||
dump_byte(binary.back());
|
||||
}
|
||||
|
||||
put_literal("],\"subtype\":");
|
||||
if (binary.has_subtype())
|
||||
{
|
||||
dump_integer(binary.subtype());
|
||||
put_char('}');
|
||||
}
|
||||
else
|
||||
{
|
||||
put_literal("null}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the indentation level to use for the children of the current value
|
||||
@@ -678,6 +729,217 @@ class serializer
|
||||
return new_indent;
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
@brief append the string @a s in quotes, followed by @a tail
|
||||
|
||||
The quote, the string and @a tail (the closing quote, possibly with the
|
||||
colon of an object key) are the bulk of what most documents consist of, and
|
||||
most keys and many values are short and plain. Those are handled in one go
|
||||
by put_short_quoted(); everything else takes the general path.
|
||||
*/
|
||||
template<std::size_t N>
|
||||
void put_quoted(const string_t& s, const char (&tail)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
{
|
||||
#if JSON_DUMP_SHORT_STRINGS
|
||||
if (put_short_quoted(s, tail))
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
put_char('"');
|
||||
dump_escaped(s);
|
||||
put_literal(tail);
|
||||
}
|
||||
|
||||
#if JSON_DUMP_SHORT_STRINGS
|
||||
/// the longest string put_short_quoted() takes
|
||||
static constexpr std::size_t short_string_max = JSON_SCAN_VECTOR ? 32 : 16;
|
||||
|
||||
/// the high bit of every byte of @a v that is not printable ASCII or is a
|
||||
/// quote, a backslash, or DEL (zero if the bytes can be copied as they are)
|
||||
static std::uint64_t swar_not_plain(const std::uint64_t v) noexcept
|
||||
{
|
||||
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
||||
const std::uint64_t q = v ^ 0x2222222222222222ull; // '"'
|
||||
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull; // '\\'
|
||||
return (((q - ones) & ~q) | ((b - ones) & ~b) // == '"', == '\\'
|
||||
| ((v - 0x2020202020202020ull) & ~v) // < 0x20
|
||||
| v | (v + ones)) // >= 0x7F
|
||||
& 0x8080808080808080ull;
|
||||
}
|
||||
|
||||
template<typename WordType>
|
||||
static WordType load_word(const char* p) noexcept
|
||||
{
|
||||
WordType w;
|
||||
std::memcpy(&w, p, sizeof(w));
|
||||
return w;
|
||||
}
|
||||
|
||||
template<typename WordType>
|
||||
static void store_word(char* p, const WordType w) noexcept
|
||||
{
|
||||
std::memcpy(p, &w, sizeof(w));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write `"`, @a s and @a tail if @a s is short and needs no escaping
|
||||
|
||||
A string of up to 16 (32 with vector instructions) printable ASCII
|
||||
characters (no quote, backslash, or DEL) is read with two overlapping loads
|
||||
and written with two overlapping stores, instead of scanning it and calling
|
||||
memcpy with a length that is only known at run time. Returns false, having
|
||||
changed nothing, if @a s does not qualify or the write buffer lacks the
|
||||
room.
|
||||
*/
|
||||
template<std::size_t N>
|
||||
bool put_short_quoted(const string_t& s, const char (&tail)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
{
|
||||
const std::size_t n = s.size();
|
||||
if (n > short_string_max || write_buffer_pos + n + N > write_buffer.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
char* d = write_buffer.data() + write_buffer_pos + 1;
|
||||
const char* const p = s.data();
|
||||
|
||||
if (n >= 8)
|
||||
{
|
||||
#if JSON_SCAN_VECTOR
|
||||
if (!(n >= 16 ? copy_plain_vectors(d, p, n) : copy_plain_halves(d, p, n)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
const auto a = load_word<std::uint64_t>(p);
|
||||
const auto b = load_word<std::uint64_t>(p + n - 8);
|
||||
if ((swar_not_plain(a) | swar_not_plain(b)) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
store_word(d, a);
|
||||
store_word(d + n - 8, b);
|
||||
#endif
|
||||
}
|
||||
else if (n >= 4)
|
||||
{
|
||||
const auto a = load_word<std::uint32_t>(p);
|
||||
const auto b = load_word<std::uint32_t>(p + n - 4);
|
||||
// the bytes above the string are filled with a plain character
|
||||
const std::uint64_t w = a | (static_cast<std::uint64_t>(b) << (8 * (n - 4)))
|
||||
| (0x4141414141414141ull << (8 * n));
|
||||
if (swar_not_plain(w) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
store_word(d, a);
|
||||
store_word(d + n - 4, b);
|
||||
}
|
||||
else if (n >= 2)
|
||||
{
|
||||
const auto a = load_word<std::uint16_t>(p);
|
||||
const auto b = load_word<std::uint16_t>(p + n - 2);
|
||||
const std::uint64_t w = a | (static_cast<std::uint64_t>(b) << (8 * (n - 2)))
|
||||
| (0x4141414141414141ull << (8 * n));
|
||||
if (swar_not_plain(w) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
store_word(d, a);
|
||||
store_word(d + n - 2, b);
|
||||
}
|
||||
else if (n == 1)
|
||||
{
|
||||
const std::uint64_t w = static_cast<std::uint64_t>(static_cast<unsigned char>(p[0])) | (0x4141414141414141ull << 8u);
|
||||
if (swar_not_plain(w) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
*d = p[0];
|
||||
}
|
||||
|
||||
d[-1] = '"';
|
||||
std::memcpy(d + n, tail, N - 1);
|
||||
write_buffer_pos += n + N;
|
||||
return true;
|
||||
}
|
||||
|
||||
#if JSON_SCAN_VECTOR
|
||||
/// copy the 8 to 15 bytes [@a p, @a p + @a n) to @a d with two overlapping
|
||||
/// words, tested together as one vector, see copy_plain_vectors()
|
||||
static bool copy_plain_halves(char* d, const char* p, const std::size_t n) noexcept
|
||||
{
|
||||
#if JSON_SCAN_NEON
|
||||
const uint8x8_t lo = vld1_u8(reinterpret_cast<const std::uint8_t*>(p));
|
||||
const uint8x8_t hi = vld1_u8(reinterpret_cast<const std::uint8_t*>(p + n - 8));
|
||||
const uint8x16_t in = vcombine_u8(lo, hi);
|
||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
|
||||
vorrq_u8(vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20)), vceqq_u8(in, vdupq_n_u8(0x7F))));
|
||||
if (vmaxvq_u8(special) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
vst1_u8(reinterpret_cast<std::uint8_t*>(d), lo);
|
||||
vst1_u8(reinterpret_cast<std::uint8_t*>(d + n - 8), hi);
|
||||
#else
|
||||
const __m128i lo = _mm_loadl_epi64(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i hi = _mm_loadl_epi64(static_cast<const __m128i*>(static_cast<const void*>(p + n - 8))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i in = _mm_unpacklo_epi64(lo, hi);
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
||||
_mm_or_si128(_mm_cmplt_epi8(in, _mm_set1_epi8(0x20)), _mm_cmpeq_epi8(in, _mm_set1_epi8(0x7F))));
|
||||
if (_mm_movemask_epi8(special) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_mm_storel_epi64(static_cast<__m128i*>(static_cast<void*>(d)), lo); // NOLINT(bugprone-casting-through-void)
|
||||
_mm_storel_epi64(static_cast<__m128i*>(static_cast<void*>(d + n - 8)), hi); // NOLINT(bugprone-casting-through-void)
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
/// copy the 16 to 32 bytes [@a p, @a p + @a n) to @a d with two overlapping
|
||||
/// vectors, unless one of them is a quote, a backslash, a control
|
||||
/// character, DEL or not ASCII
|
||||
static bool copy_plain_vectors(char* d, const char* p, const std::size_t n) noexcept
|
||||
{
|
||||
#if JSON_SCAN_NEON
|
||||
const uint8x16_t a = vld1q_u8(reinterpret_cast<const std::uint8_t*>(p));
|
||||
const uint8x16_t b = vld1q_u8(reinterpret_cast<const std::uint8_t*>(p + n - 16));
|
||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vorrq_u8(vceqq_u8(a, vdupq_n_u8('"')), vceqq_u8(a, vdupq_n_u8('\\'))),
|
||||
vorrq_u8(vceqq_u8(b, vdupq_n_u8('"')), vceqq_u8(b, vdupq_n_u8('\\')))),
|
||||
vorrq_u8(vorrq_u8(vcltq_s8(vreinterpretq_s8_u8(a), vdupq_n_s8(0x20)), vcltq_s8(vreinterpretq_s8_u8(b), vdupq_n_s8(0x20))),
|
||||
vorrq_u8(vceqq_u8(a, vdupq_n_u8(0x7F)), vceqq_u8(b, vdupq_n_u8(0x7F)))));
|
||||
if (vmaxvq_u8(special) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
vst1q_u8(reinterpret_cast<std::uint8_t*>(d), a);
|
||||
vst1q_u8(reinterpret_cast<std::uint8_t*>(d + n - 16), b);
|
||||
#else
|
||||
const __m128i a = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i b = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p + n - 16))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i quote = _mm_set1_epi8('"');
|
||||
const __m128i backslash = _mm_set1_epi8('\\');
|
||||
const __m128i space = _mm_set1_epi8(0x20);
|
||||
const __m128i del = _mm_set1_epi8(0x7F);
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(a, quote), _mm_cmpeq_epi8(a, backslash)),
|
||||
_mm_or_si128(_mm_cmpeq_epi8(b, quote), _mm_cmpeq_epi8(b, backslash))),
|
||||
_mm_or_si128(_mm_or_si128(_mm_cmplt_epi8(a, space), _mm_cmplt_epi8(b, space)),
|
||||
_mm_or_si128(_mm_cmpeq_epi8(a, del), _mm_cmpeq_epi8(b, del))));
|
||||
if (_mm_movemask_epi8(special) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_mm_storeu_si128(static_cast<__m128i*>(static_cast<void*>(d)), a); // NOLINT(bugprone-casting-through-void)
|
||||
_mm_storeu_si128(static_cast<__m128i*>(static_cast<void*>(d + n - 16)), b); // NOLINT(bugprone-casting-through-void)
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
JSON_PRIVATE_UNLESS_TESTED:
|
||||
/*!
|
||||
@brief dump escaped string
|
||||
@@ -690,14 +952,23 @@ class serializer
|
||||
@param[in] s the string to escape
|
||||
|
||||
Complexity: Linear in the length of string @a s.
|
||||
|
||||
@note kept out of line: with the short-string attempts in front of it gone
|
||||
from here, compilers would otherwise inline it into @ref put_quoted
|
||||
and then no longer into the loops that call that
|
||||
*/
|
||||
void dump_escaped(const string_t& s)
|
||||
JSON_HEDLEY_NEVER_INLINE void dump_escaped(const string_t& s)
|
||||
{
|
||||
// Short strings of printable ASCII - most object keys and many values -
|
||||
// are checked byte by byte against a table and then copied in one go.
|
||||
// For them, this is cheaper than starting the vector scanner below,
|
||||
// whose setup costs more than such a string takes to scan. The same
|
||||
// check serves both values of ensure_ascii.
|
||||
//
|
||||
// Where put_short_quoted() exists and takes strings of up to 32 bytes,
|
||||
// the callers have tried it already, and repeating the attempt only
|
||||
// costs time for strings that need escaping or are not ASCII.
|
||||
#if !(JSON_DUMP_SHORT_STRINGS && JSON_SCAN_VECTOR)
|
||||
if (s.size() <= 32)
|
||||
{
|
||||
const auto& copyable = ascii_copyable();
|
||||
@@ -719,6 +990,7 @@ class serializer
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// dispatch once here rather than test the flag inside the loop: it does
|
||||
// not change while a string is written, and folding it lets each of the
|
||||
@@ -796,7 +1068,7 @@ class serializer
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
put_string(s, i, i + run);
|
||||
put_run(s, i, run);
|
||||
bytes_after_last_accept = 0;
|
||||
undumped_chars = 0;
|
||||
i += run;
|
||||
@@ -836,6 +1108,27 @@ class serializer
|
||||
|
||||
const auto byte = static_cast<std::uint8_t>(s[i]);
|
||||
|
||||
if (EnsureAscii && state == UTF8_ACCEPT && byte >= 0x80)
|
||||
{
|
||||
// a run of well-formed multi-byte characters is escaped
|
||||
// straight into the write buffer; an ill-formed sequence
|
||||
// ends the run and is left to the byte-at-a-time path
|
||||
if (bytes != 0)
|
||||
{
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
bytes_after_last_accept = 0;
|
||||
}
|
||||
const std::size_t escaped = write_u_escapes(reinterpret_cast<const unsigned char*>(s.data()) + i, s.size() - i);
|
||||
if (escaped != 0)
|
||||
{
|
||||
bytes_after_last_accept = 0;
|
||||
undumped_chars = 0;
|
||||
i += escaped - 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
switch (decode(state, codepoint, byte))
|
||||
{
|
||||
case UTF8_ACCEPT: // decode found a new code point
|
||||
@@ -1113,6 +1406,21 @@ class serializer
|
||||
return;
|
||||
}
|
||||
|
||||
// the usual indentation: four words of the indentation character are
|
||||
// stored unconditionally and only the part wanted is claimed, which
|
||||
// spares the call of memset
|
||||
if (JSON_HEDLEY_LIKELY(indent <= 32 && write_buffer_pos + 32 <= write_buffer.size()))
|
||||
{
|
||||
const std::uint64_t word = 0x0101010101010101ull * static_cast<unsigned char>(indent_char);
|
||||
char* const d = write_buffer.data() + write_buffer_pos;
|
||||
std::memcpy(d, &word, sizeof(word));
|
||||
std::memcpy(d + 8, &word, sizeof(word));
|
||||
std::memcpy(d + 16, &word, sizeof(word));
|
||||
std::memcpy(d + 24, &word, sizeof(word));
|
||||
write_buffer_pos += indent;
|
||||
return;
|
||||
}
|
||||
|
||||
const std::size_t capacity = write_buffer.size();
|
||||
|
||||
// fill whatever room is left in the buffer; this is the whole job
|
||||
@@ -1146,6 +1454,32 @@ class serializer
|
||||
write_buffer_pos = indent;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief append a string literal and then @a indent indentation characters
|
||||
|
||||
The usual line break and indentation go into the buffer with one check of
|
||||
its room and four stores of the indentation, see @ref put_indent.
|
||||
*/
|
||||
template<std::size_t N>
|
||||
JSON_HEDLEY_ALWAYS_INLINE void put_literal_indent(const char (&s)[N], const std::size_t indent) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
{
|
||||
constexpr std::size_t length = N - 1;
|
||||
if (JSON_HEDLEY_LIKELY(indent <= 32 && write_buffer_pos + length + 32 <= write_buffer.size()))
|
||||
{
|
||||
const std::uint64_t word = 0x0101010101010101ull * static_cast<unsigned char>(indent_char);
|
||||
char* const d = write_buffer.data() + write_buffer_pos;
|
||||
std::memcpy(d, s, length);
|
||||
std::memcpy(d + length, &word, sizeof(word));
|
||||
std::memcpy(d + length + 8, &word, sizeof(word));
|
||||
std::memcpy(d + length + 16, &word, sizeof(word));
|
||||
std::memcpy(d + length + 24, &word, sizeof(word));
|
||||
write_buffer_pos += length + indent;
|
||||
return;
|
||||
}
|
||||
put_literal(s);
|
||||
put_indent(indent);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief append a string literal to the write buffer
|
||||
|
||||
@@ -1170,6 +1504,28 @@ class serializer
|
||||
write_buffer_pos += length;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief append the characters of @a str in [@a start, @a start + @a length)
|
||||
|
||||
A run of up to 16 characters with 16 or more in the string from @a start on
|
||||
and in the write buffer from its end on is copied as a whole block of 16,
|
||||
which spares the call of memcpy with a length known only at run time; the
|
||||
surplus characters in the buffer are overwritten by what follows.
|
||||
*/
|
||||
template<typename StringType>
|
||||
void put_run(const StringType& str, const std::size_t start, const std::size_t length)
|
||||
{
|
||||
// a short run is the exception in text without escapes (CJK, accented
|
||||
// Latin), for which this keeps the long path the fast one
|
||||
if (JSON_HEDLEY_UNLIKELY(length <= 16 && start + 16 <= str.size() && write_buffer_pos + 16 <= write_buffer.size()))
|
||||
{
|
||||
std::memcpy(write_buffer.data() + write_buffer_pos, str.data() + start, 16);
|
||||
write_buffer_pos += length;
|
||||
return;
|
||||
}
|
||||
put_string(str, start, start + length);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief append the characters of @a str in [@a start, @a end)
|
||||
|
||||
@@ -1412,6 +1768,75 @@ class serializer
|
||||
return escapes;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief escape the well-formed multi-byte characters at the start of @a data
|
||||
|
||||
Writes the "\\uXXXX" escape of each character (two of them for a character
|
||||
above U+FFFF) straight into the write buffer, which is why the caller must
|
||||
have written out what is pending in @ref string_buffer. Stops at the first
|
||||
byte that is below 0x80 or starts an ill-formed or truncated sequence, which
|
||||
the caller handles.
|
||||
|
||||
@return the number of bytes of @a data that have been escaped
|
||||
*/
|
||||
JSON_HEDLEY_NEVER_INLINE std::size_t write_u_escapes(const unsigned char* data, std::size_t n)
|
||||
{
|
||||
constexpr const char* nibble_to_hex = "0123456789abcdef";
|
||||
std::size_t i = 0;
|
||||
while (i < n && data[i] >= 0x80)
|
||||
{
|
||||
const std::size_t length = validate_one_utf8(data + i, n - i);
|
||||
if (length == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
const auto b0 = static_cast<std::uint32_t>(data[i]);
|
||||
const auto b1 = static_cast<std::uint32_t>(data[i + 1]);
|
||||
std::uint32_t codepoint = 0;
|
||||
switch (length)
|
||||
{
|
||||
case 2:
|
||||
codepoint = ((b0 & 0x1Fu) << 6u) | (b1 & 0x3Fu);
|
||||
break;
|
||||
case 3:
|
||||
codepoint = ((b0 & 0x0Fu) << 12u) | ((b1 & 0x3Fu) << 6u) | (static_cast<std::uint32_t>(data[i + 2]) & 0x3Fu);
|
||||
break;
|
||||
default:
|
||||
codepoint = ((b0 & 0x07u) << 18u) | ((b1 & 0x3Fu) << 12u) | ((static_cast<std::uint32_t>(data[i + 2]) & 0x3Fu) << 6u) | (static_cast<std::uint32_t>(data[i + 3]) & 0x3Fu);
|
||||
break;
|
||||
}
|
||||
|
||||
// at most two escapes of six bytes each
|
||||
if (JSON_HEDLEY_UNLIKELY(write_buffer.size() - write_buffer_pos < 12))
|
||||
{
|
||||
flush();
|
||||
}
|
||||
char* d = write_buffer.data() + write_buffer_pos;
|
||||
if (codepoint > 0xFFFFu)
|
||||
{
|
||||
d = put_u_escape(d, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)), nibble_to_hex);
|
||||
codepoint = 0xDC00u + (codepoint & 0x3FFu);
|
||||
}
|
||||
d = put_u_escape(d, static_cast<std::uint16_t>(codepoint), nibble_to_hex);
|
||||
write_buffer_pos = static_cast<std::size_t>(d - write_buffer.data());
|
||||
i += length;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static char* put_u_escape(char* d, const std::uint16_t codeunit, const char* nibble_to_hex) noexcept
|
||||
{
|
||||
const auto unit = static_cast<std::uint32_t>(codeunit);
|
||||
d[0] = '\\';
|
||||
d[1] = 'u';
|
||||
d[2] = nibble_to_hex[(unit >> 12u) & 0x0Fu];
|
||||
d[3] = nibble_to_hex[(unit >> 8u) & 0x0Fu];
|
||||
d[4] = nibble_to_hex[(unit >> 4u) & 0x0Fu];
|
||||
d[5] = nibble_to_hex[unit & 0x0Fu];
|
||||
return d + 6;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief write a lowercase "\\uXXXX" escape sequence into @a string_buffer
|
||||
*
|
||||
@@ -1769,11 +2194,22 @@ class serializer
|
||||
/// the output of the serializer (non-owning; the adapter lives at the call site)
|
||||
output_adapter_protocol<char>* o = nullptr;
|
||||
|
||||
/// a character buffer left uninitialized: the buffers below are written
|
||||
/// before they are read (only the part written is ever read), and zeroing
|
||||
/// 1.6 KiB took more than half of what dumping a small value costs (the
|
||||
/// user-provided constructor keeps the member initialization, which
|
||||
/// -Weffc++ asks for, from zeroing them)
|
||||
template<std::size_t N>
|
||||
struct uninitialized_buffer : std::array<char, N>
|
||||
{
|
||||
uninitialized_buffer() noexcept {} // NOLINT(modernize-use-equals-default,hicpp-use-equals-default,cppcoreguidelines-pro-type-member-init,hicpp-member-init): see above
|
||||
};
|
||||
|
||||
/// a (hopefully) large enough character buffer
|
||||
std::array<char, 64> number_buffer{{}};
|
||||
uninitialized_buffer<64> number_buffer;
|
||||
|
||||
/// string buffer
|
||||
std::array<char, 512> string_buffer{{}};
|
||||
uninitialized_buffer<512> string_buffer;
|
||||
|
||||
/// the indentation character
|
||||
const char indent_char;
|
||||
@@ -1793,7 +2229,7 @@ class serializer
|
||||
/// buffer collecting output before it is flushed to the output adapter, so
|
||||
/// that the many small structural writes become few bulk writes
|
||||
static constexpr std::size_t write_buffer_size = 1024;
|
||||
std::array<char, write_buffer_size> write_buffer{{}};
|
||||
uninitialized_buffer<write_buffer_size> write_buffer;
|
||||
/// number of valid bytes currently held in @ref write_buffer
|
||||
std::size_t write_buffer_pos = 0;
|
||||
};
|
||||
|
||||
@@ -275,41 +275,11 @@ class builder
|
||||
0, 0, '\b', 0, 0, 0, '\f', 0, 0, 0, 0, 0, 0, 0, '\n', 0, 0, 0, '\r', 0, '\t', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
}
|
||||
};
|
||||
// The write position is kept in locals: the stores of characters into
|
||||
// the arena may alias the members of the builder, which would then be
|
||||
// reloaded after every one. It is stored back where the arena grows
|
||||
// and at the end.
|
||||
const unsigned char* const end = m_end;
|
||||
const std::size_t start = arena_used();
|
||||
char* w = m_arena_write;
|
||||
const char* w_end = m_arena_end;
|
||||
#define NLOHMANN_VIEW_ENSURE(n) \
|
||||
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(w_end - w) < (n))) \
|
||||
{ \
|
||||
m_arena_write = w; \
|
||||
arena_grow(n); \
|
||||
w = m_arena_write; \
|
||||
w_end = m_arena_end; \
|
||||
}
|
||||
{
|
||||
const auto n = static_cast<std::size_t>(p - s);
|
||||
if (n <= 16 && end - s >= 16)
|
||||
{
|
||||
NLOHMANN_VIEW_ENSURE(16)
|
||||
std::memcpy(w, s, 16);
|
||||
}
|
||||
else if (n != 0)
|
||||
{
|
||||
// (an empty run leaves an empty arena unallocated, and memcpy()
|
||||
// must not be passed its null pointer even for zero bytes)
|
||||
NLOHMANN_VIEW_ENSURE(n)
|
||||
std::memcpy(w, s, n);
|
||||
}
|
||||
w += n;
|
||||
}
|
||||
arena_run(s, static_cast<std::size_t>(p - s));
|
||||
for (;;)
|
||||
{
|
||||
if (p == end)
|
||||
if (p == m_end)
|
||||
{
|
||||
return failed(error_code::string_missing_quote, p);
|
||||
}
|
||||
@@ -317,7 +287,6 @@ class builder
|
||||
if (c == '"')
|
||||
{
|
||||
++p;
|
||||
m_arena_write = w;
|
||||
return decoded{p, start, arena_used() - start};
|
||||
}
|
||||
if (c != '\\')
|
||||
@@ -328,7 +297,7 @@ class builder
|
||||
return failed(c < 0x20 ? error_code::string_control_character : error_code::string_utf8, p);
|
||||
}
|
||||
++p;
|
||||
if (p == end)
|
||||
if (p == m_end)
|
||||
{
|
||||
return failed(error_code::string_missing_quote, p);
|
||||
}
|
||||
@@ -346,7 +315,7 @@ class builder
|
||||
{
|
||||
return failed(error_code::string_surrogate_low, p);
|
||||
}
|
||||
if (end - p < 2 || p[0] != '\\' || p[1] != 'u')
|
||||
if (m_end - p < 2 || p[0] != '\\' || p[1] != 'u')
|
||||
{
|
||||
return failed(error_code::string_surrogate_high, p);
|
||||
}
|
||||
@@ -362,108 +331,27 @@ class builder
|
||||
}
|
||||
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
|
||||
}
|
||||
NLOHMANN_VIEW_ENSURE(4)
|
||||
w = put_utf8(w, cp);
|
||||
arena_ensure(4);
|
||||
m_arena_write = put_utf8(m_arena_write, cp);
|
||||
}
|
||||
else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0))
|
||||
{
|
||||
NLOHMANN_VIEW_ENSURE(1)
|
||||
*w++ = simple_escape[d];
|
||||
arena_ensure(1);
|
||||
*m_arena_write++ = simple_escape[d];
|
||||
}
|
||||
else
|
||||
{
|
||||
--p;
|
||||
return failed(error_code::string_escape, p);
|
||||
}
|
||||
#if NLOHMANN_VIEW_NEON
|
||||
if (p != end && *p == '\\')
|
||||
if (p != m_end && *p == '\\')
|
||||
{
|
||||
continue; // consecutive escapes ("\u00e4\u00f6"): no run in between
|
||||
}
|
||||
// The first 16 bytes of the run are checked one by one, in predicted branches, and copied
|
||||
// as one block: where the runs between escapes are short, the vector step below would put
|
||||
// its longer dependency chain (load, compare, mask, count, add) on p for each of them,
|
||||
// and be slower than these steps (the scanner of the keys works the same way)
|
||||
{
|
||||
if (end - p >= 16)
|
||||
{
|
||||
const std::uint8_t* const plain = string_plain();
|
||||
std::size_t steps = 16;
|
||||
#define NLOHMANN_VIEW_STEP(i) if (NLOHMANN_VIEW_LIKELY(plain[p[i]] != 0)) {} else { steps = (i); goto run_found; }
|
||||
NLOHMANN_VIEW_REPEAT16(NLOHMANN_VIEW_STEP)
|
||||
#undef NLOHMANN_VIEW_STEP
|
||||
run_found:
|
||||
NLOHMANN_VIEW_ENSURE(16)
|
||||
std::memcpy(w, p, 16); // (only the first `steps` bytes are kept)
|
||||
w += steps;
|
||||
p += steps;
|
||||
if (steps < 16)
|
||||
{
|
||||
if (*p >= 0x80)
|
||||
{
|
||||
// non-ASCII: the UTF-8 check, and the rest of the run
|
||||
const unsigned char* const r = p;
|
||||
p = scan_string_run(p, end);
|
||||
const auto n = static_cast<std::size_t>(p - r);
|
||||
NLOHMANN_VIEW_ENSURE(n)
|
||||
std::memcpy(w, r, n);
|
||||
w += n;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (p != end && (*p == '\\' || *p == '"'))
|
||||
{
|
||||
continue; // consecutive escapes ("\u00e4\u00f6"), or the end of the string: no run in between
|
||||
}
|
||||
#endif
|
||||
#if NLOHMANN_VIEW_VECTOR
|
||||
// the plain bytes up to the next quote, backslash, or control
|
||||
// character are copied as they are scanned, 16 bytes at a time
|
||||
for (;;)
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(end - p < 16))
|
||||
{
|
||||
const unsigned char* const r = p;
|
||||
p = scan_string_run(p, end);
|
||||
const auto n = static_cast<std::size_t>(p - r);
|
||||
NLOHMANN_VIEW_ENSURE(n)
|
||||
std::memcpy(w, r, n);
|
||||
w += n;
|
||||
break;
|
||||
}
|
||||
NLOHMANN_VIEW_ENSURE(16)
|
||||
const unsigned k = copy_plain_run16(p, w);
|
||||
w += k;
|
||||
p += k;
|
||||
if (k == 16)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (NLOHMANN_VIEW_UNLIKELY(*p >= 0x80))
|
||||
{
|
||||
// non-ASCII: the UTF-8 check, and the rest of the run
|
||||
const unsigned char* const r = p;
|
||||
p = scan_string_run(p, end);
|
||||
const auto n = static_cast<std::size_t>(p - r);
|
||||
NLOHMANN_VIEW_ENSURE(n)
|
||||
std::memcpy(w, r, n);
|
||||
w += n;
|
||||
}
|
||||
break;
|
||||
}
|
||||
#else
|
||||
const unsigned char* const r = p;
|
||||
p = scan_string_run(p, end);
|
||||
const auto n = static_cast<std::size_t>(p - r);
|
||||
NLOHMANN_VIEW_ENSURE(n)
|
||||
std::memcpy(w, r, n);
|
||||
w += n;
|
||||
#endif
|
||||
p = scan_string_run(p, m_end);
|
||||
arena_run(r, static_cast<std::size_t>(p - r));
|
||||
}
|
||||
#undef NLOHMANN_VIEW_ENSURE
|
||||
}
|
||||
|
||||
/// does the float token [s, p) overflow FloatType? (parse() rejects it)
|
||||
@@ -509,6 +397,14 @@ run_found:
|
||||
return m_arena_write != nullptr ? static_cast<std::size_t>(m_arena_write - m_doc.arena.data()) : 0;
|
||||
}
|
||||
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE void arena_ensure(std::size_t n)
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(m_arena_end - m_arena_write) < n))
|
||||
{
|
||||
arena_grow(n);
|
||||
}
|
||||
}
|
||||
|
||||
NLOHMANN_VIEW_NOINLINE void arena_grow(std::size_t n)
|
||||
{
|
||||
const std::size_t used = arena_used();
|
||||
@@ -517,6 +413,25 @@ run_found:
|
||||
m_arena_end = m_doc.arena.data() + m_doc.arena.size();
|
||||
}
|
||||
|
||||
/// append the run [r, r + n) to the arena; short runs as one fixed-size
|
||||
/// 16-byte move when both sides have the room (no library call)
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE void arena_run(const unsigned char* r, std::size_t n)
|
||||
{
|
||||
if (n <= 16 && m_end - r >= 16)
|
||||
{
|
||||
arena_ensure(16);
|
||||
std::memcpy(m_arena_write, r, 16);
|
||||
}
|
||||
else if (n != 0)
|
||||
{
|
||||
// (an empty run leaves an empty arena unallocated, and memcpy()
|
||||
// must not be passed its null pointer even for zero bytes)
|
||||
arena_ensure(n);
|
||||
std::memcpy(m_arena_write, r, n);
|
||||
}
|
||||
m_arena_write += n;
|
||||
}
|
||||
|
||||
/// UTF-8 encoding of cp at w (room for 4 bytes); returns the position after
|
||||
/// it. (A returned pointer stays in a register; writing through a
|
||||
/// reference to m_arena_write would store and reload it around every byte.)
|
||||
@@ -1039,11 +954,11 @@ indent_done:
|
||||
++p;
|
||||
}
|
||||
const unsigned char* const int_start = p;
|
||||
if (cur() == '0')
|
||||
if (p != e && *p == '0')
|
||||
{
|
||||
++p;
|
||||
}
|
||||
else if (NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9'))
|
||||
else if (NLOHMANN_VIEW_LIKELY(p != e && *p >= '1' && *p <= '9'))
|
||||
{
|
||||
p = skip_digits(p + 1, e);
|
||||
}
|
||||
@@ -1054,7 +969,7 @@ indent_done:
|
||||
const auto int_digits = static_cast<std::size_t>(p - int_start);
|
||||
std::size_t frac_digits = 0;
|
||||
bool is_float = false;
|
||||
if (cur() == '.')
|
||||
if (p != e && *p == '.')
|
||||
{
|
||||
++p;
|
||||
const unsigned char* const f0 = p;
|
||||
@@ -1067,20 +982,20 @@ indent_done:
|
||||
is_float = true;
|
||||
}
|
||||
std::int64_t exponent = 0;
|
||||
if ((cur() | 0x20) == 'e')
|
||||
if (p != e && (*p | 0x20) == 'e')
|
||||
{
|
||||
++p;
|
||||
bool exp_negative = false;
|
||||
if (cur() == '+' || cur() == '-')
|
||||
if (p != e && (*p == '+' || *p == '-'))
|
||||
{
|
||||
exp_negative = *p == '-';
|
||||
++p;
|
||||
}
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_digit(cur())))
|
||||
if (NLOHMANN_VIEW_UNLIKELY(p == e || !is_digit(*p)))
|
||||
{
|
||||
return fail(error_code::number_after_exponent);
|
||||
}
|
||||
while (is_digit(cur()))
|
||||
while (p != e && is_digit(*p))
|
||||
{
|
||||
if (exponent < 100000)
|
||||
{
|
||||
@@ -1142,7 +1057,7 @@ indent_done:
|
||||
++p; // opening quote
|
||||
const unsigned char* const s = p;
|
||||
p = scan_string_run<Value>(p, e);
|
||||
if (NLOHMANN_VIEW_LIKELY(cur() == '"'))
|
||||
if (NLOHMANN_VIEW_LIKELY(p != e && *p == '"'))
|
||||
{
|
||||
push_node(value_t::string, 0, 0, static_cast<std::size_t>(s - b), static_cast<std::uint64_t>(p - s));
|
||||
++p;
|
||||
|
||||
@@ -10,9 +10,6 @@
|
||||
|
||||
#include <algorithm> // max
|
||||
#include <cstddef> // size_t
|
||||
#ifndef JSON_NO_IO
|
||||
#include <cstdio> // FILE, fread
|
||||
#endif
|
||||
#include <istream> // istream, streambuf
|
||||
#include <string> // basic_string, char_traits, string
|
||||
#include <type_traits> // decay, integral_constant, is_array, is_const, is_integral, is_lvalue_reference, is_pointer, is_same, remove_cv, remove_extent, remove_pointer, remove_reference
|
||||
@@ -157,37 +154,6 @@ inline void append_stream(std::string& buf, std::istream& is)
|
||||
is.clear(is.rdstate() | std::ios::eofbit);
|
||||
}
|
||||
|
||||
/// a pointer to a C stream (never with JSON_NO_IO, which leaves out <cstdio>)
|
||||
template<typename T>
|
||||
struct is_c_stream
|
||||
#ifndef JSON_NO_IO
|
||||
: std::is_same<T, std::FILE*>
|
||||
#else
|
||||
: std::false_type
|
||||
#endif
|
||||
{};
|
||||
|
||||
#ifndef JSON_NO_IO
|
||||
/// append the rest of a C stream: in blocks with fread, not character by
|
||||
/// character (the library's file adapter reads with fgetc);
|
||||
/// f is not null
|
||||
inline void append_file(std::string& buf, std::FILE* f)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
const std::size_t used = buf.size();
|
||||
const std::size_t room = (std::max)(buf.capacity() - used, std::size_t{4096});
|
||||
buf.resize(used + room);
|
||||
const std::size_t got = std::fread(&buf[used], 1, room, f);
|
||||
buf.resize(used + got);
|
||||
if (got < room)
|
||||
{
|
||||
break; // end of file, or an error (as fgetc, which returns EOF for both)
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace view
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -147,21 +147,20 @@ class view_item
|
||||
|
||||
view_item(const iterator& it, std::size_t index)
|
||||
: m_it(it)
|
||||
, m_position(index)
|
||||
{}
|
||||
{
|
||||
if (!it.is_object_iterator())
|
||||
{
|
||||
m_index = std::to_string(index);
|
||||
}
|
||||
}
|
||||
|
||||
/// the member key, or the element index for arrays (as text made on the
|
||||
/// first call, so that iterating over the values pays nothing for it)
|
||||
/// the member key, or the element index for arrays
|
||||
string_view_t key() const
|
||||
{
|
||||
if (m_it.is_object_iterator())
|
||||
{
|
||||
return m_it.key();
|
||||
}
|
||||
if (m_index.empty())
|
||||
{
|
||||
m_index = std::to_string(m_position);
|
||||
}
|
||||
return string_view_t(m_index.data(), m_index.size());
|
||||
}
|
||||
|
||||
@@ -184,8 +183,7 @@ class view_item
|
||||
|
||||
private:
|
||||
iterator m_it;
|
||||
std::size_t m_position; ///< the index of an array element
|
||||
mutable std::string m_index{}; ///< the text of m_position, once key() was called // NOLINT(readability-redundant-member-init)
|
||||
std::string m_index{}; // NOLINT(readability-redundant-member-init)
|
||||
};
|
||||
|
||||
/// the range returned by basic_json_view::items()
|
||||
|
||||
@@ -166,10 +166,6 @@ const node* element_at(const document_data& d, const node* array, std::size_t id
|
||||
{
|
||||
return e + idx; // a growable block: one link per element
|
||||
}
|
||||
if (array->next == array->len + 1u && (!nav::editable() || (array->flags & node_flags::moved) == 0))
|
||||
{
|
||||
return e + idx; // no element has a subtree: the elements are the nodes after the array
|
||||
}
|
||||
for (std::size_t i = 0; i < idx; ++i)
|
||||
{
|
||||
e = document_data::after(e);
|
||||
@@ -185,10 +181,6 @@ const node* last_child(const document_data& d, const node* container) noexcept
|
||||
const std::size_t value_offset = container->kind == static_cast<std::uint8_t>(value_t::object) ? 1 : 0;
|
||||
const node* const end = navigation<Editable>::end(d, container);
|
||||
const node* last = navigation<Editable>::first(d, container);
|
||||
if (value_offset == 0 && container->next == container->len + 1u && (!navigation<Editable>::editable() || (container->flags & node_flags::moved) == 0))
|
||||
{
|
||||
return last + (container->len - 1u); // an array without subtrees: the elements are the nodes after it
|
||||
}
|
||||
for (const node* c = document_data::after(last + value_offset); c != end; c = document_data::after(c + value_offset))
|
||||
{
|
||||
last = c;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array> // array
|
||||
#include <algorithm> // max
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // int64_t, uint8_t
|
||||
#include <string> // string
|
||||
@@ -44,9 +44,8 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
using nav = navigation<Editable>;
|
||||
|
||||
// the open containers: their next element (or key of the next member),
|
||||
// their end, and whether they are objects; for the first 16 levels on the
|
||||
// stack, else sized once from the document's deepest nesting (for a
|
||||
// subtree an upper bound)
|
||||
// their end, and whether they are objects; sized once from the
|
||||
// document's deepest nesting (for a subtree an upper bound)
|
||||
struct frame
|
||||
{
|
||||
const node* pos;
|
||||
@@ -54,43 +53,10 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
bool object;
|
||||
};
|
||||
|
||||
// a scalar needs neither the handler nor the stack of open containers
|
||||
switch (static_cast<value_t>(n->kind))
|
||||
{
|
||||
case value_t::string:
|
||||
return BasicJsonType(string_t(d.str(*n), n->len));
|
||||
case value_t::number_integer:
|
||||
return BasicJsonType(static_cast<typename BasicJsonType::number_integer_t>(static_cast<std::int64_t>(integer_bits(*n))));
|
||||
case value_t::number_unsigned:
|
||||
return BasicJsonType(static_cast<typename BasicJsonType::number_unsigned_t>(integer_bits(*n)));
|
||||
case value_t::number_float:
|
||||
return BasicJsonType(float_value<typename BasicJsonType::number_float_t>(d, *n));
|
||||
case value_t::boolean:
|
||||
return BasicJsonType((n->flags & node_flags::is_true) != 0);
|
||||
case value_t::null:
|
||||
return BasicJsonType(nullptr);
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
BasicJsonType result;
|
||||
sax_t sax(result, true);
|
||||
const string_t no_token{};
|
||||
// (shallow subtrees on the stack: a call for a small value then allocates only for the value)
|
||||
std::array<frame, 16> shallow; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): filled as containers open
|
||||
std::vector<frame> deep{};
|
||||
frame* open = shallow.data();
|
||||
std::size_t capacity = shallow.size();
|
||||
if (d.max_depth > capacity)
|
||||
{
|
||||
deep.resize(d.max_depth);
|
||||
open = deep.data();
|
||||
capacity = deep.size();
|
||||
}
|
||||
std::vector<frame> open((std::max)(d.max_depth, std::size_t{1}));
|
||||
std::size_t depth = 0;
|
||||
for (;;)
|
||||
{
|
||||
@@ -110,13 +76,9 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
{
|
||||
sax.start_array(n->len);
|
||||
}
|
||||
if (NLOHMANN_VIEW_UNLIKELY(depth == capacity))
|
||||
if (NLOHMANN_VIEW_UNLIKELY(depth == open.size()))
|
||||
{
|
||||
// (deeper than the recorded nesting, e.g. after edits)
|
||||
deep.assign(open, open + depth);
|
||||
deep.resize(depth * 2);
|
||||
open = deep.data();
|
||||
capacity = deep.size();
|
||||
open.resize(depth * 2); // (deeper than the recorded nesting, e.g. after edits)
|
||||
}
|
||||
open[depth++] = frame{nav::first(d, n), nav::end(d, n), object};
|
||||
break;
|
||||
|
||||
@@ -47,96 +47,27 @@ constexpr std::size_t index_max_displacement = 64;
|
||||
static_assert(document_data::index_min_members >= 2 * index_max_displacement,
|
||||
"a table must be much larger than the probe window of a lookup");
|
||||
|
||||
/// hash of a key: its bytes, eight at a time (in a fixed byte order), where the
|
||||
/// last word may overlap the one before (so that no loop over single bytes is
|
||||
/// needed); a key of fewer than eight bytes is read as two overlapping words of
|
||||
/// four or two bytes (in the native byte order: the hash is not stored, and the
|
||||
/// same on both sides of a lookup)
|
||||
/// hash of a key: its bytes, eight at a time, in a fixed byte order
|
||||
inline std::uint64_t key_hash(const char* s, std::size_t n) noexcept
|
||||
{
|
||||
std::uint64_t h = 0x9E3779B97F4A7C15u * (n + 1);
|
||||
const auto* p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
while (n >= 8)
|
||||
{
|
||||
h = (h ^ read_eight_bytes(p)) * 0xBF58476D1CE4E5B9u;
|
||||
h ^= h >> 29u;
|
||||
p += 8;
|
||||
n -= 8;
|
||||
}
|
||||
std::uint64_t w = 0;
|
||||
if (n >= 8)
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
const unsigned char* const last = p + n - 8;
|
||||
while (p < last)
|
||||
{
|
||||
h = (h ^ read_eight_bytes(p)) * 0xBF58476D1CE4E5B9u;
|
||||
h ^= h >> 29u;
|
||||
p += 8;
|
||||
}
|
||||
w = read_eight_bytes(last);
|
||||
w |= static_cast<std::uint64_t>(p[i]) << (8u * i);
|
||||
}
|
||||
else if (n >= 4)
|
||||
{
|
||||
std::uint32_t head = 0;
|
||||
std::uint32_t tail = 0;
|
||||
std::memcpy(&head, p, 4);
|
||||
std::memcpy(&tail, p + n - 4, 4);
|
||||
w = static_cast<std::uint64_t>(head) | (static_cast<std::uint64_t>(tail) << 32u);
|
||||
}
|
||||
else if (n >= 2)
|
||||
{
|
||||
std::uint16_t head = 0;
|
||||
std::uint16_t tail = 0;
|
||||
std::memcpy(&head, p, 2);
|
||||
std::memcpy(&tail, p + n - 2, 2);
|
||||
w = static_cast<std::uint64_t>(head) | (static_cast<std::uint64_t>(tail) << 16u);
|
||||
}
|
||||
else if (n == 1)
|
||||
{
|
||||
w = p[0];
|
||||
}
|
||||
// (a product only carries bits upwards, and the slot is taken from the low
|
||||
// bits: the high bits of the last word are folded down before the multiplication)
|
||||
h = (h ^ w ^ (w >> 29u)) * 0x94D049BB133111EBu;
|
||||
h = (h ^ w) * 0x94D049BB133111EBu;
|
||||
return h ^ (h >> 31u);
|
||||
}
|
||||
|
||||
/// whether the n <= 16 bytes at a and b are equal, as two overlapping words
|
||||
/// (the first and the last 8, 4, or 2 bytes) and without a call
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE bool same_short_bytes(const unsigned char* a, const unsigned char* b, std::size_t n) noexcept
|
||||
{
|
||||
if (n >= 8)
|
||||
{
|
||||
std::uint64_t x0 = 0;
|
||||
std::uint64_t x1 = 0;
|
||||
std::uint64_t y0 = 0;
|
||||
std::uint64_t y1 = 0;
|
||||
std::memcpy(&x0, a, 8);
|
||||
std::memcpy(&x1, a + n - 8, 8);
|
||||
std::memcpy(&y0, b, 8);
|
||||
std::memcpy(&y1, b + n - 8, 8);
|
||||
return x0 == y0 && x1 == y1;
|
||||
}
|
||||
if (n >= 4)
|
||||
{
|
||||
std::uint32_t x0 = 0;
|
||||
std::uint32_t x1 = 0;
|
||||
std::uint32_t y0 = 0;
|
||||
std::uint32_t y1 = 0;
|
||||
std::memcpy(&x0, a, 4);
|
||||
std::memcpy(&x1, a + n - 4, 4);
|
||||
std::memcpy(&y0, b, 4);
|
||||
std::memcpy(&y1, b + n - 4, 4);
|
||||
return x0 == y0 && x1 == y1;
|
||||
}
|
||||
if (n >= 2)
|
||||
{
|
||||
std::uint16_t x0 = 0;
|
||||
std::uint16_t x1 = 0;
|
||||
std::uint16_t y0 = 0;
|
||||
std::uint16_t y1 = 0;
|
||||
std::memcpy(&x0, a, 2);
|
||||
std::memcpy(&x1, a + n - 2, 2);
|
||||
std::memcpy(&y0, b, 2);
|
||||
std::memcpy(&y1, b + n - 2, 2);
|
||||
return x0 == y0 && x1 == y1;
|
||||
}
|
||||
return n == 0 || *a == *b;
|
||||
}
|
||||
|
||||
/// build the table of a large object
|
||||
inline void build_object_index(document_data& d, node* obj)
|
||||
{
|
||||
@@ -201,27 +132,6 @@ inline const node* find_indexed(const document_data& d, const node* obj, const c
|
||||
const document_data::object_index& ix = d.indexes[obj->extra - 1u];
|
||||
const std::uint32_t* const table = d.index_slots.data() + ix.start;
|
||||
std::size_t slot = conditional_static_cast<std::size_t>(key_hash(key, n)) & ix.mask;
|
||||
const auto* const k_bytes = reinterpret_cast<const unsigned char*>(key); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
// (the loop for longer keys comes first, as the code is laid out; it is not the more common case)
|
||||
if (NLOHMANN_VIEW_UNLIKELY(n <= 16))
|
||||
{
|
||||
// (up to 16 bytes, the bytes are compared as two words, without a call)
|
||||
for (std::size_t distance = 0; distance <= index_max_displacement; ++distance)
|
||||
{
|
||||
const std::uint32_t s = table[slot];
|
||||
if (s == 0)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
const node* const k = obj + s;
|
||||
if (k->len == n && same_short_bytes(reinterpret_cast<const unsigned char*>(d.str(*k)), k_bytes, n)) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
{
|
||||
return k;
|
||||
}
|
||||
slot = (slot + 1) & ix.mask;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
for (std::size_t distance = 0; distance <= index_max_displacement; ++distance)
|
||||
{
|
||||
const std::uint32_t s = table[slot];
|
||||
|
||||
@@ -116,34 +116,6 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* vector_plain_run(const unsigned
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief copy 16 bytes from p to w, and find the first quote, backslash,
|
||||
control character, or non-ASCII byte among them
|
||||
|
||||
The bytes are copied whether or not they are all plain (the caller has room
|
||||
for 16 bytes and keeps only the plain ones).
|
||||
|
||||
@return the offset of that byte, or 16 if there is none
|
||||
*/
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE unsigned copy_plain_run16(const unsigned char* p, char* w) noexcept
|
||||
{
|
||||
#if NLOHMANN_VIEW_NEON
|
||||
const uint8x16_t in = vld1q_u8(p);
|
||||
vst1q_u8(reinterpret_cast<std::uint8_t*>(w), in); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
|
||||
vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20)));
|
||||
const std::uint64_t bits = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
|
||||
return bits != 0 ? static_cast<unsigned>(count_trailing_zeros(bits) >> 2u) : 16u;
|
||||
#else
|
||||
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
||||
_mm_storeu_si128(static_cast<__m128i*>(static_cast<void*>(w)), in); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
||||
_mm_cmplt_epi8(in, _mm_set1_epi8(0x20)));
|
||||
const auto bits = static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
|
||||
return bits != 0 ? static_cast<unsigned>(count_trailing_zeros(bits)) : 16u;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#if NLOHMANN_VIEW_SSSE3_DISPATCH
|
||||
|
||||
@@ -295,7 +295,7 @@ class basic_json_view
|
||||
}
|
||||
detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name());
|
||||
}
|
||||
return idx < m_node->len ? element(idx) : basic_json_view();
|
||||
return idx < m_node->len ? basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx))) : basic_json_view();
|
||||
}
|
||||
|
||||
/// any other integer type (int, unsigned, long, std::int64_t, ...; a
|
||||
@@ -358,7 +358,7 @@ class basic_json_view
|
||||
{
|
||||
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
|
||||
}
|
||||
return element(idx);
|
||||
return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
|
||||
}
|
||||
|
||||
/// any other integer type, see operator[]; negative values are out of
|
||||
@@ -392,13 +392,7 @@ class basic_json_view
|
||||
|
||||
string_t value(string_view_t key, const char* default_value) const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
||||
{
|
||||
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
|
||||
}
|
||||
// (the default is made only if there is no such member)
|
||||
const basic_json_view r = lookup(key);
|
||||
return r.is_discarded() ? string_t(default_value) : r.template get<string_t>();
|
||||
return value(key, string_t(default_value));
|
||||
}
|
||||
|
||||
/// the value a JSON pointer refers to converted to T, or the default
|
||||
@@ -417,12 +411,7 @@ class basic_json_view
|
||||
|
||||
string_t value(const json_pointer& ptr, const char* default_value) const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
||||
{
|
||||
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
|
||||
}
|
||||
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
|
||||
return r.is_discarded() ? string_t(default_value) : r.template get<string_t>();
|
||||
return value(ptr, string_t(default_value));
|
||||
}
|
||||
|
||||
/// the first element or member value; a primitive value itself. Throws
|
||||
@@ -444,8 +433,7 @@ class basic_json_view
|
||||
{
|
||||
if (is_structured() && m_node->len != 0)
|
||||
{
|
||||
return unedited() ? basic_json_view(m_doc, detail::view::last_child<false>(*m_doc, m_node) + (is_object() ? 1 : 0))
|
||||
: basic_json_view(m_doc, navigation::value(detail::view::last_child<Editable>(*m_doc, m_node) + (is_object() ? 1 : 0)));
|
||||
return basic_json_view(m_doc, navigation::value(detail::view::last_child<Editable>(*m_doc, m_node) + (is_object() ? 1 : 0)));
|
||||
}
|
||||
return front();
|
||||
}
|
||||
@@ -462,8 +450,7 @@ class basic_json_view
|
||||
{
|
||||
return end();
|
||||
}
|
||||
const node* const k = unedited() ? detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size())
|
||||
: detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
|
||||
const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
|
||||
return k != nullptr ? iterator(m_doc, k, true) : end();
|
||||
}
|
||||
|
||||
@@ -480,8 +467,7 @@ class basic_json_view
|
||||
/// whether this is an object with a member with this key
|
||||
bool contains(string_view_t key) const
|
||||
{
|
||||
return is_object() && (unedited() ? detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size()) != nullptr
|
||||
: detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size()) != nullptr);
|
||||
return is_object() && detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size()) != nullptr;
|
||||
}
|
||||
|
||||
bool contains(const char* key) const
|
||||
@@ -641,14 +627,7 @@ class basic_json_view
|
||||
// the compact writer keeps 64 bytes of slack, so that it does not grow
|
||||
// the buffer just before the end
|
||||
const std::size_t estimate = source_extent() + (style.pretty ? source_extent() / 2 : 0) + 160;
|
||||
if (unedited())
|
||||
{
|
||||
detail::view::view_serializer<BasicJsonType, false>(*m_doc, out, estimate, style).dump(m_node);
|
||||
}
|
||||
else
|
||||
{
|
||||
detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node);
|
||||
}
|
||||
detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node);
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -728,7 +707,7 @@ class basic_json_view
|
||||
{
|
||||
return BasicJsonType(value_t::discarded);
|
||||
}
|
||||
return unedited() ? detail::view::materialize<BasicJsonType, false>(*m_doc, m_node) : detail::view::materialize<BasicJsonType, Editable>(*m_doc, m_node);
|
||||
return detail::view::materialize<BasicJsonType, Editable>(*m_doc, m_node);
|
||||
}
|
||||
|
||||
/// byte offset of this value in the source text (for strings: of the
|
||||
@@ -806,32 +785,10 @@ class basic_json_view
|
||||
/// (object required)
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept
|
||||
{
|
||||
if (unedited())
|
||||
{
|
||||
const node* const k = detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size());
|
||||
return k != nullptr ? basic_json_view(m_doc, k + 1) : basic_json_view();
|
||||
}
|
||||
const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
|
||||
return k != nullptr ? basic_json_view(m_doc, navigation::value(k + 1)) : basic_json_view();
|
||||
}
|
||||
|
||||
/// whether the document has not been edited yet: until the first edit, its index is walked like that of a
|
||||
/// read-only document (nothing is moved or linked), which is cheaper
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE bool unedited() const noexcept
|
||||
{
|
||||
return !navigation::editable() || m_doc->edits == nullptr;
|
||||
}
|
||||
|
||||
/// the element of an array at an index below its size
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view element(size_type idx) const
|
||||
{
|
||||
if (unedited())
|
||||
{
|
||||
return basic_json_view(m_doc, detail::view::element_at<false>(*m_doc, m_node, idx));
|
||||
}
|
||||
return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
|
||||
}
|
||||
|
||||
// --- get() dispatch ---
|
||||
|
||||
bool get_impl(detail::view::value_tag<bool> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
@@ -1571,8 +1528,7 @@ class basic_json_document
|
||||
// NOLINTBEGIN(readability-avoid-nested-conditional-operator): a constant expression of C++11
|
||||
append_input_impl(buf, std::forward<T>(input), std::integral_constant < int, detail::is_contiguous_byte_container<D>::value ? 0
|
||||
: std::is_base_of<std::istream, D>::value ? 1
|
||||
: detail::view::is_adapter_input<D>::value ? 2
|
||||
: detail::view::is_c_stream<D>::value ? 4 : 3 > {});
|
||||
: detail::view::is_adapter_input<D>::value ? 2 : 3 > {});
|
||||
// NOLINTEND(readability-avoid-nested-conditional-operator)
|
||||
}
|
||||
|
||||
@@ -1594,19 +1550,6 @@ class basic_json_document
|
||||
detail::view::append_adapter(buf, std::move(input.ia));
|
||||
}
|
||||
|
||||
#ifndef JSON_NO_IO
|
||||
static void append_input_impl(std::string& buf, std::FILE* file, std::integral_constant<int, 4> /*C stream*/)
|
||||
{
|
||||
if (file == nullptr)
|
||||
{
|
||||
// (as before: the library's adapter decides what a null stream yields)
|
||||
detail::view::append_adapter(buf, detail::input_adapter(file));
|
||||
return;
|
||||
}
|
||||
detail::view::append_file(buf, file);
|
||||
}
|
||||
#endif
|
||||
|
||||
template<typename T>
|
||||
static void append_input_impl(std::string& buf, T&& input, std::integral_constant<int, 3> /*other*/)
|
||||
{
|
||||
|
||||
Generated
+551
-114
@@ -30178,6 +30178,14 @@ NLOHMANN_JSON_NAMESPACE_END
|
||||
// #include <nlohmann/detail/value_t.hpp>
|
||||
|
||||
|
||||
// the short-string fast path of the serializer assembles words from little-endian
|
||||
// loads, see serializer::put_short_quoted()
|
||||
#if defined(_MSC_VER) || defined(__x86_64__) || defined(__i386__) || (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
|
||||
#define JSON_DUMP_SHORT_STRINGS 1
|
||||
#else
|
||||
#define JSON_DUMP_SHORT_STRINGS 0
|
||||
#endif
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
@@ -30224,11 +30232,14 @@ class serializer
|
||||
const std::size_t indent_step_ = 0,
|
||||
error_handler_t error_handler_ = error_handler_t::strict)
|
||||
: o(&s)
|
||||
, number_buffer() // NOLINT(readability-redundant-member-init)
|
||||
, string_buffer() // NOLINT(readability-redundant-member-init)
|
||||
, indent_char(ichar)
|
||||
, pretty_print(pretty_print_)
|
||||
, ensure_ascii(ensure_ascii_)
|
||||
, indent_step(indent_step_)
|
||||
, error_handler(error_handler_)
|
||||
, write_buffer() // NOLINT(readability-redundant-member-init)
|
||||
{}
|
||||
|
||||
// deleted because of pointer members
|
||||
@@ -30303,34 +30314,27 @@ class serializer
|
||||
|
||||
if (pretty_print)
|
||||
{
|
||||
put_literal("{\n");
|
||||
|
||||
// variable to hold indentation for recursive calls
|
||||
const auto new_indent = next_indent(current_indent, indent_step);
|
||||
|
||||
put_literal_indent("{\n", new_indent);
|
||||
|
||||
// first n-1 elements
|
||||
auto i = val.m_data.m_value.object->cbegin();
|
||||
for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
|
||||
{
|
||||
put_indent(new_indent);
|
||||
put_char('"');
|
||||
dump_escaped(i->first);
|
||||
put_literal("\": ");
|
||||
dump_internal(i->second, new_indent, depth + 1);
|
||||
put_literal(",\n");
|
||||
put_quoted(i->first, "\": ");
|
||||
dump_element(i->second, new_indent, depth + 1);
|
||||
put_literal_indent(",\n", new_indent);
|
||||
}
|
||||
|
||||
// last element
|
||||
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
||||
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
||||
put_indent(new_indent);
|
||||
put_char('"');
|
||||
dump_escaped(i->first);
|
||||
put_literal("\": ");
|
||||
dump_internal(i->second, new_indent, depth + 1);
|
||||
put_quoted(i->first, "\": ");
|
||||
dump_element(i->second, new_indent, depth + 1);
|
||||
|
||||
put_char('\n');
|
||||
put_indent(current_indent);
|
||||
put_literal_indent("\n", current_indent);
|
||||
put_char('}');
|
||||
}
|
||||
else
|
||||
@@ -30341,20 +30345,16 @@ class serializer
|
||||
auto i = val.m_data.m_value.object->cbegin();
|
||||
for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
|
||||
{
|
||||
put_char('"');
|
||||
dump_escaped(i->first);
|
||||
put_literal("\":");
|
||||
dump_internal(i->second, current_indent, depth + 1);
|
||||
put_quoted(i->first, "\":");
|
||||
dump_element(i->second, current_indent, depth + 1);
|
||||
put_char(',');
|
||||
}
|
||||
|
||||
// last element
|
||||
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
||||
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
||||
put_char('"');
|
||||
dump_escaped(i->first);
|
||||
put_literal("\":");
|
||||
dump_internal(i->second, current_indent, depth + 1);
|
||||
put_quoted(i->first, "\":");
|
||||
dump_element(i->second, current_indent, depth + 1);
|
||||
|
||||
put_char('}');
|
||||
}
|
||||
@@ -30378,27 +30378,24 @@ class serializer
|
||||
|
||||
if (pretty_print)
|
||||
{
|
||||
put_literal("[\n");
|
||||
|
||||
// variable to hold indentation for recursive calls
|
||||
const auto new_indent = next_indent(current_indent, indent_step);
|
||||
|
||||
put_literal_indent("[\n", new_indent);
|
||||
|
||||
// first n-1 elements
|
||||
for (auto i = val.m_data.m_value.array->cbegin();
|
||||
i != val.m_data.m_value.array->cend() - 1; ++i)
|
||||
{
|
||||
put_indent(new_indent);
|
||||
dump_internal(*i, new_indent, depth + 1);
|
||||
put_literal(",\n");
|
||||
dump_element(*i, new_indent, depth + 1);
|
||||
put_literal_indent(",\n", new_indent);
|
||||
}
|
||||
|
||||
// last element
|
||||
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
||||
put_indent(new_indent);
|
||||
dump_internal(val.m_data.m_value.array->back(), new_indent, depth + 1);
|
||||
dump_element(val.m_data.m_value.array->back(), new_indent, depth + 1);
|
||||
|
||||
put_char('\n');
|
||||
put_indent(current_indent);
|
||||
put_literal_indent("\n", current_indent);
|
||||
put_char(']');
|
||||
}
|
||||
else
|
||||
@@ -30409,13 +30406,13 @@ class serializer
|
||||
for (auto i = val.m_data.m_value.array->cbegin();
|
||||
i != val.m_data.m_value.array->cend() - 1; ++i)
|
||||
{
|
||||
dump_internal(*i, current_indent, depth + 1);
|
||||
dump_element(*i, current_indent, depth + 1);
|
||||
put_char(',');
|
||||
}
|
||||
|
||||
// last element
|
||||
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
||||
dump_internal(val.m_data.m_value.array->back(), current_indent, depth + 1);
|
||||
dump_element(val.m_data.m_value.array->back(), current_indent, depth + 1);
|
||||
|
||||
put_char(']');
|
||||
}
|
||||
@@ -30438,6 +30435,27 @@ class serializer
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
@brief serialize an element of a container
|
||||
|
||||
Most elements are scalars, which need none of the state dump_internal() keeps
|
||||
for a container; writing them from here spares each the entry and exit of
|
||||
that function.
|
||||
*/
|
||||
JSON_HEDLEY_ALWAYS_INLINE void dump_element(const BasicJsonType& val,
|
||||
const std::size_t current_indent,
|
||||
const std::size_t depth)
|
||||
{
|
||||
if (val.m_data.m_type == value_t::object || val.m_data.m_type == value_t::array)
|
||||
{
|
||||
dump_internal(val, current_indent, depth);
|
||||
}
|
||||
else
|
||||
{
|
||||
dump_scalar(val, current_indent);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write out @a val and everything below it without the call stack
|
||||
|
||||
@@ -30497,16 +30515,13 @@ class serializer
|
||||
put_indent(frame.child_indent);
|
||||
}
|
||||
|
||||
put_char('"');
|
||||
dump_escaped(frame.object_it->first);
|
||||
|
||||
if (pretty_print)
|
||||
{
|
||||
put_literal("\": ");
|
||||
put_quoted(frame.object_it->first, "\": ");
|
||||
}
|
||||
else
|
||||
{
|
||||
put_literal("\":");
|
||||
put_quoted(frame.object_it->first, "\":");
|
||||
}
|
||||
|
||||
const BasicJsonType& element = frame.object_it->second;
|
||||
@@ -30681,78 +30696,13 @@ class serializer
|
||||
{
|
||||
case value_t::string:
|
||||
{
|
||||
put_char('"');
|
||||
dump_escaped(*val.m_data.m_value.string);
|
||||
put_char('"');
|
||||
dump_string_value(*val.m_data.m_value.string);
|
||||
return;
|
||||
}
|
||||
|
||||
case value_t::binary:
|
||||
{
|
||||
if (pretty_print)
|
||||
{
|
||||
put_literal("{\n");
|
||||
|
||||
// variable to hold indentation for the bytes
|
||||
const auto new_indent = next_indent(current_indent, indent_step);
|
||||
|
||||
put_indent(new_indent);
|
||||
|
||||
put_literal("\"bytes\": [");
|
||||
|
||||
if (!val.m_data.m_value.binary->empty())
|
||||
{
|
||||
for (auto i = val.m_data.m_value.binary->cbegin();
|
||||
i != val.m_data.m_value.binary->cend() - 1; ++i)
|
||||
{
|
||||
dump_byte(*i);
|
||||
put_literal(", ");
|
||||
}
|
||||
dump_byte(val.m_data.m_value.binary->back());
|
||||
}
|
||||
|
||||
put_literal("],\n");
|
||||
put_indent(new_indent);
|
||||
|
||||
put_literal("\"subtype\": ");
|
||||
if (val.m_data.m_value.binary->has_subtype())
|
||||
{
|
||||
dump_integer(val.m_data.m_value.binary->subtype());
|
||||
}
|
||||
else
|
||||
{
|
||||
put_literal("null");
|
||||
}
|
||||
put_char('\n');
|
||||
put_indent(current_indent);
|
||||
put_char('}');
|
||||
}
|
||||
else
|
||||
{
|
||||
put_literal("{\"bytes\":[");
|
||||
|
||||
if (!val.m_data.m_value.binary->empty())
|
||||
{
|
||||
for (auto i = val.m_data.m_value.binary->cbegin();
|
||||
i != val.m_data.m_value.binary->cend() - 1; ++i)
|
||||
{
|
||||
dump_byte(*i);
|
||||
put_char(',');
|
||||
}
|
||||
dump_byte(val.m_data.m_value.binary->back());
|
||||
}
|
||||
|
||||
put_literal("],\"subtype\":");
|
||||
if (val.m_data.m_value.binary->has_subtype())
|
||||
{
|
||||
dump_integer(val.m_data.m_value.binary->subtype());
|
||||
put_char('}');
|
||||
}
|
||||
else
|
||||
{
|
||||
put_literal("null}");
|
||||
}
|
||||
}
|
||||
dump_binary_value(current_indent, *val.m_data.m_value.binary);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -30771,19 +30721,19 @@ class serializer
|
||||
|
||||
case value_t::number_integer:
|
||||
{
|
||||
dump_integer(val.m_data.m_value.number_integer);
|
||||
dump_signed_value(val.m_data.m_value.number_integer);
|
||||
return;
|
||||
}
|
||||
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
dump_integer(val.m_data.m_value.number_unsigned);
|
||||
dump_unsigned_value(val.m_data.m_value.number_unsigned);
|
||||
return;
|
||||
}
|
||||
|
||||
case value_t::number_float:
|
||||
{
|
||||
dump_float(val.m_data.m_value.number_float);
|
||||
dump_float_value(val.m_data.m_value.number_float);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -30806,6 +30756,107 @@ class serializer
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief write the string @a s in quotes; kept out of line, so that the
|
||||
/// other scalars do not pay for its register needs (and the other way round)
|
||||
JSON_HEDLEY_NEVER_INLINE void dump_string_value(const string_t& s)
|
||||
{
|
||||
#if JSON_DUMP_SHORT_STRINGS
|
||||
if (s.size() <= short_string_max && put_short_quoted(s, "\""))
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
put_char('"');
|
||||
dump_escaped(s);
|
||||
put_char('"');
|
||||
}
|
||||
|
||||
// The numbers, and the rest of what dump_scalar() writes, are left to
|
||||
// functions of their own as well: a single function holding all of them
|
||||
// saves and restores every register any of them needs, on every call. Now
|
||||
// dump_scalar() only dispatches, and each writer pays for what it uses.
|
||||
JSON_HEDLEY_NEVER_INLINE void dump_signed_value(const number_integer_t x)
|
||||
{
|
||||
dump_integer(x);
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NEVER_INLINE void dump_unsigned_value(const number_unsigned_t x)
|
||||
{
|
||||
dump_integer(x);
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NEVER_INLINE void dump_float_value(const number_float_t x)
|
||||
{
|
||||
dump_float(x);
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NEVER_INLINE void dump_binary_value(const std::size_t current_indent, const typename BasicJsonType::binary_t& binary)
|
||||
{
|
||||
if (pretty_print)
|
||||
{
|
||||
put_literal("{\n");
|
||||
|
||||
// variable to hold indentation for the bytes
|
||||
const auto new_indent = next_indent(current_indent, indent_step);
|
||||
|
||||
put_indent(new_indent);
|
||||
|
||||
put_literal("\"bytes\": [");
|
||||
|
||||
if (!binary.empty())
|
||||
{
|
||||
for (auto i = binary.cbegin();
|
||||
i != binary.cend() - 1; ++i)
|
||||
{
|
||||
dump_byte(*i);
|
||||
put_literal(", ");
|
||||
}
|
||||
dump_byte(binary.back());
|
||||
}
|
||||
|
||||
put_literal("],\n");
|
||||
put_indent(new_indent);
|
||||
|
||||
put_literal("\"subtype\": ");
|
||||
if (binary.has_subtype())
|
||||
{
|
||||
dump_integer(binary.subtype());
|
||||
}
|
||||
else
|
||||
{
|
||||
put_literal("null");
|
||||
}
|
||||
put_char('\n');
|
||||
put_indent(current_indent);
|
||||
put_char('}');
|
||||
}
|
||||
else
|
||||
{
|
||||
put_literal("{\"bytes\":[");
|
||||
|
||||
if (!binary.empty())
|
||||
{
|
||||
for (auto i = binary.cbegin();
|
||||
i != binary.cend() - 1; ++i)
|
||||
{
|
||||
dump_byte(*i);
|
||||
put_char(',');
|
||||
}
|
||||
dump_byte(binary.back());
|
||||
}
|
||||
|
||||
put_literal("],\"subtype\":");
|
||||
if (binary.has_subtype())
|
||||
{
|
||||
dump_integer(binary.subtype());
|
||||
put_char('}');
|
||||
}
|
||||
else
|
||||
{
|
||||
put_literal("null}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the indentation level to use for the children of the current value
|
||||
@@ -30822,6 +30873,217 @@ class serializer
|
||||
return new_indent;
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
@brief append the string @a s in quotes, followed by @a tail
|
||||
|
||||
The quote, the string and @a tail (the closing quote, possibly with the
|
||||
colon of an object key) are the bulk of what most documents consist of, and
|
||||
most keys and many values are short and plain. Those are handled in one go
|
||||
by put_short_quoted(); everything else takes the general path.
|
||||
*/
|
||||
template<std::size_t N>
|
||||
void put_quoted(const string_t& s, const char (&tail)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
{
|
||||
#if JSON_DUMP_SHORT_STRINGS
|
||||
if (put_short_quoted(s, tail))
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
put_char('"');
|
||||
dump_escaped(s);
|
||||
put_literal(tail);
|
||||
}
|
||||
|
||||
#if JSON_DUMP_SHORT_STRINGS
|
||||
/// the longest string put_short_quoted() takes
|
||||
static constexpr std::size_t short_string_max = JSON_SCAN_VECTOR ? 32 : 16;
|
||||
|
||||
/// the high bit of every byte of @a v that is not printable ASCII or is a
|
||||
/// quote, a backslash, or DEL (zero if the bytes can be copied as they are)
|
||||
static std::uint64_t swar_not_plain(const std::uint64_t v) noexcept
|
||||
{
|
||||
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
||||
const std::uint64_t q = v ^ 0x2222222222222222ull; // '"'
|
||||
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull; // '\\'
|
||||
return (((q - ones) & ~q) | ((b - ones) & ~b) // == '"', == '\\'
|
||||
| ((v - 0x2020202020202020ull) & ~v) // < 0x20
|
||||
| v | (v + ones)) // >= 0x7F
|
||||
& 0x8080808080808080ull;
|
||||
}
|
||||
|
||||
template<typename WordType>
|
||||
static WordType load_word(const char* p) noexcept
|
||||
{
|
||||
WordType w;
|
||||
std::memcpy(&w, p, sizeof(w));
|
||||
return w;
|
||||
}
|
||||
|
||||
template<typename WordType>
|
||||
static void store_word(char* p, const WordType w) noexcept
|
||||
{
|
||||
std::memcpy(p, &w, sizeof(w));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write `"`, @a s and @a tail if @a s is short and needs no escaping
|
||||
|
||||
A string of up to 16 (32 with vector instructions) printable ASCII
|
||||
characters (no quote, backslash, or DEL) is read with two overlapping loads
|
||||
and written with two overlapping stores, instead of scanning it and calling
|
||||
memcpy with a length that is only known at run time. Returns false, having
|
||||
changed nothing, if @a s does not qualify or the write buffer lacks the
|
||||
room.
|
||||
*/
|
||||
template<std::size_t N>
|
||||
bool put_short_quoted(const string_t& s, const char (&tail)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
{
|
||||
const std::size_t n = s.size();
|
||||
if (n > short_string_max || write_buffer_pos + n + N > write_buffer.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
char* d = write_buffer.data() + write_buffer_pos + 1;
|
||||
const char* const p = s.data();
|
||||
|
||||
if (n >= 8)
|
||||
{
|
||||
#if JSON_SCAN_VECTOR
|
||||
if (!(n >= 16 ? copy_plain_vectors(d, p, n) : copy_plain_halves(d, p, n)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
const auto a = load_word<std::uint64_t>(p);
|
||||
const auto b = load_word<std::uint64_t>(p + n - 8);
|
||||
if ((swar_not_plain(a) | swar_not_plain(b)) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
store_word(d, a);
|
||||
store_word(d + n - 8, b);
|
||||
#endif
|
||||
}
|
||||
else if (n >= 4)
|
||||
{
|
||||
const auto a = load_word<std::uint32_t>(p);
|
||||
const auto b = load_word<std::uint32_t>(p + n - 4);
|
||||
// the bytes above the string are filled with a plain character
|
||||
const std::uint64_t w = a | (static_cast<std::uint64_t>(b) << (8 * (n - 4)))
|
||||
| (0x4141414141414141ull << (8 * n));
|
||||
if (swar_not_plain(w) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
store_word(d, a);
|
||||
store_word(d + n - 4, b);
|
||||
}
|
||||
else if (n >= 2)
|
||||
{
|
||||
const auto a = load_word<std::uint16_t>(p);
|
||||
const auto b = load_word<std::uint16_t>(p + n - 2);
|
||||
const std::uint64_t w = a | (static_cast<std::uint64_t>(b) << (8 * (n - 2)))
|
||||
| (0x4141414141414141ull << (8 * n));
|
||||
if (swar_not_plain(w) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
store_word(d, a);
|
||||
store_word(d + n - 2, b);
|
||||
}
|
||||
else if (n == 1)
|
||||
{
|
||||
const std::uint64_t w = static_cast<std::uint64_t>(static_cast<unsigned char>(p[0])) | (0x4141414141414141ull << 8u);
|
||||
if (swar_not_plain(w) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
*d = p[0];
|
||||
}
|
||||
|
||||
d[-1] = '"';
|
||||
std::memcpy(d + n, tail, N - 1);
|
||||
write_buffer_pos += n + N;
|
||||
return true;
|
||||
}
|
||||
|
||||
#if JSON_SCAN_VECTOR
|
||||
/// copy the 8 to 15 bytes [@a p, @a p + @a n) to @a d with two overlapping
|
||||
/// words, tested together as one vector, see copy_plain_vectors()
|
||||
static bool copy_plain_halves(char* d, const char* p, const std::size_t n) noexcept
|
||||
{
|
||||
#if JSON_SCAN_NEON
|
||||
const uint8x8_t lo = vld1_u8(reinterpret_cast<const std::uint8_t*>(p));
|
||||
const uint8x8_t hi = vld1_u8(reinterpret_cast<const std::uint8_t*>(p + n - 8));
|
||||
const uint8x16_t in = vcombine_u8(lo, hi);
|
||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
|
||||
vorrq_u8(vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20)), vceqq_u8(in, vdupq_n_u8(0x7F))));
|
||||
if (vmaxvq_u8(special) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
vst1_u8(reinterpret_cast<std::uint8_t*>(d), lo);
|
||||
vst1_u8(reinterpret_cast<std::uint8_t*>(d + n - 8), hi);
|
||||
#else
|
||||
const __m128i lo = _mm_loadl_epi64(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i hi = _mm_loadl_epi64(static_cast<const __m128i*>(static_cast<const void*>(p + n - 8))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i in = _mm_unpacklo_epi64(lo, hi);
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
||||
_mm_or_si128(_mm_cmplt_epi8(in, _mm_set1_epi8(0x20)), _mm_cmpeq_epi8(in, _mm_set1_epi8(0x7F))));
|
||||
if (_mm_movemask_epi8(special) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_mm_storel_epi64(static_cast<__m128i*>(static_cast<void*>(d)), lo); // NOLINT(bugprone-casting-through-void)
|
||||
_mm_storel_epi64(static_cast<__m128i*>(static_cast<void*>(d + n - 8)), hi); // NOLINT(bugprone-casting-through-void)
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
/// copy the 16 to 32 bytes [@a p, @a p + @a n) to @a d with two overlapping
|
||||
/// vectors, unless one of them is a quote, a backslash, a control
|
||||
/// character, DEL or not ASCII
|
||||
static bool copy_plain_vectors(char* d, const char* p, const std::size_t n) noexcept
|
||||
{
|
||||
#if JSON_SCAN_NEON
|
||||
const uint8x16_t a = vld1q_u8(reinterpret_cast<const std::uint8_t*>(p));
|
||||
const uint8x16_t b = vld1q_u8(reinterpret_cast<const std::uint8_t*>(p + n - 16));
|
||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vorrq_u8(vceqq_u8(a, vdupq_n_u8('"')), vceqq_u8(a, vdupq_n_u8('\\'))),
|
||||
vorrq_u8(vceqq_u8(b, vdupq_n_u8('"')), vceqq_u8(b, vdupq_n_u8('\\')))),
|
||||
vorrq_u8(vorrq_u8(vcltq_s8(vreinterpretq_s8_u8(a), vdupq_n_s8(0x20)), vcltq_s8(vreinterpretq_s8_u8(b), vdupq_n_s8(0x20))),
|
||||
vorrq_u8(vceqq_u8(a, vdupq_n_u8(0x7F)), vceqq_u8(b, vdupq_n_u8(0x7F)))));
|
||||
if (vmaxvq_u8(special) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
vst1q_u8(reinterpret_cast<std::uint8_t*>(d), a);
|
||||
vst1q_u8(reinterpret_cast<std::uint8_t*>(d + n - 16), b);
|
||||
#else
|
||||
const __m128i a = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i b = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p + n - 16))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i quote = _mm_set1_epi8('"');
|
||||
const __m128i backslash = _mm_set1_epi8('\\');
|
||||
const __m128i space = _mm_set1_epi8(0x20);
|
||||
const __m128i del = _mm_set1_epi8(0x7F);
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(a, quote), _mm_cmpeq_epi8(a, backslash)),
|
||||
_mm_or_si128(_mm_cmpeq_epi8(b, quote), _mm_cmpeq_epi8(b, backslash))),
|
||||
_mm_or_si128(_mm_or_si128(_mm_cmplt_epi8(a, space), _mm_cmplt_epi8(b, space)),
|
||||
_mm_or_si128(_mm_cmpeq_epi8(a, del), _mm_cmpeq_epi8(b, del))));
|
||||
if (_mm_movemask_epi8(special) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_mm_storeu_si128(static_cast<__m128i*>(static_cast<void*>(d)), a); // NOLINT(bugprone-casting-through-void)
|
||||
_mm_storeu_si128(static_cast<__m128i*>(static_cast<void*>(d + n - 16)), b); // NOLINT(bugprone-casting-through-void)
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
JSON_PRIVATE_UNLESS_TESTED:
|
||||
/*!
|
||||
@brief dump escaped string
|
||||
@@ -30834,14 +31096,23 @@ class serializer
|
||||
@param[in] s the string to escape
|
||||
|
||||
Complexity: Linear in the length of string @a s.
|
||||
|
||||
@note kept out of line: with the short-string attempts in front of it gone
|
||||
from here, compilers would otherwise inline it into @ref put_quoted
|
||||
and then no longer into the loops that call that
|
||||
*/
|
||||
void dump_escaped(const string_t& s)
|
||||
JSON_HEDLEY_NEVER_INLINE void dump_escaped(const string_t& s)
|
||||
{
|
||||
// Short strings of printable ASCII - most object keys and many values -
|
||||
// are checked byte by byte against a table and then copied in one go.
|
||||
// For them, this is cheaper than starting the vector scanner below,
|
||||
// whose setup costs more than such a string takes to scan. The same
|
||||
// check serves both values of ensure_ascii.
|
||||
//
|
||||
// Where put_short_quoted() exists and takes strings of up to 32 bytes,
|
||||
// the callers have tried it already, and repeating the attempt only
|
||||
// costs time for strings that need escaping or are not ASCII.
|
||||
#if !(JSON_DUMP_SHORT_STRINGS && JSON_SCAN_VECTOR)
|
||||
if (s.size() <= 32)
|
||||
{
|
||||
const auto& copyable = ascii_copyable();
|
||||
@@ -30863,6 +31134,7 @@ class serializer
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// dispatch once here rather than test the flag inside the loop: it does
|
||||
// not change while a string is written, and folding it lets each of the
|
||||
@@ -30940,7 +31212,7 @@ class serializer
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
put_string(s, i, i + run);
|
||||
put_run(s, i, run);
|
||||
bytes_after_last_accept = 0;
|
||||
undumped_chars = 0;
|
||||
i += run;
|
||||
@@ -30980,6 +31252,27 @@ class serializer
|
||||
|
||||
const auto byte = static_cast<std::uint8_t>(s[i]);
|
||||
|
||||
if (EnsureAscii && state == UTF8_ACCEPT && byte >= 0x80)
|
||||
{
|
||||
// a run of well-formed multi-byte characters is escaped
|
||||
// straight into the write buffer; an ill-formed sequence
|
||||
// ends the run and is left to the byte-at-a-time path
|
||||
if (bytes != 0)
|
||||
{
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
bytes_after_last_accept = 0;
|
||||
}
|
||||
const std::size_t escaped = write_u_escapes(reinterpret_cast<const unsigned char*>(s.data()) + i, s.size() - i);
|
||||
if (escaped != 0)
|
||||
{
|
||||
bytes_after_last_accept = 0;
|
||||
undumped_chars = 0;
|
||||
i += escaped - 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
switch (decode(state, codepoint, byte))
|
||||
{
|
||||
case UTF8_ACCEPT: // decode found a new code point
|
||||
@@ -31257,6 +31550,21 @@ class serializer
|
||||
return;
|
||||
}
|
||||
|
||||
// the usual indentation: four words of the indentation character are
|
||||
// stored unconditionally and only the part wanted is claimed, which
|
||||
// spares the call of memset
|
||||
if (JSON_HEDLEY_LIKELY(indent <= 32 && write_buffer_pos + 32 <= write_buffer.size()))
|
||||
{
|
||||
const std::uint64_t word = 0x0101010101010101ull * static_cast<unsigned char>(indent_char);
|
||||
char* const d = write_buffer.data() + write_buffer_pos;
|
||||
std::memcpy(d, &word, sizeof(word));
|
||||
std::memcpy(d + 8, &word, sizeof(word));
|
||||
std::memcpy(d + 16, &word, sizeof(word));
|
||||
std::memcpy(d + 24, &word, sizeof(word));
|
||||
write_buffer_pos += indent;
|
||||
return;
|
||||
}
|
||||
|
||||
const std::size_t capacity = write_buffer.size();
|
||||
|
||||
// fill whatever room is left in the buffer; this is the whole job
|
||||
@@ -31290,6 +31598,32 @@ class serializer
|
||||
write_buffer_pos = indent;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief append a string literal and then @a indent indentation characters
|
||||
|
||||
The usual line break and indentation go into the buffer with one check of
|
||||
its room and four stores of the indentation, see @ref put_indent.
|
||||
*/
|
||||
template<std::size_t N>
|
||||
JSON_HEDLEY_ALWAYS_INLINE void put_literal_indent(const char (&s)[N], const std::size_t indent) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
{
|
||||
constexpr std::size_t length = N - 1;
|
||||
if (JSON_HEDLEY_LIKELY(indent <= 32 && write_buffer_pos + length + 32 <= write_buffer.size()))
|
||||
{
|
||||
const std::uint64_t word = 0x0101010101010101ull * static_cast<unsigned char>(indent_char);
|
||||
char* const d = write_buffer.data() + write_buffer_pos;
|
||||
std::memcpy(d, s, length);
|
||||
std::memcpy(d + length, &word, sizeof(word));
|
||||
std::memcpy(d + length + 8, &word, sizeof(word));
|
||||
std::memcpy(d + length + 16, &word, sizeof(word));
|
||||
std::memcpy(d + length + 24, &word, sizeof(word));
|
||||
write_buffer_pos += length + indent;
|
||||
return;
|
||||
}
|
||||
put_literal(s);
|
||||
put_indent(indent);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief append a string literal to the write buffer
|
||||
|
||||
@@ -31314,6 +31648,28 @@ class serializer
|
||||
write_buffer_pos += length;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief append the characters of @a str in [@a start, @a start + @a length)
|
||||
|
||||
A run of up to 16 characters with 16 or more in the string from @a start on
|
||||
and in the write buffer from its end on is copied as a whole block of 16,
|
||||
which spares the call of memcpy with a length known only at run time; the
|
||||
surplus characters in the buffer are overwritten by what follows.
|
||||
*/
|
||||
template<typename StringType>
|
||||
void put_run(const StringType& str, const std::size_t start, const std::size_t length)
|
||||
{
|
||||
// a short run is the exception in text without escapes (CJK, accented
|
||||
// Latin), for which this keeps the long path the fast one
|
||||
if (JSON_HEDLEY_UNLIKELY(length <= 16 && start + 16 <= str.size() && write_buffer_pos + 16 <= write_buffer.size()))
|
||||
{
|
||||
std::memcpy(write_buffer.data() + write_buffer_pos, str.data() + start, 16);
|
||||
write_buffer_pos += length;
|
||||
return;
|
||||
}
|
||||
put_string(str, start, start + length);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief append the characters of @a str in [@a start, @a end)
|
||||
|
||||
@@ -31556,6 +31912,75 @@ class serializer
|
||||
return escapes;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief escape the well-formed multi-byte characters at the start of @a data
|
||||
|
||||
Writes the "\\uXXXX" escape of each character (two of them for a character
|
||||
above U+FFFF) straight into the write buffer, which is why the caller must
|
||||
have written out what is pending in @ref string_buffer. Stops at the first
|
||||
byte that is below 0x80 or starts an ill-formed or truncated sequence, which
|
||||
the caller handles.
|
||||
|
||||
@return the number of bytes of @a data that have been escaped
|
||||
*/
|
||||
JSON_HEDLEY_NEVER_INLINE std::size_t write_u_escapes(const unsigned char* data, std::size_t n)
|
||||
{
|
||||
constexpr const char* nibble_to_hex = "0123456789abcdef";
|
||||
std::size_t i = 0;
|
||||
while (i < n && data[i] >= 0x80)
|
||||
{
|
||||
const std::size_t length = validate_one_utf8(data + i, n - i);
|
||||
if (length == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
const auto b0 = static_cast<std::uint32_t>(data[i]);
|
||||
const auto b1 = static_cast<std::uint32_t>(data[i + 1]);
|
||||
std::uint32_t codepoint = 0;
|
||||
switch (length)
|
||||
{
|
||||
case 2:
|
||||
codepoint = ((b0 & 0x1Fu) << 6u) | (b1 & 0x3Fu);
|
||||
break;
|
||||
case 3:
|
||||
codepoint = ((b0 & 0x0Fu) << 12u) | ((b1 & 0x3Fu) << 6u) | (static_cast<std::uint32_t>(data[i + 2]) & 0x3Fu);
|
||||
break;
|
||||
default:
|
||||
codepoint = ((b0 & 0x07u) << 18u) | ((b1 & 0x3Fu) << 12u) | ((static_cast<std::uint32_t>(data[i + 2]) & 0x3Fu) << 6u) | (static_cast<std::uint32_t>(data[i + 3]) & 0x3Fu);
|
||||
break;
|
||||
}
|
||||
|
||||
// at most two escapes of six bytes each
|
||||
if (JSON_HEDLEY_UNLIKELY(write_buffer.size() - write_buffer_pos < 12))
|
||||
{
|
||||
flush();
|
||||
}
|
||||
char* d = write_buffer.data() + write_buffer_pos;
|
||||
if (codepoint > 0xFFFFu)
|
||||
{
|
||||
d = put_u_escape(d, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)), nibble_to_hex);
|
||||
codepoint = 0xDC00u + (codepoint & 0x3FFu);
|
||||
}
|
||||
d = put_u_escape(d, static_cast<std::uint16_t>(codepoint), nibble_to_hex);
|
||||
write_buffer_pos = static_cast<std::size_t>(d - write_buffer.data());
|
||||
i += length;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static char* put_u_escape(char* d, const std::uint16_t codeunit, const char* nibble_to_hex) noexcept
|
||||
{
|
||||
const auto unit = static_cast<std::uint32_t>(codeunit);
|
||||
d[0] = '\\';
|
||||
d[1] = 'u';
|
||||
d[2] = nibble_to_hex[(unit >> 12u) & 0x0Fu];
|
||||
d[3] = nibble_to_hex[(unit >> 8u) & 0x0Fu];
|
||||
d[4] = nibble_to_hex[(unit >> 4u) & 0x0Fu];
|
||||
d[5] = nibble_to_hex[unit & 0x0Fu];
|
||||
return d + 6;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief write a lowercase "\\uXXXX" escape sequence into @a string_buffer
|
||||
*
|
||||
@@ -31913,11 +32338,22 @@ class serializer
|
||||
/// the output of the serializer (non-owning; the adapter lives at the call site)
|
||||
output_adapter_protocol<char>* o = nullptr;
|
||||
|
||||
/// a character buffer left uninitialized: the buffers below are written
|
||||
/// before they are read (only the part written is ever read), and zeroing
|
||||
/// 1.6 KiB took more than half of what dumping a small value costs (the
|
||||
/// user-provided constructor keeps the member initialization, which
|
||||
/// -Weffc++ asks for, from zeroing them)
|
||||
template<std::size_t N>
|
||||
struct uninitialized_buffer : std::array<char, N>
|
||||
{
|
||||
uninitialized_buffer() noexcept {} // NOLINT(modernize-use-equals-default,hicpp-use-equals-default,cppcoreguidelines-pro-type-member-init,hicpp-member-init): see above
|
||||
};
|
||||
|
||||
/// a (hopefully) large enough character buffer
|
||||
std::array<char, 64> number_buffer{{}};
|
||||
uninitialized_buffer<64> number_buffer;
|
||||
|
||||
/// string buffer
|
||||
std::array<char, 512> string_buffer{{}};
|
||||
uninitialized_buffer<512> string_buffer;
|
||||
|
||||
/// the indentation character
|
||||
const char indent_char;
|
||||
@@ -31937,7 +32373,7 @@ class serializer
|
||||
/// buffer collecting output before it is flushed to the output adapter, so
|
||||
/// that the many small structural writes become few bulk writes
|
||||
static constexpr std::size_t write_buffer_size = 1024;
|
||||
std::array<char, write_buffer_size> write_buffer{{}};
|
||||
uninitialized_buffer<write_buffer_size> write_buffer;
|
||||
/// number of valid bytes currently held in @ref write_buffer
|
||||
std::size_t write_buffer_pos = 0;
|
||||
};
|
||||
@@ -40701,6 +41137,7 @@ struct formatter<nlohmann::NLOHMANN_BASIC_JSON_TPL, char> // NOLINT(cert-dcl58-c
|
||||
#undef JSON_SCAN_SSSE3_TARGET
|
||||
#undef JSON_SCAN_VECTOR
|
||||
#undef JSON_SCAN_VECTOR_UTF8
|
||||
#undef JSON_DUMP_SHORT_STRINGS
|
||||
|
||||
#ifndef JSON_TEST_KEEP_MACROS
|
||||
#undef JSON_CATCH
|
||||
|
||||
Generated
+81
-423
@@ -756,34 +756,6 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* vector_plain_run(const unsigned
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief copy 16 bytes from p to w, and find the first quote, backslash,
|
||||
control character, or non-ASCII byte among them
|
||||
|
||||
The bytes are copied whether or not they are all plain (the caller has room
|
||||
for 16 bytes and keeps only the plain ones).
|
||||
|
||||
@return the offset of that byte, or 16 if there is none
|
||||
*/
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE unsigned copy_plain_run16(const unsigned char* p, char* w) noexcept
|
||||
{
|
||||
#if NLOHMANN_VIEW_NEON
|
||||
const uint8x16_t in = vld1q_u8(p);
|
||||
vst1q_u8(reinterpret_cast<std::uint8_t*>(w), in); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
|
||||
vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20)));
|
||||
const std::uint64_t bits = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
|
||||
return bits != 0 ? static_cast<unsigned>(count_trailing_zeros(bits) >> 2u) : 16u;
|
||||
#else
|
||||
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
||||
_mm_storeu_si128(static_cast<__m128i*>(static_cast<void*>(w)), in); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
||||
_mm_cmplt_epi8(in, _mm_set1_epi8(0x20)));
|
||||
const auto bits = static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
|
||||
return bits != 0 ? static_cast<unsigned>(count_trailing_zeros(bits)) : 16u;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#if NLOHMANN_VIEW_SSSE3_DISPATCH
|
||||
@@ -1465,41 +1437,11 @@ class builder
|
||||
0, 0, '\b', 0, 0, 0, '\f', 0, 0, 0, 0, 0, 0, 0, '\n', 0, 0, 0, '\r', 0, '\t', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
}
|
||||
};
|
||||
// The write position is kept in locals: the stores of characters into
|
||||
// the arena may alias the members of the builder, which would then be
|
||||
// reloaded after every one. It is stored back where the arena grows
|
||||
// and at the end.
|
||||
const unsigned char* const end = m_end;
|
||||
const std::size_t start = arena_used();
|
||||
char* w = m_arena_write;
|
||||
const char* w_end = m_arena_end;
|
||||
#define NLOHMANN_VIEW_ENSURE(n) \
|
||||
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(w_end - w) < (n))) \
|
||||
{ \
|
||||
m_arena_write = w; \
|
||||
arena_grow(n); \
|
||||
w = m_arena_write; \
|
||||
w_end = m_arena_end; \
|
||||
}
|
||||
{
|
||||
const auto n = static_cast<std::size_t>(p - s);
|
||||
if (n <= 16 && end - s >= 16)
|
||||
{
|
||||
NLOHMANN_VIEW_ENSURE(16)
|
||||
std::memcpy(w, s, 16);
|
||||
}
|
||||
else if (n != 0)
|
||||
{
|
||||
// (an empty run leaves an empty arena unallocated, and memcpy()
|
||||
// must not be passed its null pointer even for zero bytes)
|
||||
NLOHMANN_VIEW_ENSURE(n)
|
||||
std::memcpy(w, s, n);
|
||||
}
|
||||
w += n;
|
||||
}
|
||||
arena_run(s, static_cast<std::size_t>(p - s));
|
||||
for (;;)
|
||||
{
|
||||
if (p == end)
|
||||
if (p == m_end)
|
||||
{
|
||||
return failed(error_code::string_missing_quote, p);
|
||||
}
|
||||
@@ -1507,7 +1449,6 @@ class builder
|
||||
if (c == '"')
|
||||
{
|
||||
++p;
|
||||
m_arena_write = w;
|
||||
return decoded{p, start, arena_used() - start};
|
||||
}
|
||||
if (c != '\\')
|
||||
@@ -1518,7 +1459,7 @@ class builder
|
||||
return failed(c < 0x20 ? error_code::string_control_character : error_code::string_utf8, p);
|
||||
}
|
||||
++p;
|
||||
if (p == end)
|
||||
if (p == m_end)
|
||||
{
|
||||
return failed(error_code::string_missing_quote, p);
|
||||
}
|
||||
@@ -1536,7 +1477,7 @@ class builder
|
||||
{
|
||||
return failed(error_code::string_surrogate_low, p);
|
||||
}
|
||||
if (end - p < 2 || p[0] != '\\' || p[1] != 'u')
|
||||
if (m_end - p < 2 || p[0] != '\\' || p[1] != 'u')
|
||||
{
|
||||
return failed(error_code::string_surrogate_high, p);
|
||||
}
|
||||
@@ -1552,108 +1493,27 @@ class builder
|
||||
}
|
||||
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
|
||||
}
|
||||
NLOHMANN_VIEW_ENSURE(4)
|
||||
w = put_utf8(w, cp);
|
||||
arena_ensure(4);
|
||||
m_arena_write = put_utf8(m_arena_write, cp);
|
||||
}
|
||||
else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0))
|
||||
{
|
||||
NLOHMANN_VIEW_ENSURE(1)
|
||||
*w++ = simple_escape[d];
|
||||
arena_ensure(1);
|
||||
*m_arena_write++ = simple_escape[d];
|
||||
}
|
||||
else
|
||||
{
|
||||
--p;
|
||||
return failed(error_code::string_escape, p);
|
||||
}
|
||||
#if NLOHMANN_VIEW_NEON
|
||||
if (p != end && *p == '\\')
|
||||
if (p != m_end && *p == '\\')
|
||||
{
|
||||
continue; // consecutive escapes ("\u00e4\u00f6"): no run in between
|
||||
}
|
||||
// The first 16 bytes of the run are checked one by one, in predicted branches, and copied
|
||||
// as one block: where the runs between escapes are short, the vector step below would put
|
||||
// its longer dependency chain (load, compare, mask, count, add) on p for each of them,
|
||||
// and be slower than these steps (the scanner of the keys works the same way)
|
||||
{
|
||||
if (end - p >= 16)
|
||||
{
|
||||
const std::uint8_t* const plain = string_plain();
|
||||
std::size_t steps = 16;
|
||||
#define NLOHMANN_VIEW_STEP(i) if (NLOHMANN_VIEW_LIKELY(plain[p[i]] != 0)) {} else { steps = (i); goto run_found; }
|
||||
NLOHMANN_VIEW_REPEAT16(NLOHMANN_VIEW_STEP)
|
||||
#undef NLOHMANN_VIEW_STEP
|
||||
run_found:
|
||||
NLOHMANN_VIEW_ENSURE(16)
|
||||
std::memcpy(w, p, 16); // (only the first `steps` bytes are kept)
|
||||
w += steps;
|
||||
p += steps;
|
||||
if (steps < 16)
|
||||
{
|
||||
if (*p >= 0x80)
|
||||
{
|
||||
// non-ASCII: the UTF-8 check, and the rest of the run
|
||||
const unsigned char* const r = p;
|
||||
p = scan_string_run(p, end);
|
||||
const auto n = static_cast<std::size_t>(p - r);
|
||||
NLOHMANN_VIEW_ENSURE(n)
|
||||
std::memcpy(w, r, n);
|
||||
w += n;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (p != end && (*p == '\\' || *p == '"'))
|
||||
{
|
||||
continue; // consecutive escapes ("\u00e4\u00f6"), or the end of the string: no run in between
|
||||
}
|
||||
#endif
|
||||
#if NLOHMANN_VIEW_VECTOR
|
||||
// the plain bytes up to the next quote, backslash, or control
|
||||
// character are copied as they are scanned, 16 bytes at a time
|
||||
for (;;)
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(end - p < 16))
|
||||
{
|
||||
const unsigned char* const r = p;
|
||||
p = scan_string_run(p, end);
|
||||
const auto n = static_cast<std::size_t>(p - r);
|
||||
NLOHMANN_VIEW_ENSURE(n)
|
||||
std::memcpy(w, r, n);
|
||||
w += n;
|
||||
break;
|
||||
}
|
||||
NLOHMANN_VIEW_ENSURE(16)
|
||||
const unsigned k = copy_plain_run16(p, w);
|
||||
w += k;
|
||||
p += k;
|
||||
if (k == 16)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (NLOHMANN_VIEW_UNLIKELY(*p >= 0x80))
|
||||
{
|
||||
// non-ASCII: the UTF-8 check, and the rest of the run
|
||||
const unsigned char* const r = p;
|
||||
p = scan_string_run(p, end);
|
||||
const auto n = static_cast<std::size_t>(p - r);
|
||||
NLOHMANN_VIEW_ENSURE(n)
|
||||
std::memcpy(w, r, n);
|
||||
w += n;
|
||||
}
|
||||
break;
|
||||
}
|
||||
#else
|
||||
const unsigned char* const r = p;
|
||||
p = scan_string_run(p, end);
|
||||
const auto n = static_cast<std::size_t>(p - r);
|
||||
NLOHMANN_VIEW_ENSURE(n)
|
||||
std::memcpy(w, r, n);
|
||||
w += n;
|
||||
#endif
|
||||
p = scan_string_run(p, m_end);
|
||||
arena_run(r, static_cast<std::size_t>(p - r));
|
||||
}
|
||||
#undef NLOHMANN_VIEW_ENSURE
|
||||
}
|
||||
|
||||
/// does the float token [s, p) overflow FloatType? (parse() rejects it)
|
||||
@@ -1699,6 +1559,14 @@ run_found:
|
||||
return m_arena_write != nullptr ? static_cast<std::size_t>(m_arena_write - m_doc.arena.data()) : 0;
|
||||
}
|
||||
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE void arena_ensure(std::size_t n)
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(m_arena_end - m_arena_write) < n))
|
||||
{
|
||||
arena_grow(n);
|
||||
}
|
||||
}
|
||||
|
||||
NLOHMANN_VIEW_NOINLINE void arena_grow(std::size_t n)
|
||||
{
|
||||
const std::size_t used = arena_used();
|
||||
@@ -1707,6 +1575,25 @@ run_found:
|
||||
m_arena_end = m_doc.arena.data() + m_doc.arena.size();
|
||||
}
|
||||
|
||||
/// append the run [r, r + n) to the arena; short runs as one fixed-size
|
||||
/// 16-byte move when both sides have the room (no library call)
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE void arena_run(const unsigned char* r, std::size_t n)
|
||||
{
|
||||
if (n <= 16 && m_end - r >= 16)
|
||||
{
|
||||
arena_ensure(16);
|
||||
std::memcpy(m_arena_write, r, 16);
|
||||
}
|
||||
else if (n != 0)
|
||||
{
|
||||
// (an empty run leaves an empty arena unallocated, and memcpy()
|
||||
// must not be passed its null pointer even for zero bytes)
|
||||
arena_ensure(n);
|
||||
std::memcpy(m_arena_write, r, n);
|
||||
}
|
||||
m_arena_write += n;
|
||||
}
|
||||
|
||||
/// UTF-8 encoding of cp at w (room for 4 bytes); returns the position after
|
||||
/// it. (A returned pointer stays in a register; writing through a
|
||||
/// reference to m_arena_write would store and reload it around every byte.)
|
||||
@@ -2229,11 +2116,11 @@ indent_done:
|
||||
++p;
|
||||
}
|
||||
const unsigned char* const int_start = p;
|
||||
if (cur() == '0')
|
||||
if (p != e && *p == '0')
|
||||
{
|
||||
++p;
|
||||
}
|
||||
else if (NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9'))
|
||||
else if (NLOHMANN_VIEW_LIKELY(p != e && *p >= '1' && *p <= '9'))
|
||||
{
|
||||
p = skip_digits(p + 1, e);
|
||||
}
|
||||
@@ -2244,7 +2131,7 @@ indent_done:
|
||||
const auto int_digits = static_cast<std::size_t>(p - int_start);
|
||||
std::size_t frac_digits = 0;
|
||||
bool is_float = false;
|
||||
if (cur() == '.')
|
||||
if (p != e && *p == '.')
|
||||
{
|
||||
++p;
|
||||
const unsigned char* const f0 = p;
|
||||
@@ -2257,20 +2144,20 @@ indent_done:
|
||||
is_float = true;
|
||||
}
|
||||
std::int64_t exponent = 0;
|
||||
if ((cur() | 0x20) == 'e')
|
||||
if (p != e && (*p | 0x20) == 'e')
|
||||
{
|
||||
++p;
|
||||
bool exp_negative = false;
|
||||
if (cur() == '+' || cur() == '-')
|
||||
if (p != e && (*p == '+' || *p == '-'))
|
||||
{
|
||||
exp_negative = *p == '-';
|
||||
++p;
|
||||
}
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_digit(cur())))
|
||||
if (NLOHMANN_VIEW_UNLIKELY(p == e || !is_digit(*p)))
|
||||
{
|
||||
return fail(error_code::number_after_exponent);
|
||||
}
|
||||
while (is_digit(cur()))
|
||||
while (p != e && is_digit(*p))
|
||||
{
|
||||
if (exponent < 100000)
|
||||
{
|
||||
@@ -2332,7 +2219,7 @@ indent_done:
|
||||
++p; // opening quote
|
||||
const unsigned char* const s = p;
|
||||
p = scan_string_run<Value>(p, e);
|
||||
if (NLOHMANN_VIEW_LIKELY(cur() == '"'))
|
||||
if (NLOHMANN_VIEW_LIKELY(p != e && *p == '"'))
|
||||
{
|
||||
push_node(value_t::string, 0, 0, static_cast<std::size_t>(s - b), static_cast<std::uint64_t>(p - s));
|
||||
++p;
|
||||
@@ -3281,96 +3168,27 @@ constexpr std::size_t index_max_displacement = 64;
|
||||
static_assert(document_data::index_min_members >= 2 * index_max_displacement,
|
||||
"a table must be much larger than the probe window of a lookup");
|
||||
|
||||
/// hash of a key: its bytes, eight at a time (in a fixed byte order), where the
|
||||
/// last word may overlap the one before (so that no loop over single bytes is
|
||||
/// needed); a key of fewer than eight bytes is read as two overlapping words of
|
||||
/// four or two bytes (in the native byte order: the hash is not stored, and the
|
||||
/// same on both sides of a lookup)
|
||||
/// hash of a key: its bytes, eight at a time, in a fixed byte order
|
||||
inline std::uint64_t key_hash(const char* s, std::size_t n) noexcept
|
||||
{
|
||||
std::uint64_t h = 0x9E3779B97F4A7C15u * (n + 1);
|
||||
const auto* p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
while (n >= 8)
|
||||
{
|
||||
h = (h ^ read_eight_bytes(p)) * 0xBF58476D1CE4E5B9u;
|
||||
h ^= h >> 29u;
|
||||
p += 8;
|
||||
n -= 8;
|
||||
}
|
||||
std::uint64_t w = 0;
|
||||
if (n >= 8)
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
const unsigned char* const last = p + n - 8;
|
||||
while (p < last)
|
||||
{
|
||||
h = (h ^ read_eight_bytes(p)) * 0xBF58476D1CE4E5B9u;
|
||||
h ^= h >> 29u;
|
||||
p += 8;
|
||||
}
|
||||
w = read_eight_bytes(last);
|
||||
w |= static_cast<std::uint64_t>(p[i]) << (8u * i);
|
||||
}
|
||||
else if (n >= 4)
|
||||
{
|
||||
std::uint32_t head = 0;
|
||||
std::uint32_t tail = 0;
|
||||
std::memcpy(&head, p, 4);
|
||||
std::memcpy(&tail, p + n - 4, 4);
|
||||
w = static_cast<std::uint64_t>(head) | (static_cast<std::uint64_t>(tail) << 32u);
|
||||
}
|
||||
else if (n >= 2)
|
||||
{
|
||||
std::uint16_t head = 0;
|
||||
std::uint16_t tail = 0;
|
||||
std::memcpy(&head, p, 2);
|
||||
std::memcpy(&tail, p + n - 2, 2);
|
||||
w = static_cast<std::uint64_t>(head) | (static_cast<std::uint64_t>(tail) << 16u);
|
||||
}
|
||||
else if (n == 1)
|
||||
{
|
||||
w = p[0];
|
||||
}
|
||||
// (a product only carries bits upwards, and the slot is taken from the low
|
||||
// bits: the high bits of the last word are folded down before the multiplication)
|
||||
h = (h ^ w ^ (w >> 29u)) * 0x94D049BB133111EBu;
|
||||
h = (h ^ w) * 0x94D049BB133111EBu;
|
||||
return h ^ (h >> 31u);
|
||||
}
|
||||
|
||||
/// whether the n <= 16 bytes at a and b are equal, as two overlapping words
|
||||
/// (the first and the last 8, 4, or 2 bytes) and without a call
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE bool same_short_bytes(const unsigned char* a, const unsigned char* b, std::size_t n) noexcept
|
||||
{
|
||||
if (n >= 8)
|
||||
{
|
||||
std::uint64_t x0 = 0;
|
||||
std::uint64_t x1 = 0;
|
||||
std::uint64_t y0 = 0;
|
||||
std::uint64_t y1 = 0;
|
||||
std::memcpy(&x0, a, 8);
|
||||
std::memcpy(&x1, a + n - 8, 8);
|
||||
std::memcpy(&y0, b, 8);
|
||||
std::memcpy(&y1, b + n - 8, 8);
|
||||
return x0 == y0 && x1 == y1;
|
||||
}
|
||||
if (n >= 4)
|
||||
{
|
||||
std::uint32_t x0 = 0;
|
||||
std::uint32_t x1 = 0;
|
||||
std::uint32_t y0 = 0;
|
||||
std::uint32_t y1 = 0;
|
||||
std::memcpy(&x0, a, 4);
|
||||
std::memcpy(&x1, a + n - 4, 4);
|
||||
std::memcpy(&y0, b, 4);
|
||||
std::memcpy(&y1, b + n - 4, 4);
|
||||
return x0 == y0 && x1 == y1;
|
||||
}
|
||||
if (n >= 2)
|
||||
{
|
||||
std::uint16_t x0 = 0;
|
||||
std::uint16_t x1 = 0;
|
||||
std::uint16_t y0 = 0;
|
||||
std::uint16_t y1 = 0;
|
||||
std::memcpy(&x0, a, 2);
|
||||
std::memcpy(&x1, a + n - 2, 2);
|
||||
std::memcpy(&y0, b, 2);
|
||||
std::memcpy(&y1, b + n - 2, 2);
|
||||
return x0 == y0 && x1 == y1;
|
||||
}
|
||||
return n == 0 || *a == *b;
|
||||
}
|
||||
|
||||
/// build the table of a large object
|
||||
inline void build_object_index(document_data& d, node* obj)
|
||||
{
|
||||
@@ -3435,27 +3253,6 @@ inline const node* find_indexed(const document_data& d, const node* obj, const c
|
||||
const document_data::object_index& ix = d.indexes[obj->extra - 1u];
|
||||
const std::uint32_t* const table = d.index_slots.data() + ix.start;
|
||||
std::size_t slot = conditional_static_cast<std::size_t>(key_hash(key, n)) & ix.mask;
|
||||
const auto* const k_bytes = reinterpret_cast<const unsigned char*>(key); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
// (the loop for longer keys comes first, as the code is laid out; it is not the more common case)
|
||||
if (NLOHMANN_VIEW_UNLIKELY(n <= 16))
|
||||
{
|
||||
// (up to 16 bytes, the bytes are compared as two words, without a call)
|
||||
for (std::size_t distance = 0; distance <= index_max_displacement; ++distance)
|
||||
{
|
||||
const std::uint32_t s = table[slot];
|
||||
if (s == 0)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
const node* const k = obj + s;
|
||||
if (k->len == n && same_short_bytes(reinterpret_cast<const unsigned char*>(d.str(*k)), k_bytes, n)) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
{
|
||||
return k;
|
||||
}
|
||||
slot = (slot + 1) & ix.mask;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
for (std::size_t distance = 0; distance <= index_max_displacement; ++distance)
|
||||
{
|
||||
const std::uint32_t s = table[slot];
|
||||
@@ -3624,10 +3421,6 @@ const node* element_at(const document_data& d, const node* array, std::size_t id
|
||||
{
|
||||
return e + idx; // a growable block: one link per element
|
||||
}
|
||||
if (array->next == array->len + 1u && (!nav::editable() || (array->flags & node_flags::moved) == 0))
|
||||
{
|
||||
return e + idx; // no element has a subtree: the elements are the nodes after the array
|
||||
}
|
||||
for (std::size_t i = 0; i < idx; ++i)
|
||||
{
|
||||
e = document_data::after(e);
|
||||
@@ -3643,10 +3436,6 @@ const node* last_child(const document_data& d, const node* container) noexcept
|
||||
const std::size_t value_offset = container->kind == static_cast<std::uint8_t>(value_t::object) ? 1 : 0;
|
||||
const node* const end = navigation<Editable>::end(d, container);
|
||||
const node* last = navigation<Editable>::first(d, container);
|
||||
if (value_offset == 0 && container->next == container->len + 1u && (!navigation<Editable>::editable() || (container->flags & node_flags::moved) == 0))
|
||||
{
|
||||
return last + (container->len - 1u); // an array without subtrees: the elements are the nodes after it
|
||||
}
|
||||
for (const node* c = document_data::after(last + value_offset); c != end; c = document_data::after(c + value_offset))
|
||||
{
|
||||
last = c;
|
||||
@@ -5512,9 +5301,6 @@ NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
#include <algorithm> // max
|
||||
#include <cstddef> // size_t
|
||||
#ifndef JSON_NO_IO
|
||||
#include <cstdio> // FILE, fread
|
||||
#endif
|
||||
#include <istream> // istream, streambuf
|
||||
#include <string> // basic_string, char_traits, string
|
||||
#include <type_traits> // decay, integral_constant, is_array, is_const, is_integral, is_lvalue_reference, is_pointer, is_same, remove_cv, remove_extent, remove_pointer, remove_reference
|
||||
@@ -5660,37 +5446,6 @@ inline void append_stream(std::string& buf, std::istream& is)
|
||||
is.clear(is.rdstate() | std::ios::eofbit);
|
||||
}
|
||||
|
||||
/// a pointer to a C stream (never with JSON_NO_IO, which leaves out <cstdio>)
|
||||
template<typename T>
|
||||
struct is_c_stream
|
||||
#ifndef JSON_NO_IO
|
||||
: std::is_same<T, std::FILE*>
|
||||
#else
|
||||
: std::false_type
|
||||
#endif
|
||||
{};
|
||||
|
||||
#ifndef JSON_NO_IO
|
||||
/// append the rest of a C stream: in blocks with fread, not character by
|
||||
/// character (the library's file adapter reads with fgetc);
|
||||
/// f is not null
|
||||
inline void append_file(std::string& buf, std::FILE* f)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
const std::size_t used = buf.size();
|
||||
const std::size_t room = (std::max)(buf.capacity() - used, std::size_t{4096});
|
||||
buf.resize(used + room);
|
||||
const std::size_t got = std::fread(&buf[used], 1, room, f);
|
||||
buf.resize(used + got);
|
||||
if (got < room)
|
||||
{
|
||||
break; // end of file, or an error (as fgetc, which returns EOF for both)
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace view
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -5849,21 +5604,20 @@ class view_item
|
||||
|
||||
view_item(const iterator& it, std::size_t index)
|
||||
: m_it(it)
|
||||
, m_position(index)
|
||||
{}
|
||||
{
|
||||
if (!it.is_object_iterator())
|
||||
{
|
||||
m_index = std::to_string(index);
|
||||
}
|
||||
}
|
||||
|
||||
/// the member key, or the element index for arrays (as text made on the
|
||||
/// first call, so that iterating over the values pays nothing for it)
|
||||
/// the member key, or the element index for arrays
|
||||
string_view_t key() const
|
||||
{
|
||||
if (m_it.is_object_iterator())
|
||||
{
|
||||
return m_it.key();
|
||||
}
|
||||
if (m_index.empty())
|
||||
{
|
||||
m_index = std::to_string(m_position);
|
||||
}
|
||||
return string_view_t(m_index.data(), m_index.size());
|
||||
}
|
||||
|
||||
@@ -5886,8 +5640,7 @@ class view_item
|
||||
|
||||
private:
|
||||
iterator m_it;
|
||||
std::size_t m_position; ///< the index of an array element
|
||||
mutable std::string m_index{}; ///< the text of m_position, once key() was called // NOLINT(readability-redundant-member-init)
|
||||
std::string m_index{}; // NOLINT(readability-redundant-member-init)
|
||||
};
|
||||
|
||||
/// the range returned by basic_json_view::items()
|
||||
@@ -5981,7 +5734,7 @@ NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
|
||||
|
||||
#include <array> // array
|
||||
#include <algorithm> // max
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // int64_t, uint8_t
|
||||
#include <string> // string
|
||||
@@ -6021,9 +5774,8 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
using nav = navigation<Editable>;
|
||||
|
||||
// the open containers: their next element (or key of the next member),
|
||||
// their end, and whether they are objects; for the first 16 levels on the
|
||||
// stack, else sized once from the document's deepest nesting (for a
|
||||
// subtree an upper bound)
|
||||
// their end, and whether they are objects; sized once from the
|
||||
// document's deepest nesting (for a subtree an upper bound)
|
||||
struct frame
|
||||
{
|
||||
const node* pos;
|
||||
@@ -6031,43 +5783,10 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
bool object;
|
||||
};
|
||||
|
||||
// a scalar needs neither the handler nor the stack of open containers
|
||||
switch (static_cast<value_t>(n->kind))
|
||||
{
|
||||
case value_t::string:
|
||||
return BasicJsonType(string_t(d.str(*n), n->len));
|
||||
case value_t::number_integer:
|
||||
return BasicJsonType(static_cast<typename BasicJsonType::number_integer_t>(static_cast<std::int64_t>(integer_bits(*n))));
|
||||
case value_t::number_unsigned:
|
||||
return BasicJsonType(static_cast<typename BasicJsonType::number_unsigned_t>(integer_bits(*n)));
|
||||
case value_t::number_float:
|
||||
return BasicJsonType(float_value<typename BasicJsonType::number_float_t>(d, *n));
|
||||
case value_t::boolean:
|
||||
return BasicJsonType((n->flags & node_flags::is_true) != 0);
|
||||
case value_t::null:
|
||||
return BasicJsonType(nullptr);
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
BasicJsonType result;
|
||||
sax_t sax(result, true);
|
||||
const string_t no_token{};
|
||||
// (shallow subtrees on the stack: a call for a small value then allocates only for the value)
|
||||
std::array<frame, 16> shallow; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): filled as containers open
|
||||
std::vector<frame> deep{};
|
||||
frame* open = shallow.data();
|
||||
std::size_t capacity = shallow.size();
|
||||
if (d.max_depth > capacity)
|
||||
{
|
||||
deep.resize(d.max_depth);
|
||||
open = deep.data();
|
||||
capacity = deep.size();
|
||||
}
|
||||
std::vector<frame> open((std::max)(d.max_depth, std::size_t{1}));
|
||||
std::size_t depth = 0;
|
||||
for (;;)
|
||||
{
|
||||
@@ -6087,13 +5806,9 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
{
|
||||
sax.start_array(n->len);
|
||||
}
|
||||
if (NLOHMANN_VIEW_UNLIKELY(depth == capacity))
|
||||
if (NLOHMANN_VIEW_UNLIKELY(depth == open.size()))
|
||||
{
|
||||
// (deeper than the recorded nesting, e.g. after edits)
|
||||
deep.assign(open, open + depth);
|
||||
deep.resize(depth * 2);
|
||||
open = deep.data();
|
||||
capacity = deep.size();
|
||||
open.resize(depth * 2); // (deeper than the recorded nesting, e.g. after edits)
|
||||
}
|
||||
open[depth++] = frame{nav::first(d, n), nav::end(d, n), object};
|
||||
break;
|
||||
@@ -7875,7 +7590,7 @@ class basic_json_view
|
||||
}
|
||||
detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name());
|
||||
}
|
||||
return idx < m_node->len ? element(idx) : basic_json_view();
|
||||
return idx < m_node->len ? basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx))) : basic_json_view();
|
||||
}
|
||||
|
||||
/// any other integer type (int, unsigned, long, std::int64_t, ...; a
|
||||
@@ -7938,7 +7653,7 @@ class basic_json_view
|
||||
{
|
||||
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
|
||||
}
|
||||
return element(idx);
|
||||
return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
|
||||
}
|
||||
|
||||
/// any other integer type, see operator[]; negative values are out of
|
||||
@@ -7972,13 +7687,7 @@ class basic_json_view
|
||||
|
||||
string_t value(string_view_t key, const char* default_value) const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
||||
{
|
||||
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
|
||||
}
|
||||
// (the default is made only if there is no such member)
|
||||
const basic_json_view r = lookup(key);
|
||||
return r.is_discarded() ? string_t(default_value) : r.template get<string_t>();
|
||||
return value(key, string_t(default_value));
|
||||
}
|
||||
|
||||
/// the value a JSON pointer refers to converted to T, or the default
|
||||
@@ -7997,12 +7706,7 @@ class basic_json_view
|
||||
|
||||
string_t value(const json_pointer& ptr, const char* default_value) const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
||||
{
|
||||
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
|
||||
}
|
||||
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
|
||||
return r.is_discarded() ? string_t(default_value) : r.template get<string_t>();
|
||||
return value(ptr, string_t(default_value));
|
||||
}
|
||||
|
||||
/// the first element or member value; a primitive value itself. Throws
|
||||
@@ -8024,8 +7728,7 @@ class basic_json_view
|
||||
{
|
||||
if (is_structured() && m_node->len != 0)
|
||||
{
|
||||
return unedited() ? basic_json_view(m_doc, detail::view::last_child<false>(*m_doc, m_node) + (is_object() ? 1 : 0))
|
||||
: basic_json_view(m_doc, navigation::value(detail::view::last_child<Editable>(*m_doc, m_node) + (is_object() ? 1 : 0)));
|
||||
return basic_json_view(m_doc, navigation::value(detail::view::last_child<Editable>(*m_doc, m_node) + (is_object() ? 1 : 0)));
|
||||
}
|
||||
return front();
|
||||
}
|
||||
@@ -8042,8 +7745,7 @@ class basic_json_view
|
||||
{
|
||||
return end();
|
||||
}
|
||||
const node* const k = unedited() ? detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size())
|
||||
: detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
|
||||
const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
|
||||
return k != nullptr ? iterator(m_doc, k, true) : end();
|
||||
}
|
||||
|
||||
@@ -8060,8 +7762,7 @@ class basic_json_view
|
||||
/// whether this is an object with a member with this key
|
||||
bool contains(string_view_t key) const
|
||||
{
|
||||
return is_object() && (unedited() ? detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size()) != nullptr
|
||||
: detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size()) != nullptr);
|
||||
return is_object() && detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size()) != nullptr;
|
||||
}
|
||||
|
||||
bool contains(const char* key) const
|
||||
@@ -8221,14 +7922,7 @@ class basic_json_view
|
||||
// the compact writer keeps 64 bytes of slack, so that it does not grow
|
||||
// the buffer just before the end
|
||||
const std::size_t estimate = source_extent() + (style.pretty ? source_extent() / 2 : 0) + 160;
|
||||
if (unedited())
|
||||
{
|
||||
detail::view::view_serializer<BasicJsonType, false>(*m_doc, out, estimate, style).dump(m_node);
|
||||
}
|
||||
else
|
||||
{
|
||||
detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node);
|
||||
}
|
||||
detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node);
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -8308,7 +8002,7 @@ class basic_json_view
|
||||
{
|
||||
return BasicJsonType(value_t::discarded);
|
||||
}
|
||||
return unedited() ? detail::view::materialize<BasicJsonType, false>(*m_doc, m_node) : detail::view::materialize<BasicJsonType, Editable>(*m_doc, m_node);
|
||||
return detail::view::materialize<BasicJsonType, Editable>(*m_doc, m_node);
|
||||
}
|
||||
|
||||
/// byte offset of this value in the source text (for strings: of the
|
||||
@@ -8386,32 +8080,10 @@ class basic_json_view
|
||||
/// (object required)
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept
|
||||
{
|
||||
if (unedited())
|
||||
{
|
||||
const node* const k = detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size());
|
||||
return k != nullptr ? basic_json_view(m_doc, k + 1) : basic_json_view();
|
||||
}
|
||||
const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
|
||||
return k != nullptr ? basic_json_view(m_doc, navigation::value(k + 1)) : basic_json_view();
|
||||
}
|
||||
|
||||
/// whether the document has not been edited yet: until the first edit, its index is walked like that of a
|
||||
/// read-only document (nothing is moved or linked), which is cheaper
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE bool unedited() const noexcept
|
||||
{
|
||||
return !navigation::editable() || m_doc->edits == nullptr;
|
||||
}
|
||||
|
||||
/// the element of an array at an index below its size
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view element(size_type idx) const
|
||||
{
|
||||
if (unedited())
|
||||
{
|
||||
return basic_json_view(m_doc, detail::view::element_at<false>(*m_doc, m_node, idx));
|
||||
}
|
||||
return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
|
||||
}
|
||||
|
||||
// --- get() dispatch ---
|
||||
|
||||
bool get_impl(detail::view::value_tag<bool> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
@@ -9151,8 +8823,7 @@ class basic_json_document
|
||||
// NOLINTBEGIN(readability-avoid-nested-conditional-operator): a constant expression of C++11
|
||||
append_input_impl(buf, std::forward<T>(input), std::integral_constant < int, detail::is_contiguous_byte_container<D>::value ? 0
|
||||
: std::is_base_of<std::istream, D>::value ? 1
|
||||
: detail::view::is_adapter_input<D>::value ? 2
|
||||
: detail::view::is_c_stream<D>::value ? 4 : 3 > {});
|
||||
: detail::view::is_adapter_input<D>::value ? 2 : 3 > {});
|
||||
// NOLINTEND(readability-avoid-nested-conditional-operator)
|
||||
}
|
||||
|
||||
@@ -9174,19 +8845,6 @@ class basic_json_document
|
||||
detail::view::append_adapter(buf, std::move(input.ia));
|
||||
}
|
||||
|
||||
#ifndef JSON_NO_IO
|
||||
static void append_input_impl(std::string& buf, std::FILE* file, std::integral_constant<int, 4> /*C stream*/)
|
||||
{
|
||||
if (file == nullptr)
|
||||
{
|
||||
// (as before: the library's adapter decides what a null stream yields)
|
||||
detail::view::append_adapter(buf, detail::input_adapter(file));
|
||||
return;
|
||||
}
|
||||
detail::view::append_file(buf, file);
|
||||
}
|
||||
#endif
|
||||
|
||||
template<typename T>
|
||||
static void append_input_impl(std::string& buf, T&& input, std::integral_constant<int, 3> /*other*/)
|
||||
{
|
||||
|
||||
@@ -185,29 +185,6 @@ TEST_CASE("json_view")
|
||||
// duplicate keys: the last value, at the position of the first key
|
||||
CHECK(materialized<json_document>(R"({"a":1,"b":2,"a":3})") == json::parse(R"({"a":1,"b":2,"a":3})"));
|
||||
CHECK(materialized<ordered_json_document>(R"({"a":1,"b":2,"a":3})").dump() == R"({"a":3,"b":2})");
|
||||
// scalars (made without the handler), and nesting around the depth up to which the stack of open containers is local
|
||||
for (const char* scalar :
|
||||
{"null", "true", "false", "0", "-7", "18446744073709551615", "1.5e300", "-0.0", "\"\"", "\"short\"", "\"a string that is longer than fifteen bytes\"", "\"\\u00e4\\n\""
|
||||
})
|
||||
{
|
||||
CAPTURE(scalar)
|
||||
CHECK(materialized<json_document>(scalar) == json::parse(scalar));
|
||||
const std::string in_array = std::string("[1,") + scalar + "]";
|
||||
const json_document d = json_document::parse(in_array);
|
||||
CHECK(d.root()[1].materialize() == json::parse(scalar));
|
||||
CHECK(d.root()[1].get<json>() == json::parse(scalar));
|
||||
}
|
||||
for (const std::size_t levels :
|
||||
{
|
||||
std::size_t{1}, std::size_t{15}, std::size_t{16}, std::size_t{17}, std::size_t{32}, std::size_t{33}
|
||||
})
|
||||
{
|
||||
CAPTURE(levels)
|
||||
const std::string nested = std::string(levels, '[') + "{\"k\":[1,{\"j\":2}]}" + std::string(levels, ']');
|
||||
CHECK(materialized<json_document>(nested) == json::parse(nested));
|
||||
const std::string wide = "{\"a\":" + nested + ",\"b\":" + nested + "}";
|
||||
CHECK(materialized<json_document>(wide) == json::parse(wide));
|
||||
}
|
||||
// very deep nesting (iterative, as parse())
|
||||
const std::string deep = std::string(100000, '[') + std::string(100000, ']');
|
||||
// (one check per step, so that a failure shows which step it is)
|
||||
@@ -433,31 +410,6 @@ TEST_CASE("json_view")
|
||||
std::istringstream rvalue_source("[1]");
|
||||
CHECK(json_document::parse_copy(std::move(rvalue_source)).owns_source());
|
||||
|
||||
// a C stream is read in blocks as well (more than one block, and a
|
||||
// text whose length is a multiple of the block size)
|
||||
for (const std::size_t extra :
|
||||
{
|
||||
std::size_t{0}, std::size_t{1}
|
||||
})
|
||||
{
|
||||
std::FILE* long_file = std::tmpfile(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
if (long_file != nullptr)
|
||||
{
|
||||
std::string padded = text;
|
||||
padded.append((4096 - (padded.size() % 4096)) % 4096 + extra, ' ');
|
||||
CHECK(std::fwrite(padded.data(), 1, padded.size(), long_file) == padded.size());
|
||||
CHECK(std::fseek(long_file, 0, SEEK_SET) == 0);
|
||||
const json_document from_long_file = json_document::parse(long_file);
|
||||
CHECK(from_long_file.source() == padded);
|
||||
CHECK(from_long_file.root().materialize() == json::parse(text));
|
||||
CHECK(std::fclose(long_file) == 0); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
}
|
||||
{
|
||||
json_document from_null;
|
||||
CHECK_THROWS_AS(from_null = json_document::parse(static_cast<std::FILE*>(nullptr)), json::parse_error&);
|
||||
}
|
||||
|
||||
// a stream without a buffer is an empty input
|
||||
std::istream no_buffer(nullptr);
|
||||
CHECK(json_document::parse(no_buffer, false).is_discarded());
|
||||
@@ -1054,49 +1006,6 @@ TEST_CASE("json_view element access and iteration")
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("arrays whose elements have no subtrees: index and back()")
|
||||
{
|
||||
// (the elements are adjacent in the index; an array, an object, or an
|
||||
// empty container anywhere makes the lookup skip element by element)
|
||||
const std::vector<std::string> texts =
|
||||
{
|
||||
"[]", "[1]", "[1,2,3]", "[1,\"two\",3.5,true,null,-7,18446744073709551615]", "[[]]", "[{}]", "[1,[2],3]",
|
||||
"[1,2,{\"a\":3}]", "[[1,2],3,4]", "[1,2,3,[4,5,6,7,8],9,10,11,12]", "[{\"a\":[1]},2]", "[\"a\\u00e4\",\"b\"]"
|
||||
};
|
||||
for (const std::string& text : texts)
|
||||
{
|
||||
CAPTURE(text)
|
||||
const json_document d = json_document::parse(text);
|
||||
const json_view v = d.root();
|
||||
const json expected = json::parse(text);
|
||||
for (std::size_t i = 0; i < expected.size(); ++i)
|
||||
{
|
||||
CHECK(v[i].materialize() == expected[i]);
|
||||
CHECK(v.at(i).materialize() == expected[i]);
|
||||
}
|
||||
CHECK(v[expected.size()].is_discarded());
|
||||
if (!expected.empty())
|
||||
{
|
||||
CHECK(v.back().materialize() == expected.back());
|
||||
}
|
||||
}
|
||||
// a long array (the direct lookup is not limited to short ones), also with a container at its end
|
||||
std::string text = "[0";
|
||||
for (int i = 1; i < 1000; ++i)
|
||||
{
|
||||
text += "," + std::to_string(i);
|
||||
}
|
||||
const json_document scalars = json_document::parse(text + "]");
|
||||
CHECK(scalars.root()[999].get<int>() == 999);
|
||||
CHECK(scalars.root()[500].get<int>() == 500);
|
||||
CHECK(scalars.root().at(1).get<int>() == 1);
|
||||
CHECK(scalars.root().back().get<int>() == 999);
|
||||
const json_document container_last = json_document::parse(text + ",[1000]]");
|
||||
CHECK(container_last.root()[999].get<int>() == 999);
|
||||
CHECK(container_last.root()[1000][0].get<int>() == 1000);
|
||||
CHECK(container_last.root().back()[0].get<int>() == 1000);
|
||||
}
|
||||
|
||||
SECTION("iterators")
|
||||
{
|
||||
const json_document d = json_document::parse(R"({"x":[1,{"y":2}],"z":null})");
|
||||
@@ -1136,28 +1045,6 @@ TEST_CASE("json_view element access and iteration")
|
||||
CHECK((*first++).key() == "0");
|
||||
CHECK((*first).key() == "1");
|
||||
CHECK(++first == items.end());
|
||||
|
||||
// the keys of array elements are their indexes, whether or not they are asked for
|
||||
std::string numbers = "[0";
|
||||
for (int i = 1; i < 1234; ++i)
|
||||
{
|
||||
numbers += "," + std::to_string(i);
|
||||
}
|
||||
const json_document array = json_document::parse(numbers + "]");
|
||||
std::size_t position = 0;
|
||||
for (const auto& item : array.root().items())
|
||||
{
|
||||
CHECK(item.value().get<std::size_t>() == position);
|
||||
if (position % 3 != 0) // (not for every item: values alone do not make the key)
|
||||
{
|
||||
CHECK(item.key() == std::to_string(position));
|
||||
CHECK(item.key().data() == item.key().data()); // (the same text every time)
|
||||
const json_view::item copy = item;
|
||||
CHECK(copy.key() == std::to_string(position));
|
||||
}
|
||||
++position;
|
||||
}
|
||||
CHECK(position == 1234);
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
std::string pairs;
|
||||
for (const auto [key, value] : d.root().items())
|
||||
@@ -1496,17 +1383,7 @@ TEST_CASE("json_view values")
|
||||
CHECK(exception_of([&] { static_cast<void>(v.value("s", 0)); }) == without_path(exception_of([&] { static_cast<void>(j.value("s", 0)); })));
|
||||
CHECK(exception_of([&] { static_cast<void>(v["n"].value("x", 0)); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value("x", 0)); })));
|
||||
CHECK(exception_of([&] { static_cast<void>(v["n"].value(json::json_pointer("/x"), 0)); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value(json::json_pointer("/x"), 0)); })));
|
||||
// string defaults: the member is not a string, or the value is neither an object nor an array
|
||||
CHECK(exception_of([&] { static_cast<void>(v.value("n", "default")); }) == without_path(exception_of([&] { static_cast<void>(j.value("n", "default")); })));
|
||||
CHECK(exception_of([&] { static_cast<void>(v["n"].value("x", "default")); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value("x", "default")); })));
|
||||
CHECK(exception_of([&] { static_cast<void>(v.value(json::json_pointer("/n"), "default")); }) == without_path(exception_of([&] { static_cast<void>(j.value(json::json_pointer("/n"), "default")); })));
|
||||
CHECK(exception_of([&] { static_cast<void>(v["s"].value(json::json_pointer("/x"), "default")); }) == without_path(exception_of([&] { static_cast<void>(j["s"].value(json::json_pointer("/x"), "default")); })));
|
||||
#endif
|
||||
// (defaults and members longer than the short string optimization)
|
||||
CHECK(v.value("s", "a default longer than fifteen characters") == "text");
|
||||
CHECK(v.value("missing", "a default longer than fifteen characters") == "a default longer than fifteen characters");
|
||||
CHECK(v.value(json::json_pointer("/s"), "a default longer than fifteen characters") == "text");
|
||||
CHECK(v.value(json::json_pointer("/zz"), "a default longer than fifteen characters") == "a default longer than fifteen characters");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -523,58 +523,6 @@ TEST_CASE("json_view builder: strings across vector blocks")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("json_view builder: escapes between runs of text")
|
||||
{
|
||||
// The text between escapes is copied 16 bytes at a time where there are
|
||||
// that many bytes left, and with the UTF-8 check where there is
|
||||
// non-ASCII text. Runs of every length from 0 to 40 between valid and
|
||||
// invalid escapes, UTF-8 sequences, and control characters, in strings
|
||||
// that are cut at all places near the end of the text.
|
||||
const std::array<const char*, 9> valid = {{"\\n", "\\\"", "\\\\", "\\/", "\\t", "\\u00e4", "\\u20ac", "\\ud83d\\ude00", "\\u0000"}};
|
||||
const std::array<const char*, 9> invalid = {{"\\x", "\\ud800", "\\udc00", "\\u12G4", "\\u12", "\\ud83dx", "\\ud83d\\u0041", "\\", "\x01"}};
|
||||
const std::array<const char*, 6> utf8 = {{"\xc3\xa4", "\xe2\x82\xac", "\xf0\x9f\x98\x80", "\xc3", "\xff", "\x80"}};
|
||||
std::mt19937 rng(2026); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed): reproducible
|
||||
for (std::size_t run = 0; run <= 40; ++run)
|
||||
{
|
||||
for (int i = 0; i < 60; ++i)
|
||||
{
|
||||
std::string content = std::string(rng() % 20, 'a');
|
||||
const auto pieces = 1 + (rng() % 6);
|
||||
for (unsigned k = 0; k < pieces; ++k)
|
||||
{
|
||||
content += valid[rng() % valid.size()];
|
||||
content += std::string(k == 0 ? run : rng() % 41, static_cast<char>('b' + (rng() % 20)));
|
||||
switch (rng() % 8)
|
||||
{
|
||||
case 0:
|
||||
content += utf8[rng() % 3];
|
||||
break;
|
||||
case 1:
|
||||
content += utf8[rng() % utf8.size()];
|
||||
break;
|
||||
case 2:
|
||||
content += invalid[rng() % invalid.size()];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
check_string(content);
|
||||
check_string(content.substr(0, content.size() - (rng() % 4)));
|
||||
}
|
||||
}
|
||||
// the text before the first escape, and runs and escapes at the end of the text
|
||||
for (std::size_t before = 0; before <= 40; ++before)
|
||||
{
|
||||
for (const char* escape : valid)
|
||||
{
|
||||
check_string(std::string(before, 'a') + escape);
|
||||
check_string(std::string(before, 'a') + escape + std::string(before % 19, 'z'));
|
||||
check_string(std::string(before, 'a') + escape + escape + std::string(before % 19, 'z'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("json_view node integer bits")
|
||||
{
|
||||
using nlohmann::detail::view::integer_bits;
|
||||
|
||||
@@ -21,7 +21,6 @@ using nlohmann::ordered_json_view;
|
||||
using json_view_test::generator;
|
||||
using json_view_test::has_duplicate_keys;
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
@@ -30,7 +29,6 @@ using json_view_test::has_duplicate_keys;
|
||||
#include <iomanip>
|
||||
#include <map>
|
||||
#include <random>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -600,142 +598,6 @@ TEST_CASE("json_view large objects")
|
||||
CHECK(without_collisions.memory_usage() >= with_collisions.memory_usage() + (table_size * sizeof(std::uint32_t)));
|
||||
}
|
||||
|
||||
SECTION("keys that differ in their last bytes are spread over the table")
|
||||
{
|
||||
// The hash must spread the usual families of keys (numbered names, hex
|
||||
// strings, dates, short names): a key farther than index_max_displacement
|
||||
// from its home slot would leave the object without a table. The keys
|
||||
// are placed with linear probing, as the table does.
|
||||
const auto max_displacement = [](const std::vector<std::string>& keys)
|
||||
{
|
||||
std::size_t slots = 16;
|
||||
while (slots < 2 * keys.size())
|
||||
{
|
||||
slots *= 2;
|
||||
}
|
||||
std::vector<bool> used(slots, false);
|
||||
std::size_t farthest = 0;
|
||||
for (const std::string& key : keys)
|
||||
{
|
||||
std::size_t i = static_cast<std::size_t>(nlohmann::detail::view::key_hash(key.data(), key.size())) & (slots - 1);
|
||||
std::size_t distance = 0;
|
||||
while (used[i])
|
||||
{
|
||||
i = (i + 1) & (slots - 1);
|
||||
++distance;
|
||||
}
|
||||
used[i] = true;
|
||||
farthest = (std::max)(farthest, distance);
|
||||
}
|
||||
return farthest;
|
||||
};
|
||||
// the keys of a printf-like pattern with one %d, %0Nd or %0Nx, formatted with a stream
|
||||
// (a format string that is not a literal is a Flawfinder error)
|
||||
const auto numbered = [](const char* format, int count, int step)
|
||||
{
|
||||
const std::string pattern(format);
|
||||
const std::size_t percent = pattern.find('%');
|
||||
std::size_t conversion = percent + 1;
|
||||
std::size_t width = 0;
|
||||
while (pattern[conversion] >= '0' && pattern[conversion] <= '9')
|
||||
{
|
||||
width = width * 10 + static_cast<std::size_t>(pattern[conversion] - '0');
|
||||
++conversion;
|
||||
}
|
||||
const bool hex = pattern[conversion] == 'x';
|
||||
std::vector<std::string> keys;
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
std::ostringstream key;
|
||||
key << pattern.substr(0, percent) << std::setfill('0') << std::setw(static_cast<int>(width))
|
||||
<< (hex ? std::hex : std::dec) << i * step << pattern.substr(conversion + 1);
|
||||
keys.push_back(key.str());
|
||||
}
|
||||
return keys;
|
||||
};
|
||||
for (const char* format :
|
||||
{"key%d", "%d", "item_%04d", "a%05d", "%03dxyz", "id%d", "user_%d", "%d_suffix", "0x%04x", "A%07d", "12345678%d", "1234567%d", "123456789%d",
|
||||
"configuration_parameter_value_%04d_extra", "2026-01-%02dT00:00:00Z"
|
||||
})
|
||||
{
|
||||
CAPTURE(format)
|
||||
CHECK(max_displacement(numbered(format, std::string(format).find("%02d") != std::string::npos ? 60 : 1000, 1)) < nlohmann::detail::view::index_max_displacement);
|
||||
CHECK(max_displacement(numbered(format, 600, 7)) < nlohmann::detail::view::index_max_displacement);
|
||||
}
|
||||
std::mt19937_64 rng(7);
|
||||
for (const std::size_t length :
|
||||
{
|
||||
std::size_t{1}, std::size_t{2}, std::size_t{3}, std::size_t{5}, std::size_t{7}, std::size_t{8}, std::size_t{9}, std::size_t{12}, std::size_t{16}, std::size_t{17}, std::size_t{33}
|
||||
})
|
||||
{
|
||||
CAPTURE(length)
|
||||
std::set<std::string> distinct;
|
||||
while (distinct.size() < (length == 1 ? 26 : (length == 2 ? 500 : 700))) // (there are 26 and 676 keys of one and two letters)
|
||||
{
|
||||
std::string key(length, 'a');
|
||||
for (char& c : key)
|
||||
{
|
||||
c = static_cast<char>('a' + (rng() % 26));
|
||||
}
|
||||
distinct.insert(key);
|
||||
}
|
||||
CHECK(max_displacement(std::vector<std::string>(distinct.begin(), distinct.end())) < nlohmann::detail::view::index_max_displacement);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("keys of every length that differ in one byte")
|
||||
{
|
||||
// In a large object, a key is found by comparing its bytes with those of the member the
|
||||
// table points to: up to 16 bytes as two overlapping words, longer ones with memcmp.
|
||||
// Keys of every length from 0 to 40, and keys that differ from them in one byte.
|
||||
std::mt19937_64 rng(2026);
|
||||
std::vector<std::string> keys;
|
||||
std::set<std::string> distinct;
|
||||
for (std::size_t length = 0; length <= 40; ++length)
|
||||
{
|
||||
for (int copy = 0; copy < 5; ++copy)
|
||||
{
|
||||
std::string key(length, 'a');
|
||||
for (char& c : key)
|
||||
{
|
||||
c = static_cast<char>('a' + (rng() % 26));
|
||||
}
|
||||
if (distinct.insert(key).second)
|
||||
{
|
||||
keys.push_back(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
std::string text = "{";
|
||||
for (std::size_t i = 0; i < keys.size(); ++i)
|
||||
{
|
||||
text += (i != 0 ? ",\"" : "\"") + keys[i] + "\":" + std::to_string(i);
|
||||
}
|
||||
text += "}";
|
||||
REQUIRE(keys.size() >= 128); // (a table is built for 128 members)
|
||||
const json_document d = json_document::parse(text);
|
||||
const json_view v = d.root();
|
||||
for (std::size_t i = 0; i < keys.size(); ++i)
|
||||
{
|
||||
CAPTURE(keys[i])
|
||||
CHECK(v[keys[i]].get<std::size_t>() == i);
|
||||
CHECK(v.at(keys[i]).get<std::size_t>() == i);
|
||||
CHECK(v.contains(keys[i]));
|
||||
for (std::size_t at = 0; at < keys[i].size(); ++at)
|
||||
{
|
||||
std::string other = keys[i];
|
||||
other[at] = other[at] == 'z' ? 'y' : 'z';
|
||||
CAPTURE(other)
|
||||
CHECK(v.contains(other) == (distinct.count(other) != 0));
|
||||
}
|
||||
CHECK(v.contains(keys[i] + "a") == (distinct.count(keys[i] + "a") != 0));
|
||||
if (!keys[i].empty())
|
||||
{
|
||||
CHECK(v.contains(keys[i].substr(1)) == (distinct.count(keys[i].substr(1)) != 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("shrink_to_fit releases the tables' spare capacity")
|
||||
{
|
||||
const auto make_text = [](int objects, int members)
|
||||
|
||||
@@ -661,115 +661,6 @@ TEST_CASE("json_view edits: replacing arrays and objects by scalars")
|
||||
CHECK(d.root().materialize() == expected);
|
||||
}
|
||||
|
||||
TEST_CASE("json_view edits: arrays whose elements have no subtrees")
|
||||
{
|
||||
// such an array is indexed directly while it is in its parsed layout; edits
|
||||
// that replace elements by scalars keep it, others move it
|
||||
json_editable_document d = json_editable_document::parse("[10,20,30,40]");
|
||||
json expected = json::array({10, 20, 30, 40});
|
||||
const auto check = [&]
|
||||
{
|
||||
CHECK(d.root().size() == expected.size());
|
||||
for (std::size_t i = 0; i < expected.size(); ++i)
|
||||
{
|
||||
CHECK(d.root()[i].materialize() == expected[i]);
|
||||
CHECK(d.root().at(i).materialize() == expected[i]);
|
||||
}
|
||||
CHECK(d.root()[expected.size()].is_discarded());
|
||||
CHECK(d.root().back().materialize() == expected.back());
|
||||
CHECK(d.root().dump() == expected.dump());
|
||||
};
|
||||
check();
|
||||
d.set(d.root()[1], "x");
|
||||
expected[1] = "x";
|
||||
check();
|
||||
d.set(d.root()[3], 2.5);
|
||||
expected[3] = 2.5;
|
||||
check();
|
||||
d.set(d.root()[2], json::array({1, 2}));
|
||||
expected[2] = json::array({1, 2});
|
||||
check();
|
||||
d.push_back(d.root(), 50);
|
||||
expected.push_back(50);
|
||||
check();
|
||||
d.insert(d.root(), 0, true);
|
||||
expected.insert(expected.begin(), true);
|
||||
check();
|
||||
d.erase(d.root(), 1);
|
||||
expected.erase(expected.begin() + 1);
|
||||
check();
|
||||
d.set(d.root()[2], nullptr);
|
||||
expected[2] = nullptr;
|
||||
check();
|
||||
}
|
||||
|
||||
TEST_CASE("json_view edits: materializing values nested deeper than the document was parsed")
|
||||
{
|
||||
// the stack of open containers of materialize() is sized from the nesting of the parsed text;
|
||||
// a value added by an edit can be deeper than that (and than the stack that is local to the call)
|
||||
json_editable_document d = json_editable_document::parse("[1,[2]]");
|
||||
json expected = json::array({1, json::array({2})});
|
||||
json nested = 3;
|
||||
for (int level = 0; level < 40; ++level)
|
||||
{
|
||||
nested = json::array({nested, json{{"k", level}}});
|
||||
}
|
||||
d.push_back(d.root(), nested);
|
||||
expected.push_back(nested);
|
||||
CHECK(d.root().materialize() == expected);
|
||||
CHECK(d.root()[2].materialize() == nested);
|
||||
d.set(d.root()[1][0], nested);
|
||||
expected[1][0] = nested;
|
||||
CHECK(d.root().materialize() == expected);
|
||||
CHECK(d.root().get<json>() == expected);
|
||||
}
|
||||
|
||||
TEST_CASE("json_view edits: an editable document before and after its first edit")
|
||||
{
|
||||
// until the first edit, an editable document is read like a read-only one; views made before the edit
|
||||
// keep working after it
|
||||
const std::string text = R"({"a":[1,2,{"x":"y"},4],"b":{"c":true,"d":null},"e":"text","f":[]})";
|
||||
const json_document read_only = json_document::parse(text);
|
||||
json_editable_document d = json_editable_document::parse(text);
|
||||
const auto root = d.root();
|
||||
const auto same_as_read_only = [&]
|
||||
{
|
||||
CHECK(root.dump() == read_only.root().dump());
|
||||
CHECK(root.dump(2) == read_only.root().dump(2));
|
||||
CHECK(root.materialize() == read_only.root().materialize());
|
||||
CHECK(root["a"][2]["x"].get_string() == "y");
|
||||
CHECK(root["a"].at(3).get<int>() == 4);
|
||||
CHECK(root["a"].back().get<int>() == 4);
|
||||
CHECK(root.contains("b"));
|
||||
CHECK(!root.contains("zz"));
|
||||
CHECK(root.find("e")->get_string() == "text");
|
||||
CHECK(root.find("zz") == root.end());
|
||||
CHECK(root["b"]["c"].get<bool>());
|
||||
CHECK(root.value("e", "none") == "text");
|
||||
std::size_t count = 0;
|
||||
for (const auto m : root["a"])
|
||||
{
|
||||
CHECK(m.materialize() == read_only.root()["a"][count].materialize());
|
||||
++count;
|
||||
}
|
||||
CHECK(count == 4);
|
||||
};
|
||||
same_as_read_only();
|
||||
// an edit that changes a scalar in place, then one that moves an array
|
||||
d.set(root["e"], "other");
|
||||
CHECK(root["e"].get_string() == "other");
|
||||
CHECK(root["a"][2]["x"].get_string() == "y");
|
||||
CHECK(root["a"].back().get<int>() == 4);
|
||||
d.push_back(root["f"], 7);
|
||||
CHECK(root["f"][0].get<int>() == 7);
|
||||
CHECK(root["f"].back().get<int>() == 7);
|
||||
CHECK(root.dump() == R"({"a":[1,2,{"x":"y"},4],"b":{"c":true,"d":null},"e":"other","f":[7]})");
|
||||
d.set(root["a"], 2, json::array({1, 2}));
|
||||
CHECK(root["a"][2][1].get<int>() == 2);
|
||||
CHECK(root["a"].dump() == "[1,2,[1,2],4]");
|
||||
CHECK(root.materialize() == json::parse(R"({"a":[1,2,[1,2],4],"b":{"c":true,"d":null},"e":"other","f":[7]})"));
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// reads of "a" in an object that repeats it: the first member wins, however the
|
||||
|
||||
@@ -1580,3 +1580,94 @@ TEST_CASE("invalid UTF-8 at every position is handled like the reference")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("short strings are written like the reference, as values and as keys")
|
||||
{
|
||||
// strings of up to 16 characters (32 with vector instructions) that need no
|
||||
// escaping are written as a whole (together with the quotes and, for a key,
|
||||
// the colon); everything else, including a string that is merely short,
|
||||
// takes the general path.
|
||||
// Every length around the edges of the size classes of the fast path is
|
||||
// combined with each kind of byte that must send a string to the general
|
||||
// path, at each position, and compared with the reference for values and,
|
||||
// via the value, for keys.
|
||||
const std::vector<std::string> specials =
|
||||
{
|
||||
"\"", "\\", "\n", "\x1f", "\x7f", "\xc3\xa9", "\xe2\x82\xac", "\xf0\x9f\x98\x80", "\xff", "\x80", "\xc3"
|
||||
};
|
||||
|
||||
for (std::size_t length = 0; length <= 36; ++length)
|
||||
{
|
||||
const std::string plain(length, 'k');
|
||||
check_dump_against_reference(plain, false);
|
||||
|
||||
for (const auto& special : specials)
|
||||
{
|
||||
for (std::size_t pos = 0; pos <= length; ++pos)
|
||||
{
|
||||
std::string s = plain;
|
||||
s.insert(pos, special);
|
||||
check_dump_against_reference(s, false);
|
||||
|
||||
for (const bool ensure_ascii :
|
||||
{
|
||||
false, true
|
||||
})
|
||||
{
|
||||
// a key and a value are written alike, in compact and
|
||||
// in pretty-printed form
|
||||
const json value = s;
|
||||
const json object = json::object({{s, 1}});
|
||||
for (const auto error_handler :
|
||||
{
|
||||
json::error_handler_t::replace, json::error_handler_t::keep
|
||||
})
|
||||
{
|
||||
const std::string quoted = value.dump(-1, ' ', ensure_ascii, error_handler);
|
||||
CHECK(object.dump(-1, ' ', ensure_ascii, error_handler) == "{" + quoted + ":1}");
|
||||
CHECK(object.dump(2, ' ', ensure_ascii, error_handler) == "{\n " + quoted + ": 1\n}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("runs of multi-byte characters are escaped like the reference under ensure_ascii")
|
||||
{
|
||||
// a run of well-formed multi-byte characters is escaped straight into the
|
||||
// write buffer; it has to flush that mid-run, stop at an ASCII character or
|
||||
// an ill-formed sequence and leave those to the general path, and pick up
|
||||
// behind what the general path has pending
|
||||
const std::vector<std::string> characters =
|
||||
{
|
||||
"\xc2\x80", "\xdf\xbf", "\xe0\xa0\x80", "\xe2\x82\xac", "\xed\x9f\xbf", "\xef\xbf\xbf",
|
||||
"\xf0\x90\x80\x80", "\xf0\x9f\x98\x80", "\xf4\x8f\xbf\xbf"
|
||||
};
|
||||
const std::vector<std::string> interruptions =
|
||||
{
|
||||
"", "a", "\n", "\"", "\x7f", "\xff", "\x80", "\xc3", "\xe2\x82", "\xf0\x9f\x98", "\xed\xa0\x80"
|
||||
};
|
||||
|
||||
for (std::size_t shift = 0; shift <= 13; ++shift)
|
||||
{
|
||||
for (const auto& interruption : interruptions)
|
||||
{
|
||||
for (const std::size_t count :
|
||||
{
|
||||
std::size_t{1}, std::size_t{2}, std::size_t{85}, std::size_t{200}
|
||||
})
|
||||
{
|
||||
std::string s(shift, 'x');
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
{
|
||||
s += characters[(i + shift) % characters.size()];
|
||||
}
|
||||
s += interruption;
|
||||
s += characters[shift % characters.size()];
|
||||
s += characters[(shift + 3) % characters.size()];
|
||||
check_dump_against_reference(s, shift < 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user