Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff8ba3cb74 | ||
|
|
0c3e83325a | ||
|
|
ebcd4ae4ab | ||
|
|
47462527b2 | ||
|
|
ae30ba55c3 | ||
|
|
731363a325 | ||
|
|
fdcf3aa390 | ||
|
|
209348c151 | ||
|
|
7c3888fe42 | ||
|
|
5bbc7a1098 |
No files matched your search
@@ -33,6 +33,7 @@
|
|||||||
#undef JSON_SCAN_SSSE3_TARGET
|
#undef JSON_SCAN_SSSE3_TARGET
|
||||||
#undef JSON_SCAN_VECTOR
|
#undef JSON_SCAN_VECTOR
|
||||||
#undef JSON_SCAN_VECTOR_UTF8
|
#undef JSON_SCAN_VECTOR_UTF8
|
||||||
|
#undef JSON_DUMP_SHORT_STRINGS
|
||||||
|
|
||||||
#ifndef JSON_TEST_KEEP_MACROS
|
#ifndef JSON_TEST_KEEP_MACROS
|
||||||
#undef JSON_CATCH
|
#undef JSON_CATCH
|
||||||
|
|||||||
@@ -34,6 +34,14 @@
|
|||||||
#include <nlohmann/detail/string_utils.hpp>
|
#include <nlohmann/detail/string_utils.hpp>
|
||||||
#include <nlohmann/detail/value_t.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
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
namespace detail
|
namespace detail
|
||||||
{
|
{
|
||||||
@@ -80,11 +88,14 @@ class serializer
|
|||||||
const std::size_t indent_step_ = 0,
|
const std::size_t indent_step_ = 0,
|
||||||
error_handler_t error_handler_ = error_handler_t::strict)
|
error_handler_t error_handler_ = error_handler_t::strict)
|
||||||
: o(&s)
|
: o(&s)
|
||||||
|
, number_buffer() // NOLINT(readability-redundant-member-init)
|
||||||
|
, string_buffer() // NOLINT(readability-redundant-member-init)
|
||||||
, indent_char(ichar)
|
, indent_char(ichar)
|
||||||
, pretty_print(pretty_print_)
|
, pretty_print(pretty_print_)
|
||||||
, ensure_ascii(ensure_ascii_)
|
, ensure_ascii(ensure_ascii_)
|
||||||
, indent_step(indent_step_)
|
, indent_step(indent_step_)
|
||||||
, error_handler(error_handler_)
|
, error_handler(error_handler_)
|
||||||
|
, write_buffer() // NOLINT(readability-redundant-member-init)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
// deleted because of pointer members
|
// deleted because of pointer members
|
||||||
@@ -159,34 +170,27 @@ class serializer
|
|||||||
|
|
||||||
if (pretty_print)
|
if (pretty_print)
|
||||||
{
|
{
|
||||||
put_literal("{\n");
|
|
||||||
|
|
||||||
// variable to hold indentation for recursive calls
|
// variable to hold indentation for recursive calls
|
||||||
const auto new_indent = next_indent(current_indent, indent_step);
|
const auto new_indent = next_indent(current_indent, indent_step);
|
||||||
|
|
||||||
|
put_literal_indent("{\n", new_indent);
|
||||||
|
|
||||||
// first n-1 elements
|
// first n-1 elements
|
||||||
auto i = val.m_data.m_value.object->cbegin();
|
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)
|
for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
|
||||||
{
|
{
|
||||||
put_indent(new_indent);
|
put_quoted(i->first, "\": ");
|
||||||
put_char('"');
|
dump_element(i->second, new_indent, depth + 1);
|
||||||
dump_escaped(i->first);
|
put_literal_indent(",\n", new_indent);
|
||||||
put_literal("\": ");
|
|
||||||
dump_internal(i->second, new_indent, depth + 1);
|
|
||||||
put_literal(",\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// last element
|
// last element
|
||||||
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
||||||
JSON_ASSERT(std::next(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_quoted(i->first, "\": ");
|
||||||
put_char('"');
|
dump_element(i->second, new_indent, depth + 1);
|
||||||
dump_escaped(i->first);
|
|
||||||
put_literal("\": ");
|
|
||||||
dump_internal(i->second, new_indent, depth + 1);
|
|
||||||
|
|
||||||
put_char('\n');
|
put_literal_indent("\n", current_indent);
|
||||||
put_indent(current_indent);
|
|
||||||
put_char('}');
|
put_char('}');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -197,20 +201,16 @@ class serializer
|
|||||||
auto i = val.m_data.m_value.object->cbegin();
|
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)
|
for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
|
||||||
{
|
{
|
||||||
put_char('"');
|
put_quoted(i->first, "\":");
|
||||||
dump_escaped(i->first);
|
dump_element(i->second, current_indent, depth + 1);
|
||||||
put_literal("\":");
|
|
||||||
dump_internal(i->second, current_indent, depth + 1);
|
|
||||||
put_char(',');
|
put_char(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
// last element
|
// last element
|
||||||
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
||||||
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
||||||
put_char('"');
|
put_quoted(i->first, "\":");
|
||||||
dump_escaped(i->first);
|
dump_element(i->second, current_indent, depth + 1);
|
||||||
put_literal("\":");
|
|
||||||
dump_internal(i->second, current_indent, depth + 1);
|
|
||||||
|
|
||||||
put_char('}');
|
put_char('}');
|
||||||
}
|
}
|
||||||
@@ -234,27 +234,24 @@ class serializer
|
|||||||
|
|
||||||
if (pretty_print)
|
if (pretty_print)
|
||||||
{
|
{
|
||||||
put_literal("[\n");
|
|
||||||
|
|
||||||
// variable to hold indentation for recursive calls
|
// variable to hold indentation for recursive calls
|
||||||
const auto new_indent = next_indent(current_indent, indent_step);
|
const auto new_indent = next_indent(current_indent, indent_step);
|
||||||
|
|
||||||
|
put_literal_indent("[\n", new_indent);
|
||||||
|
|
||||||
// first n-1 elements
|
// first n-1 elements
|
||||||
for (auto i = val.m_data.m_value.array->cbegin();
|
for (auto i = val.m_data.m_value.array->cbegin();
|
||||||
i != val.m_data.m_value.array->cend() - 1; ++i)
|
i != val.m_data.m_value.array->cend() - 1; ++i)
|
||||||
{
|
{
|
||||||
put_indent(new_indent);
|
dump_element(*i, new_indent, depth + 1);
|
||||||
dump_internal(*i, new_indent, depth + 1);
|
put_literal_indent(",\n", new_indent);
|
||||||
put_literal(",\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// last element
|
// last element
|
||||||
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
||||||
put_indent(new_indent);
|
dump_element(val.m_data.m_value.array->back(), new_indent, depth + 1);
|
||||||
dump_internal(val.m_data.m_value.array->back(), new_indent, depth + 1);
|
|
||||||
|
|
||||||
put_char('\n');
|
put_literal_indent("\n", current_indent);
|
||||||
put_indent(current_indent);
|
|
||||||
put_char(']');
|
put_char(']');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -265,13 +262,13 @@ class serializer
|
|||||||
for (auto i = val.m_data.m_value.array->cbegin();
|
for (auto i = val.m_data.m_value.array->cbegin();
|
||||||
i != val.m_data.m_value.array->cend() - 1; ++i)
|
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(',');
|
put_char(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
// last element
|
// last element
|
||||||
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
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(']');
|
put_char(']');
|
||||||
}
|
}
|
||||||
@@ -294,6 +291,27 @@ class serializer
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
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
|
@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_indent(frame.child_indent);
|
||||||
}
|
}
|
||||||
|
|
||||||
put_char('"');
|
|
||||||
dump_escaped(frame.object_it->first);
|
|
||||||
|
|
||||||
if (pretty_print)
|
if (pretty_print)
|
||||||
{
|
{
|
||||||
put_literal("\": ");
|
put_quoted(frame.object_it->first, "\": ");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
put_literal("\":");
|
put_quoted(frame.object_it->first, "\":");
|
||||||
}
|
}
|
||||||
|
|
||||||
const BasicJsonType& element = frame.object_it->second;
|
const BasicJsonType& element = frame.object_it->second;
|
||||||
@@ -537,78 +552,13 @@ class serializer
|
|||||||
{
|
{
|
||||||
case value_t::string:
|
case value_t::string:
|
||||||
{
|
{
|
||||||
put_char('"');
|
dump_string_value(*val.m_data.m_value.string);
|
||||||
dump_escaped(*val.m_data.m_value.string);
|
|
||||||
put_char('"');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::binary:
|
case value_t::binary:
|
||||||
{
|
{
|
||||||
if (pretty_print)
|
dump_binary_value(current_indent, *val.m_data.m_value.binary);
|
||||||
{
|
|
||||||
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}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -627,19 +577,19 @@ class serializer
|
|||||||
|
|
||||||
case value_t::number_integer:
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::number_unsigned:
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::number_float:
|
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;
|
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
|
@brief the indentation level to use for the children of the current value
|
||||||
@@ -678,6 +729,217 @@ class serializer
|
|||||||
return new_indent;
|
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:
|
JSON_PRIVATE_UNLESS_TESTED:
|
||||||
/*!
|
/*!
|
||||||
@brief dump escaped string
|
@brief dump escaped string
|
||||||
@@ -690,14 +952,23 @@ class serializer
|
|||||||
@param[in] s the string to escape
|
@param[in] s the string to escape
|
||||||
|
|
||||||
Complexity: Linear in the length of string @a s.
|
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 -
|
// 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.
|
// 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,
|
// For them, this is cheaper than starting the vector scanner below,
|
||||||
// whose setup costs more than such a string takes to scan. The same
|
// whose setup costs more than such a string takes to scan. The same
|
||||||
// check serves both values of ensure_ascii.
|
// 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)
|
if (s.size() <= 32)
|
||||||
{
|
{
|
||||||
const auto& copyable = ascii_copyable();
|
const auto& copyable = ascii_copyable();
|
||||||
@@ -719,6 +990,7 @@ class serializer
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// dispatch once here rather than test the flag inside the loop: it does
|
// 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
|
// 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);
|
put_buffer(string_buffer, bytes);
|
||||||
bytes = 0;
|
bytes = 0;
|
||||||
}
|
}
|
||||||
put_string(s, i, i + run);
|
put_run(s, i, run);
|
||||||
bytes_after_last_accept = 0;
|
bytes_after_last_accept = 0;
|
||||||
undumped_chars = 0;
|
undumped_chars = 0;
|
||||||
i += run;
|
i += run;
|
||||||
@@ -836,6 +1108,27 @@ class serializer
|
|||||||
|
|
||||||
const auto byte = static_cast<std::uint8_t>(s[i]);
|
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))
|
switch (decode(state, codepoint, byte))
|
||||||
{
|
{
|
||||||
case UTF8_ACCEPT: // decode found a new code point
|
case UTF8_ACCEPT: // decode found a new code point
|
||||||
@@ -1113,6 +1406,21 @@ class serializer
|
|||||||
return;
|
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();
|
const std::size_t capacity = write_buffer.size();
|
||||||
|
|
||||||
// fill whatever room is left in the buffer; this is the whole job
|
// fill whatever room is left in the buffer; this is the whole job
|
||||||
@@ -1146,6 +1454,32 @@ class serializer
|
|||||||
write_buffer_pos = indent;
|
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
|
@brief append a string literal to the write buffer
|
||||||
|
|
||||||
@@ -1170,6 +1504,28 @@ class serializer
|
|||||||
write_buffer_pos += length;
|
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)
|
@brief append the characters of @a str in [@a start, @a end)
|
||||||
|
|
||||||
@@ -1412,6 +1768,75 @@ class serializer
|
|||||||
return escapes;
|
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
|
* @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)
|
/// the output of the serializer (non-owning; the adapter lives at the call site)
|
||||||
output_adapter_protocol<char>* o = nullptr;
|
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
|
/// a (hopefully) large enough character buffer
|
||||||
std::array<char, 64> number_buffer{{}};
|
uninitialized_buffer<64> number_buffer;
|
||||||
|
|
||||||
/// string buffer
|
/// string buffer
|
||||||
std::array<char, 512> string_buffer{{}};
|
uninitialized_buffer<512> string_buffer;
|
||||||
|
|
||||||
/// the indentation character
|
/// the indentation character
|
||||||
const char indent_char;
|
const char indent_char;
|
||||||
@@ -1793,7 +2229,7 @@ class serializer
|
|||||||
/// buffer collecting output before it is flushed to the output adapter, so
|
/// buffer collecting output before it is flushed to the output adapter, so
|
||||||
/// that the many small structural writes become few bulk writes
|
/// that the many small structural writes become few bulk writes
|
||||||
static constexpr std::size_t write_buffer_size = 1024;
|
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
|
/// number of valid bytes currently held in @ref write_buffer
|
||||||
std::size_t write_buffer_pos = 0;
|
std::size_t write_buffer_pos = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -255,82 +255,6 @@ struct number_token
|
|||||||
bool is_float; ///< a fraction or an exponent
|
bool is_float; ///< a fraction or an exponent
|
||||||
};
|
};
|
||||||
|
|
||||||
/// advance over ASCII digits, eight bytes at a time
|
|
||||||
inline const unsigned char* digit_run(const unsigned char* p, const unsigned char* e) noexcept
|
|
||||||
{
|
|
||||||
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
|
||||||
while (e - p >= 8)
|
|
||||||
{
|
|
||||||
// (the digits become 0..9; a byte above 9, or with bit 7 set, is no digit. A carry
|
|
||||||
// out of a byte with bit 7 set only affects the bytes above, after the first one
|
|
||||||
// that count_trailing_zeros finds.)
|
|
||||||
const std::uint64_t x = read_eight_bytes(p) ^ (ones * 0x30u);
|
|
||||||
const std::uint64_t not_digit = ((x + (ones * 0x76u)) | x) & (ones * 0x80u);
|
|
||||||
if (not_digit != 0)
|
|
||||||
{
|
|
||||||
return p + (static_cast<std::size_t>(count_trailing_zeros(not_digit)) / 8);
|
|
||||||
}
|
|
||||||
p += 8;
|
|
||||||
}
|
|
||||||
while (p != e && is_digit(*p))
|
|
||||||
{
|
|
||||||
++p;
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// whether the n bytes at p are ASCII digits (n > 0)
|
|
||||||
inline bool all_digits(const unsigned char* p, std::size_t n) noexcept
|
|
||||||
{
|
|
||||||
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
|
||||||
const auto digits = [](const unsigned char* q)
|
|
||||||
{
|
|
||||||
const std::uint64_t x = read_eight_bytes(q) ^ (ones * 0x30u);
|
|
||||||
return (((x + (ones * 0x76u)) | x) & (ones * 0x80u)) == 0;
|
|
||||||
};
|
|
||||||
if (n >= 8)
|
|
||||||
{
|
|
||||||
std::size_t i = 0;
|
|
||||||
for (; i + 8 < n; i += 8)
|
|
||||||
{
|
|
||||||
if (!digits(p + i))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return digits(p + n - 8); // (overlapping the bytes before: they are digits)
|
|
||||||
}
|
|
||||||
for (std::size_t i = 0; i < n; ++i)
|
|
||||||
{
|
|
||||||
if (!is_digit(p[i]))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether a float token has the shape that the parser records for most of
|
|
||||||
/// them: [-]digits.digits, with the numbers of digits of the node's layout
|
|
||||||
/// (extra). Then the token is a number of that layout, and the general scan
|
|
||||||
/// that follows is not needed.
|
|
||||||
inline bool plain_float_token(const unsigned char* s, std::size_t len, std::uint16_t extra) noexcept
|
|
||||||
{
|
|
||||||
const std::size_t int_digits = extra & 0xFFu;
|
|
||||||
const std::size_t frac_digits = extra >> 8u;
|
|
||||||
if (int_digits == 0 || int_digits >= 255 || frac_digits == 0 || frac_digits >= 255 || len == 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const std::size_t negative = *s == '-' ? 1 : 0;
|
|
||||||
if (len != negative + int_digits + 1 + frac_digits)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const unsigned char* const p = s + negative;
|
|
||||||
return !(int_digits > 1 && *p == '0') && p[int_digits] == '.' && all_digits(p, int_digits) && all_digits(p + int_digits + 1, frac_digits);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// whether [s, s + len) is a JSON number (the grammar the parser accepts)
|
/// whether [s, s + len) is a JSON number (the grammar the parser accepts)
|
||||||
inline bool scan_number_token(const unsigned char* s, std::size_t len, number_token& t)
|
inline bool scan_number_token(const unsigned char* s, std::size_t len, number_token& t)
|
||||||
{
|
{
|
||||||
@@ -349,7 +273,11 @@ inline bool scan_number_token(const unsigned char* s, std::size_t len, number_to
|
|||||||
}
|
}
|
||||||
else if (*p >= '1' && *p <= '9')
|
else if (*p >= '1' && *p <= '9')
|
||||||
{
|
{
|
||||||
p = digit_run(p + 1, e);
|
do
|
||||||
|
{
|
||||||
|
++p;
|
||||||
|
}
|
||||||
|
while (p != e && is_digit(*p));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -361,7 +289,10 @@ inline bool scan_number_token(const unsigned char* s, std::size_t len, number_to
|
|||||||
if (p != e && *p == '.')
|
if (p != e && *p == '.')
|
||||||
{
|
{
|
||||||
const unsigned char* const f0 = ++p;
|
const unsigned char* const f0 = ++p;
|
||||||
p = digit_run(p, e);
|
while (p != e && is_digit(*p))
|
||||||
|
{
|
||||||
|
++p;
|
||||||
|
}
|
||||||
if (p == f0)
|
if (p == f0)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -472,15 +403,10 @@ struct float_range
|
|||||||
template<typename Check>
|
template<typename Check>
|
||||||
bool check_distinct_ranges(std::vector<byte_range>& ranges, Check check)
|
bool check_distinct_ranges(std::vector<byte_range>& ranges, Check check)
|
||||||
{
|
{
|
||||||
// (save() writes the ranges in the order of the nodes, which is sorted already)
|
std::sort(ranges.begin(), ranges.end(), [](const byte_range & a, const byte_range & b)
|
||||||
const auto before = [](const byte_range & a, const byte_range & b)
|
|
||||||
{
|
{
|
||||||
return a.off != b.off ? a.off < b.off : a.len < b.len;
|
return a.off != b.off ? a.off < b.off : a.len < b.len;
|
||||||
};
|
});
|
||||||
if (!std::is_sorted(ranges.begin(), ranges.end(), before))
|
|
||||||
{
|
|
||||||
std::sort(ranges.begin(), ranges.end(), before);
|
|
||||||
}
|
|
||||||
std::size_t end = 0;
|
std::size_t end = 0;
|
||||||
for (std::size_t i = 0; i < ranges.size(); ++i)
|
for (std::size_t i = 0; i < ranges.size(); ++i)
|
||||||
{
|
{
|
||||||
@@ -502,8 +428,7 @@ bool check_distinct_ranges(std::vector<byte_range>& ranges, Check check)
|
|||||||
/// record layouts that match it), tokens must not overlap.
|
/// record layouts that match it), tokens must not overlap.
|
||||||
inline bool check_float_ranges(std::vector<float_range>& ranges, const unsigned char* text)
|
inline bool check_float_ranges(std::vector<float_range>& ranges, const unsigned char* text)
|
||||||
{
|
{
|
||||||
// (save() writes the tokens in the order of the nodes, which is sorted already)
|
std::sort(ranges.begin(), ranges.end(), [](const float_range & a, const float_range & b)
|
||||||
const auto before = [](const float_range & a, const float_range & b)
|
|
||||||
{
|
{
|
||||||
if (a.off != b.off)
|
if (a.off != b.off)
|
||||||
{
|
{
|
||||||
@@ -514,11 +439,7 @@ inline bool check_float_ranges(std::vector<float_range>& ranges, const unsigned
|
|||||||
return a.len < b.len;
|
return a.len < b.len;
|
||||||
}
|
}
|
||||||
return a.extra < b.extra;
|
return a.extra < b.extra;
|
||||||
};
|
});
|
||||||
if (!std::is_sorted(ranges.begin(), ranges.end(), before))
|
|
||||||
{
|
|
||||||
std::sort(ranges.begin(), ranges.end(), before);
|
|
||||||
}
|
|
||||||
std::size_t end = 0;
|
std::size_t end = 0;
|
||||||
std::size_t i = 0;
|
std::size_t i = 0;
|
||||||
while (i < ranges.size())
|
while (i < ranges.size())
|
||||||
@@ -528,18 +449,14 @@ inline bool check_float_ranges(std::vector<float_range>& ranges, const unsigned
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
number_token t{};
|
||||||
const unsigned char* const s = text + r.off;
|
const unsigned char* const s = text + r.off;
|
||||||
std::uint16_t layout = r.extra;
|
if (!scan_number_token(s, r.len, t) || !float_token_finite(s, r.len, t))
|
||||||
if (!plain_float_token(s, r.len, r.extra))
|
|
||||||
{
|
{
|
||||||
number_token t{};
|
return false;
|
||||||
if (!scan_number_token(s, r.len, t) || !float_token_finite(s, r.len, t))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// the digit layout the parser records (or "many", as compaction writes it)
|
|
||||||
layout = float_layout(t);
|
|
||||||
}
|
}
|
||||||
|
// the digit layout the parser records (or "many", as compaction writes it)
|
||||||
|
const std::uint16_t layout = float_layout(t);
|
||||||
for (; i < ranges.size() && ranges[i].off == r.off && ranges[i].len == r.len; ++i)
|
for (; i < ranges.size() && ranges[i].off == r.off && ranges[i].len == r.len; ++i)
|
||||||
{
|
{
|
||||||
if (ranges[i].extra != layout && ranges[i].extra != 0xFFFFu)
|
if (ranges[i].extra != layout && ranges[i].extra != 0xFFFFu)
|
||||||
|
|||||||
@@ -120,74 +120,6 @@ NLOHMANN_VIEW_ALWAYS_INLINE std::uint32_t number_length(const node& n) noexcept
|
|||||||
: (n.extra & 0xFFu) + (n.kind == static_cast<std::uint8_t>(value_t::number_integer) ? 1u : 0u);
|
: (n.extra & 0xFFu) + (n.kind == static_cast<std::uint8_t>(value_t::number_integer) ? 1u : 0u);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// whether n bytes are equal; the short texts of JSON (keys, numbers, most
|
|
||||||
/// strings) are compared by two overlapping loads instead of a library call
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE bool bytes_equal(const char* x, const char* y, std::size_t n) noexcept
|
|
||||||
{
|
|
||||||
const auto load8 = [](const char* p)
|
|
||||||
{
|
|
||||||
std::uint64_t v = 0;
|
|
||||||
std::memcpy(&v, p, sizeof(v));
|
|
||||||
return v;
|
|
||||||
};
|
|
||||||
const auto load4 = [](const char* p)
|
|
||||||
{
|
|
||||||
std::uint32_t v = 0;
|
|
||||||
std::memcpy(&v, p, sizeof(v));
|
|
||||||
return v;
|
|
||||||
};
|
|
||||||
if (n >= 8)
|
|
||||||
{
|
|
||||||
std::size_t i = 0;
|
|
||||||
for (; i + 8 < n; i += 8)
|
|
||||||
{
|
|
||||||
if (load8(x + i) != load8(y + i))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return load8(x + n - 8) == load8(y + n - 8);
|
|
||||||
}
|
|
||||||
if (n >= 4)
|
|
||||||
{
|
|
||||||
return load4(x) == load4(y) && load4(x + n - 4) == load4(y + n - 4);
|
|
||||||
}
|
|
||||||
for (std::size_t i = 0; i < n; ++i)
|
|
||||||
{
|
|
||||||
if (x[i] != y[i])
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// whether the n1 bytes at x are before the n2 bytes at y (memcmp() order, then the shorter first);
|
|
||||||
/// the first difference is found eight bytes at a time
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE bool bytes_less(const char* x, std::size_t n1, const char* y, std::size_t n2) noexcept
|
|
||||||
{
|
|
||||||
const std::size_t n = n1 < n2 ? n1 : n2;
|
|
||||||
std::size_t i = 0;
|
|
||||||
for (; i + 8 <= n; i += 8)
|
|
||||||
{
|
|
||||||
const std::uint64_t u = read_eight_bytes(x + i);
|
|
||||||
const std::uint64_t v = read_eight_bytes(y + i);
|
|
||||||
if (u != v)
|
|
||||||
{
|
|
||||||
const auto shift = static_cast<unsigned>(count_trailing_zeros(u ^ v)) & ~7u; // (the first byte that differs)
|
|
||||||
return ((u >> shift) & 0xFFu) < ((v >> shift) & 0xFFu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (; i < n; ++i)
|
|
||||||
{
|
|
||||||
if (x[i] != y[i])
|
|
||||||
{
|
|
||||||
return static_cast<unsigned char>(x[i]) < static_cast<unsigned char>(y[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return n1 < n2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// estimated number of nodes for an input of `size` bytes (one node per ~12
|
/// estimated number of nodes for an input of `size` bytes (one node per ~12
|
||||||
/// bytes covers typical documents without regrowth)
|
/// bytes covers typical documents without regrowth)
|
||||||
inline std::size_t estimate_nodes(std::size_t size) noexcept
|
inline std::size_t estimate_nodes(std::size_t size) noexcept
|
||||||
|
|||||||
@@ -723,12 +723,8 @@ class basic_json_view
|
|||||||
template<typename, bool> friend class basic_json_document;
|
template<typename, bool> friend class basic_json_document;
|
||||||
template<typename, bool> friend class basic_json_view;
|
template<typename, bool> friend class basic_json_view;
|
||||||
template<typename, typename> friend class detail::view::editor;
|
template<typename, typename> friend class detail::view::editor;
|
||||||
template<typename, typename> friend class detail::view::view_side;
|
|
||||||
friend iterator;
|
friend iterator;
|
||||||
|
|
||||||
/// whether the view is one of an editable document
|
|
||||||
static constexpr bool is_editable = Editable;
|
|
||||||
|
|
||||||
basic_json_view(const document_data* d, const node* n) noexcept
|
basic_json_view(const document_data* d, const node* n) noexcept
|
||||||
: m_doc(d), m_node(n)
|
: m_doc(d), m_node(n)
|
||||||
{}
|
{}
|
||||||
|
|||||||
Generated
+551
-114
@@ -30178,6 +30178,14 @@ NLOHMANN_JSON_NAMESPACE_END
|
|||||||
// #include <nlohmann/detail/value_t.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
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
namespace detail
|
namespace detail
|
||||||
{
|
{
|
||||||
@@ -30224,11 +30232,14 @@ class serializer
|
|||||||
const std::size_t indent_step_ = 0,
|
const std::size_t indent_step_ = 0,
|
||||||
error_handler_t error_handler_ = error_handler_t::strict)
|
error_handler_t error_handler_ = error_handler_t::strict)
|
||||||
: o(&s)
|
: o(&s)
|
||||||
|
, number_buffer() // NOLINT(readability-redundant-member-init)
|
||||||
|
, string_buffer() // NOLINT(readability-redundant-member-init)
|
||||||
, indent_char(ichar)
|
, indent_char(ichar)
|
||||||
, pretty_print(pretty_print_)
|
, pretty_print(pretty_print_)
|
||||||
, ensure_ascii(ensure_ascii_)
|
, ensure_ascii(ensure_ascii_)
|
||||||
, indent_step(indent_step_)
|
, indent_step(indent_step_)
|
||||||
, error_handler(error_handler_)
|
, error_handler(error_handler_)
|
||||||
|
, write_buffer() // NOLINT(readability-redundant-member-init)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
// deleted because of pointer members
|
// deleted because of pointer members
|
||||||
@@ -30303,34 +30314,27 @@ class serializer
|
|||||||
|
|
||||||
if (pretty_print)
|
if (pretty_print)
|
||||||
{
|
{
|
||||||
put_literal("{\n");
|
|
||||||
|
|
||||||
// variable to hold indentation for recursive calls
|
// variable to hold indentation for recursive calls
|
||||||
const auto new_indent = next_indent(current_indent, indent_step);
|
const auto new_indent = next_indent(current_indent, indent_step);
|
||||||
|
|
||||||
|
put_literal_indent("{\n", new_indent);
|
||||||
|
|
||||||
// first n-1 elements
|
// first n-1 elements
|
||||||
auto i = val.m_data.m_value.object->cbegin();
|
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)
|
for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
|
||||||
{
|
{
|
||||||
put_indent(new_indent);
|
put_quoted(i->first, "\": ");
|
||||||
put_char('"');
|
dump_element(i->second, new_indent, depth + 1);
|
||||||
dump_escaped(i->first);
|
put_literal_indent(",\n", new_indent);
|
||||||
put_literal("\": ");
|
|
||||||
dump_internal(i->second, new_indent, depth + 1);
|
|
||||||
put_literal(",\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// last element
|
// last element
|
||||||
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
||||||
JSON_ASSERT(std::next(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_quoted(i->first, "\": ");
|
||||||
put_char('"');
|
dump_element(i->second, new_indent, depth + 1);
|
||||||
dump_escaped(i->first);
|
|
||||||
put_literal("\": ");
|
|
||||||
dump_internal(i->second, new_indent, depth + 1);
|
|
||||||
|
|
||||||
put_char('\n');
|
put_literal_indent("\n", current_indent);
|
||||||
put_indent(current_indent);
|
|
||||||
put_char('}');
|
put_char('}');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -30341,20 +30345,16 @@ class serializer
|
|||||||
auto i = val.m_data.m_value.object->cbegin();
|
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)
|
for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
|
||||||
{
|
{
|
||||||
put_char('"');
|
put_quoted(i->first, "\":");
|
||||||
dump_escaped(i->first);
|
dump_element(i->second, current_indent, depth + 1);
|
||||||
put_literal("\":");
|
|
||||||
dump_internal(i->second, current_indent, depth + 1);
|
|
||||||
put_char(',');
|
put_char(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
// last element
|
// last element
|
||||||
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
||||||
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
||||||
put_char('"');
|
put_quoted(i->first, "\":");
|
||||||
dump_escaped(i->first);
|
dump_element(i->second, current_indent, depth + 1);
|
||||||
put_literal("\":");
|
|
||||||
dump_internal(i->second, current_indent, depth + 1);
|
|
||||||
|
|
||||||
put_char('}');
|
put_char('}');
|
||||||
}
|
}
|
||||||
@@ -30378,27 +30378,24 @@ class serializer
|
|||||||
|
|
||||||
if (pretty_print)
|
if (pretty_print)
|
||||||
{
|
{
|
||||||
put_literal("[\n");
|
|
||||||
|
|
||||||
// variable to hold indentation for recursive calls
|
// variable to hold indentation for recursive calls
|
||||||
const auto new_indent = next_indent(current_indent, indent_step);
|
const auto new_indent = next_indent(current_indent, indent_step);
|
||||||
|
|
||||||
|
put_literal_indent("[\n", new_indent);
|
||||||
|
|
||||||
// first n-1 elements
|
// first n-1 elements
|
||||||
for (auto i = val.m_data.m_value.array->cbegin();
|
for (auto i = val.m_data.m_value.array->cbegin();
|
||||||
i != val.m_data.m_value.array->cend() - 1; ++i)
|
i != val.m_data.m_value.array->cend() - 1; ++i)
|
||||||
{
|
{
|
||||||
put_indent(new_indent);
|
dump_element(*i, new_indent, depth + 1);
|
||||||
dump_internal(*i, new_indent, depth + 1);
|
put_literal_indent(",\n", new_indent);
|
||||||
put_literal(",\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// last element
|
// last element
|
||||||
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
||||||
put_indent(new_indent);
|
dump_element(val.m_data.m_value.array->back(), new_indent, depth + 1);
|
||||||
dump_internal(val.m_data.m_value.array->back(), new_indent, depth + 1);
|
|
||||||
|
|
||||||
put_char('\n');
|
put_literal_indent("\n", current_indent);
|
||||||
put_indent(current_indent);
|
|
||||||
put_char(']');
|
put_char(']');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -30409,13 +30406,13 @@ class serializer
|
|||||||
for (auto i = val.m_data.m_value.array->cbegin();
|
for (auto i = val.m_data.m_value.array->cbegin();
|
||||||
i != val.m_data.m_value.array->cend() - 1; ++i)
|
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(',');
|
put_char(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
// last element
|
// last element
|
||||||
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
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(']');
|
put_char(']');
|
||||||
}
|
}
|
||||||
@@ -30438,6 +30435,27 @@ class serializer
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
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
|
@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_indent(frame.child_indent);
|
||||||
}
|
}
|
||||||
|
|
||||||
put_char('"');
|
|
||||||
dump_escaped(frame.object_it->first);
|
|
||||||
|
|
||||||
if (pretty_print)
|
if (pretty_print)
|
||||||
{
|
{
|
||||||
put_literal("\": ");
|
put_quoted(frame.object_it->first, "\": ");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
put_literal("\":");
|
put_quoted(frame.object_it->first, "\":");
|
||||||
}
|
}
|
||||||
|
|
||||||
const BasicJsonType& element = frame.object_it->second;
|
const BasicJsonType& element = frame.object_it->second;
|
||||||
@@ -30681,78 +30696,13 @@ class serializer
|
|||||||
{
|
{
|
||||||
case value_t::string:
|
case value_t::string:
|
||||||
{
|
{
|
||||||
put_char('"');
|
dump_string_value(*val.m_data.m_value.string);
|
||||||
dump_escaped(*val.m_data.m_value.string);
|
|
||||||
put_char('"');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::binary:
|
case value_t::binary:
|
||||||
{
|
{
|
||||||
if (pretty_print)
|
dump_binary_value(current_indent, *val.m_data.m_value.binary);
|
||||||
{
|
|
||||||
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}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30771,19 +30721,19 @@ class serializer
|
|||||||
|
|
||||||
case value_t::number_integer:
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::number_unsigned:
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::number_float:
|
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;
|
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
|
@brief the indentation level to use for the children of the current value
|
||||||
@@ -30822,6 +30873,217 @@ class serializer
|
|||||||
return new_indent;
|
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:
|
JSON_PRIVATE_UNLESS_TESTED:
|
||||||
/*!
|
/*!
|
||||||
@brief dump escaped string
|
@brief dump escaped string
|
||||||
@@ -30834,14 +31096,23 @@ class serializer
|
|||||||
@param[in] s the string to escape
|
@param[in] s the string to escape
|
||||||
|
|
||||||
Complexity: Linear in the length of string @a s.
|
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 -
|
// 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.
|
// 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,
|
// For them, this is cheaper than starting the vector scanner below,
|
||||||
// whose setup costs more than such a string takes to scan. The same
|
// whose setup costs more than such a string takes to scan. The same
|
||||||
// check serves both values of ensure_ascii.
|
// 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)
|
if (s.size() <= 32)
|
||||||
{
|
{
|
||||||
const auto& copyable = ascii_copyable();
|
const auto& copyable = ascii_copyable();
|
||||||
@@ -30863,6 +31134,7 @@ class serializer
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// dispatch once here rather than test the flag inside the loop: it does
|
// 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
|
// 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);
|
put_buffer(string_buffer, bytes);
|
||||||
bytes = 0;
|
bytes = 0;
|
||||||
}
|
}
|
||||||
put_string(s, i, i + run);
|
put_run(s, i, run);
|
||||||
bytes_after_last_accept = 0;
|
bytes_after_last_accept = 0;
|
||||||
undumped_chars = 0;
|
undumped_chars = 0;
|
||||||
i += run;
|
i += run;
|
||||||
@@ -30980,6 +31252,27 @@ class serializer
|
|||||||
|
|
||||||
const auto byte = static_cast<std::uint8_t>(s[i]);
|
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))
|
switch (decode(state, codepoint, byte))
|
||||||
{
|
{
|
||||||
case UTF8_ACCEPT: // decode found a new code point
|
case UTF8_ACCEPT: // decode found a new code point
|
||||||
@@ -31257,6 +31550,21 @@ class serializer
|
|||||||
return;
|
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();
|
const std::size_t capacity = write_buffer.size();
|
||||||
|
|
||||||
// fill whatever room is left in the buffer; this is the whole job
|
// fill whatever room is left in the buffer; this is the whole job
|
||||||
@@ -31290,6 +31598,32 @@ class serializer
|
|||||||
write_buffer_pos = indent;
|
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
|
@brief append a string literal to the write buffer
|
||||||
|
|
||||||
@@ -31314,6 +31648,28 @@ class serializer
|
|||||||
write_buffer_pos += length;
|
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)
|
@brief append the characters of @a str in [@a start, @a end)
|
||||||
|
|
||||||
@@ -31556,6 +31912,75 @@ class serializer
|
|||||||
return escapes;
|
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
|
* @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)
|
/// the output of the serializer (non-owning; the adapter lives at the call site)
|
||||||
output_adapter_protocol<char>* o = nullptr;
|
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
|
/// a (hopefully) large enough character buffer
|
||||||
std::array<char, 64> number_buffer{{}};
|
uninitialized_buffer<64> number_buffer;
|
||||||
|
|
||||||
/// string buffer
|
/// string buffer
|
||||||
std::array<char, 512> string_buffer{{}};
|
uninitialized_buffer<512> string_buffer;
|
||||||
|
|
||||||
/// the indentation character
|
/// the indentation character
|
||||||
const char indent_char;
|
const char indent_char;
|
||||||
@@ -31937,7 +32373,7 @@ class serializer
|
|||||||
/// buffer collecting output before it is flushed to the output adapter, so
|
/// buffer collecting output before it is flushed to the output adapter, so
|
||||||
/// that the many small structural writes become few bulk writes
|
/// that the many small structural writes become few bulk writes
|
||||||
static constexpr std::size_t write_buffer_size = 1024;
|
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
|
/// number of valid bytes currently held in @ref write_buffer
|
||||||
std::size_t write_buffer_pos = 0;
|
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_SSSE3_TARGET
|
||||||
#undef JSON_SCAN_VECTOR
|
#undef JSON_SCAN_VECTOR
|
||||||
#undef JSON_SCAN_VECTOR_UTF8
|
#undef JSON_SCAN_VECTOR_UTF8
|
||||||
|
#undef JSON_DUMP_SHORT_STRINGS
|
||||||
|
|
||||||
#ifndef JSON_TEST_KEEP_MACROS
|
#ifndef JSON_TEST_KEEP_MACROS
|
||||||
#undef JSON_CATCH
|
#undef JSON_CATCH
|
||||||
|
|||||||
Generated
+438
-1323
File diff suppressed because it is too large.
Load diff
@@ -345,33 +345,6 @@ TEST_CASE("json_view comparison")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("objects whose first keys differ or repeat")
|
|
||||||
{
|
|
||||||
const char* const texts[] =
|
|
||||||
{
|
|
||||||
R"({"a":1,"b":2})", R"({"a":1,"c":2})", R"({"b":1,"a":2})", R"({"a":1,"a":2,"b":3})", R"({"a":2,"b":3})",
|
|
||||||
R"({"a":1,"b":1,"a":2})", R"({"a":2,"b":1})", R"({"a":1,"a":2})", R"({"b":1})", R"({"a":1,"b":2,"c":3,"d":4,"e":5})",
|
|
||||||
R"({"a":1,"b":2,"c":3,"d":4,"f":5})", R"({"a":1,"b":2,"c":3,"a":4,"e":5})", R"({"a":4,"b":2,"c":3,"e":5})",
|
|
||||||
R"({"\u0061":1,"b":2})", R"({})", R"([{"a":1,"b":2}])", R"([{"a":1,"c":2}])"
|
|
||||||
};
|
|
||||||
for (const char* const x : texts)
|
|
||||||
{
|
|
||||||
for (const char* const y : texts)
|
|
||||||
{
|
|
||||||
CAPTURE(x)
|
|
||||||
CAPTURE(y)
|
|
||||||
const ordered_json_document dx = ordered_json_document::parse(x);
|
|
||||||
const ordered_json_document dy = ordered_json_document::parse(y);
|
|
||||||
const ordered_json_editable_document ex = ordered_json_editable_document::parse(x);
|
|
||||||
const ordered_json jx = ordered_json::parse(x);
|
|
||||||
const ordered_json jy = ordered_json::parse(y);
|
|
||||||
CHECK((dx.root() == dy.root()) == (jx == jy));
|
|
||||||
CHECK((dx.root() == jy) == (jx == jy));
|
|
||||||
CHECK((ex.root() == dy.root()) == (jx == jy));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("numbers, duplicate keys, member order")
|
SECTION("numbers, duplicate keys, member order")
|
||||||
{
|
{
|
||||||
const auto same = [](const char* x, const char* y)
|
const auto same = [](const char* x, const char* y)
|
||||||
@@ -412,129 +385,6 @@ TEST_CASE("json_view comparison")
|
|||||||
CHECK(!(null_document.root() == discarded));
|
CHECK(!(null_document.root() == discarded));
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("values that are the same text, or the same value written differently, at any depth")
|
|
||||||
{
|
|
||||||
// nested arrays and objects around depths where the walk over equal text gives up
|
|
||||||
for (const int depth :
|
|
||||||
{
|
|
||||||
1, 5, 47, 48, 49, 50, 60, 300
|
|
||||||
})
|
|
||||||
{
|
|
||||||
CAPTURE(depth)
|
|
||||||
std::string head;
|
|
||||||
std::string tail;
|
|
||||||
for (int i = 0; i < depth; ++i)
|
|
||||||
{
|
|
||||||
head += i % 2 == 0 ? R"({"k":[1,"s",{"x":true},)" : R"([null,2.5,{"y":)";
|
|
||||||
tail = (i % 2 == 0 ? "]}" : "}]") + tail;
|
|
||||||
}
|
|
||||||
// the same text, one number changed, and one number written differently
|
|
||||||
const std::string text = head + "7" + tail;
|
|
||||||
const std::string changed = head + "8" + tail;
|
|
||||||
const std::string spelled = head + "7.0" + tail;
|
|
||||||
const json_document a = json_document::parse(text);
|
|
||||||
const json_document b = json_document::parse(text);
|
|
||||||
const json_document c = json_document::parse(changed);
|
|
||||||
const json_document d = json_document::parse(spelled);
|
|
||||||
CHECK(a.root() == b.root());
|
|
||||||
CHECK(a.root() != c.root());
|
|
||||||
CHECK(c.root() != a.root());
|
|
||||||
CHECK(a.root() == d.root());
|
|
||||||
CHECK(d.root() == a.root());
|
|
||||||
CHECK(a.root() == json::parse(text));
|
|
||||||
CHECK(a.root() != json::parse(changed));
|
|
||||||
CHECK(json::parse(spelled) == a.root());
|
|
||||||
const nlohmann::json_editable_document e = nlohmann::json_editable_document::parse(text);
|
|
||||||
CHECK(a.root() == e.root());
|
|
||||||
CHECK(e.root() == a.root());
|
|
||||||
CHECK(c.root() != e.root());
|
|
||||||
CHECK(e.root() == e.root());
|
|
||||||
const ordered_json_document oa = ordered_json_document::parse(text);
|
|
||||||
const ordered_json_document oc = ordered_json_document::parse(changed);
|
|
||||||
CHECK(oa.root() != oc.root());
|
|
||||||
CHECK(oa.root() == ordered_json::parse(text));
|
|
||||||
CHECK(oc.root() != ordered_json::parse(text));
|
|
||||||
}
|
|
||||||
|
|
||||||
// not the same text, but the same value
|
|
||||||
const auto same = [](const char* x, const char* y)
|
|
||||||
{
|
|
||||||
const json_document dx = json_document::parse(x);
|
|
||||||
const json_document dy = json_document::parse(y);
|
|
||||||
const bool equal = dx.root() == dy.root();
|
|
||||||
CHECK(equal == (json::parse(x) == json::parse(y)));
|
|
||||||
CHECK(equal == (dx.root() == json::parse(y)));
|
|
||||||
return equal;
|
|
||||||
};
|
|
||||||
CHECK(same(R"({"a":1,"a":2})", R"({"a":3,"a":2})"));
|
|
||||||
CHECK(same(R"({"a":1,"b":[1,{"c":2,"c":3}]})", R"({"b":[1,{"c":9,"c":3}],"a":1})"));
|
|
||||||
CHECK(!same(R"({"a":1,"a":2})", R"({"a":2,"a":3})"));
|
|
||||||
CHECK(same("[1.0,-0,0,1e2]", "[1.00,0,-0,100.0]"));
|
|
||||||
CHECK(same(R"(["\u00e9","\n"])", "[\"\xc3\xa9\",\"\\n\"]"));
|
|
||||||
CHECK(!same(R"({"a":[1,2,{"b":null}]})", R"({"a":[1,2,{"b":false}]})"));
|
|
||||||
CHECK(!same(R"({"a":[1,2,{"b":null}]})", R"({"a":[1,2,{"c":null}]})"));
|
|
||||||
CHECK(!same(R"({"a":[1,2,{"b":null}]})", R"({"a":[1,2,{"b":null,"c":1}]})"));
|
|
||||||
|
|
||||||
// edited documents: moved sequences, links, and values of new nodes
|
|
||||||
nlohmann::json_editable_document edited = nlohmann::json_editable_document::parse(R"({"a":[1,2,3],"b":{"c":"d"},"e":0.5})");
|
|
||||||
const json_document plain = json_document::parse(R"({"a":[1,2,3,4],"b":{"c":"x","y":null},"e":0.5,"z":[]})");
|
|
||||||
edited.push_back(edited.root()["a"], 4);
|
|
||||||
edited.set(edited.root()["b"], "c", "x");
|
|
||||||
edited.set(edited.root()["b"], "y", nullptr);
|
|
||||||
edited.set(edited.root(), "z", json::array());
|
|
||||||
CHECK(edited.root() == plain.root());
|
|
||||||
CHECK(plain.root() == edited.root());
|
|
||||||
edited.erase(edited.root()["a"], 0);
|
|
||||||
CHECK(edited.root() != plain.root());
|
|
||||||
CHECK(edited.root()["a"] == json::parse("[2,3,4]"));
|
|
||||||
const json_document tail = json_document::parse("[2,3,4]");
|
|
||||||
CHECK(edited.root()["a"] == tail.root());
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("large documents, with a difference at any place")
|
|
||||||
{
|
|
||||||
const auto make = [](std::size_t count, std::size_t changed, bool swap_members)
|
|
||||||
{
|
|
||||||
std::string text = R"({"b":1,"list":[)";
|
|
||||||
for (std::size_t i = 0; i < count; ++i)
|
|
||||||
{
|
|
||||||
text += (i != 0 ? "," : "");
|
|
||||||
const std::string id = std::to_string(i == changed ? i + 1 : i);
|
|
||||||
text += swap_members && i % 3 == 0 ? R"({"v":[1.5,"s",{"k":)" + id + R"(}],"id":)" + id + "}" : R"({"id":)" + id + R"(,"v":[1.5,"s",{"k":)" + id + "}]}";
|
|
||||||
}
|
|
||||||
return text + R"(],"a":{"y":2,"x":3}})";
|
|
||||||
};
|
|
||||||
const std::size_t count = 3000;
|
|
||||||
for (const std::size_t changed :
|
|
||||||
{
|
|
||||||
count, std::size_t{0}, std::size_t{1}, count / 2, count - 1
|
|
||||||
})
|
|
||||||
{
|
|
||||||
for (const bool swap :
|
|
||||||
{
|
|
||||||
false, true
|
|
||||||
})
|
|
||||||
{
|
|
||||||
CAPTURE(changed)
|
|
||||||
CAPTURE(swap)
|
|
||||||
const std::string text_a = make(count, count, false);
|
|
||||||
const std::string text_b = make(count, changed, swap);
|
|
||||||
const json_document a = json_document::parse(text_a);
|
|
||||||
const json_document b = json_document::parse(text_b);
|
|
||||||
const bool equal = changed == count;
|
|
||||||
CHECK((a.root() == b.root()) == equal);
|
|
||||||
CHECK((b.root() == a.root()) == equal);
|
|
||||||
CHECK((a.root() == json::parse(text_b)) == equal);
|
|
||||||
CHECK((json::parse(text_b) == a.root()) == equal);
|
|
||||||
CHECK((a.root()["list"] == b.root()["list"]) == equal);
|
|
||||||
const ordered_json_document oa = ordered_json_document::parse(text_a);
|
|
||||||
const ordered_json_document ob = ordered_json_document::parse(text_b);
|
|
||||||
CHECK((oa.root() == ob.root()) == (equal && !swap));
|
|
||||||
CHECK((oa.root() == ordered_json::parse(text_b)) == (equal && !swap));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("deep nesting")
|
SECTION("deep nesting")
|
||||||
{
|
{
|
||||||
const std::string deep = std::string(100000, '[') + std::string(100000, ']');
|
const std::string deep = std::string(100000, '[') + std::string(100000, ']');
|
||||||
|
|||||||
@@ -682,66 +682,6 @@ TEST_CASE("json_view images: ownership")
|
|||||||
#if !defined(JSON_NOEXCEPTION)
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
TEST_CASE("json_view images: errors")
|
TEST_CASE("json_view images: errors")
|
||||||
{
|
{
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
|
||||||
SECTION("float tokens: a changed byte is accepted if the text is still a number with the digits the node records")
|
|
||||||
{
|
|
||||||
for (const char* const token :
|
|
||||||
{
|
|
||||||
"-12.375", "0.5", "123456.0", "-0.0", "9999999999999999999.5", "0.000000000000000000000001", "1.25e10", "5E-3"
|
|
||||||
})
|
|
||||||
{
|
|
||||||
CAPTURE(token)
|
|
||||||
const std::string text = std::string("[") + token + "]";
|
|
||||||
const json_document d = json_document::parse(text);
|
|
||||||
const std::vector<std::uint8_t> image = d.save();
|
|
||||||
const std::size_t at = text_at(image) + 1; // the first byte of the token
|
|
||||||
const std::uint16_t recorded = node_at(image, 1).extra;
|
|
||||||
CHECK(loaded_dump(image) == json::parse(text).dump());
|
|
||||||
for (std::size_t pos = 0; token[pos] != '\0'; ++pos)
|
|
||||||
{
|
|
||||||
for (unsigned int byte = 0; byte < 256; ++byte)
|
|
||||||
{
|
|
||||||
std::vector<std::uint8_t> changed = image;
|
|
||||||
changed[at + pos] = static_cast<std::uint8_t>(byte);
|
|
||||||
std::string t = token;
|
|
||||||
t[pos] = static_cast<char>(byte);
|
|
||||||
// the digits of the integer part and of the fraction of the changed token
|
|
||||||
std::size_t integer = 0;
|
|
||||||
std::size_t fraction = 0;
|
|
||||||
std::size_t k = (t[0] == '-') ? 1 : 0;
|
|
||||||
for (; k < t.size() && t[k] >= '0' && t[k] <= '9'; ++k)
|
|
||||||
{
|
|
||||||
++integer;
|
|
||||||
}
|
|
||||||
if (k < t.size() && t[k] == '.')
|
|
||||||
{
|
|
||||||
for (++k; k < t.size() && t[k] >= '0' && t[k] <= '9'; ++k)
|
|
||||||
{
|
|
||||||
++fraction;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// (as a JSON text, the token may have white space around it)
|
|
||||||
const bool float_token = json::accept(t) && t.find_first_of(std::string(" \t\r\n\0", 5)) == std::string::npos && (t.find('.') != std::string::npos || t.find_first_of("eE") != std::string::npos)
|
|
||||||
&& ((integer | (fraction << 8U)) == recorded);
|
|
||||||
CAPTURE(pos)
|
|
||||||
CAPTURE(byte)
|
|
||||||
std::string result;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
result = loaded_dump(changed);
|
|
||||||
}
|
|
||||||
catch (const json::parse_error& e)
|
|
||||||
{
|
|
||||||
CHECK(std::string(e.what()) == check_failed);
|
|
||||||
result = "rejected";
|
|
||||||
}
|
|
||||||
CHECK((result != "rejected") == float_token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SECTION("a literal as the root: dump() after loading")
|
SECTION("a literal as the root: dump() after loading")
|
||||||
{
|
{
|
||||||
for (const char* text :
|
for (const char* text :
|
||||||
|
|||||||
@@ -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